summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2021-05-13 11:01:04 +0200
committerAndrej Mihajlov <and@mullvad.net>2021-05-18 10:51:40 +0200
commit1364ed1ea6b6897509e7f9ae792fbcf8312f141f (patch)
treef3406a8f60a0dbdabc448c9b3b2ca769c1bf9bfc
parent5bdce484c807042d1319ca11b123c220795e134c (diff)
downloadmullvadvpn-1364ed1ea6b6897509e7f9ae792fbcf8312f141f.tar.xz
mullvadvpn-1364ed1ea6b6897509e7f9ae792fbcf8312f141f.zip
Add SSL pinning
-rw-r--r--ios/Assets/new_le_root_cert.cerbin0 -> 1391 bytes
-rw-r--r--ios/Assets/old_le_root_cert.cerbin0 -> 846 bytes
-rw-r--r--ios/BuildInstructions.md9
-rw-r--r--ios/CHANGELOG.md1
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj28
-rw-r--r--ios/MullvadVPN/MullvadRest.swift23
-rw-r--r--ios/MullvadVPN/SSLPinningURLSessionDelegate.swift73
-rwxr-xr-xios/update-relays.sh6
8 files changed, 137 insertions, 3 deletions
diff --git a/ios/Assets/new_le_root_cert.cer b/ios/Assets/new_le_root_cert.cer
new file mode 100644
index 0000000000..9d2132e7f1
--- /dev/null
+++ b/ios/Assets/new_le_root_cert.cer
Binary files differ
diff --git a/ios/Assets/old_le_root_cert.cer b/ios/Assets/old_le_root_cert.cer
new file mode 100644
index 0000000000..95500f6bd1
--- /dev/null
+++ b/ios/Assets/old_le_root_cert.cer
Binary files differ
diff --git a/ios/BuildInstructions.md b/ios/BuildInstructions.md
index 13f84867ce..4c73601ee3 100644
--- a/ios/BuildInstructions.md
+++ b/ios/BuildInstructions.md
@@ -173,3 +173,12 @@ where `<KEYCHAIN>` is the name of the target Keychain where the signing credenti
This guide does not use a separate Keychain store, so use `login.keychain-db` then.
Reference: https://docs.travis-ci.com/user/common-build-problems/#mac-macos-sierra-1012-code-signing-errors
+
+# SSL pinning
+
+The iOS app utilizes SSL pinning. Root certificates can be updated by using the source certificates shipped along with `mullvad-rpc`:
+
+```
+openssl x509 -in ../mullvad-rpc/new_le_root_cert.pem -outform der -out Assets/new_le_root_cert.cer
+openssl x509 -in ../mullvad-rpc/old_le_root_cert.pem -outform der -out Assets/old_le_root_cert.cer
+```
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md
index 5c089e7252..f27785e79f 100644
--- a/ios/CHANGELOG.md
+++ b/ios/CHANGELOG.md
@@ -29,6 +29,7 @@ Line wrap the file at 100 chars. Th
- Add interactive map.
- Reduce network traffic consumption by leveraging HTTP caching via ETag HTTP header to avoid
re-downloading the relay list if it hasn't changed.
+- Pin root SSL certificates.
### Fixed
- Fix bug which caused the tunnel manager to become unresponsive in the rare event of failure to
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 91ba67a7ff..4ec0dcc42a 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -73,6 +73,14 @@
584592612639B4A200EF967F /* ConsentContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584592602639B4A200EF967F /* ConsentContentView.swift */; };
5845F842236CBACD00B2D93C /* PacketTunnelIpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5845F841236CBACD00B2D93C /* PacketTunnelIpc.swift */; };
5845F843236CBDAB00B2D93C /* PacketTunnelIpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5845F841236CBACD00B2D93C /* PacketTunnelIpc.swift */; };
+ 584789B8264D4A2A000E45FB /* old_le_root_cert.cer in Resources */ = {isa = PBXBuildFile; fileRef = 584789B4264D4A2A000E45FB /* old_le_root_cert.cer */; };
+ 584789B9264D4A2A000E45FB /* old_le_root_cert.cer in Resources */ = {isa = PBXBuildFile; fileRef = 584789B4264D4A2A000E45FB /* old_le_root_cert.cer */; };
+ 584789BE264D4A2A000E45FB /* new_le_root_cert.cer in Resources */ = {isa = PBXBuildFile; fileRef = 584789B7264D4A2A000E45FB /* new_le_root_cert.cer */; };
+ 584789BF264D4A2A000E45FB /* new_le_root_cert.cer in Resources */ = {isa = PBXBuildFile; fileRef = 584789B7264D4A2A000E45FB /* new_le_root_cert.cer */; };
+ 584789E026529D72000E45FB /* SSLPinningURLSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584789DF26529D72000E45FB /* SSLPinningURLSessionDelegate.swift */; };
+ 584789E126529D72000E45FB /* SSLPinningURLSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584789DF26529D72000E45FB /* SSLPinningURLSessionDelegate.swift */; };
+ 584789E626529DEF000E45FB /* SSLPinningURLSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584789DF26529D72000E45FB /* SSLPinningURLSessionDelegate.swift */; };
+ 584789EC2652A1A2000E45FB /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 584789EB2652A1A2000E45FB /* Logging */; };
584E96BC240FD4DA00D3334F /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A1AA8623F43901009F7EA6 /* Location.swift */; };
584E96BD240FD4DA00D3334F /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A1AA8623F43901009F7EA6 /* Location.swift */; };
584E96BE240FD4DB00D3334F /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A1AA8623F43901009F7EA6 /* Location.swift */; };
@@ -318,6 +326,9 @@
5840250322B11AB700E4CFEC /* MullvadEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadEndpoint.swift; sourceTree = "<group>"; };
584592602639B4A200EF967F /* ConsentContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsentContentView.swift; sourceTree = "<group>"; };
5845F841236CBACD00B2D93C /* PacketTunnelIpc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelIpc.swift; sourceTree = "<group>"; };
+ 584789B4264D4A2A000E45FB /* old_le_root_cert.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = old_le_root_cert.cer; sourceTree = "<group>"; };
+ 584789B7264D4A2A000E45FB /* new_le_root_cert.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = new_le_root_cert.cer; sourceTree = "<group>"; };
+ 584789DF26529D72000E45FB /* SSLPinningURLSessionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSLPinningURLSessionDelegate.swift; sourceTree = "<group>"; };
584B26F3237434D00073B10E /* RelaySelectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelectorTests.swift; sourceTree = "<group>"; };
5850366725A47AC700A43E93 /* IPAddressRange+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IPAddressRange+Codable.swift"; sourceTree = "<group>"; };
58561C98239A5D1500BD6B5E /* IPEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPEndpoint.swift; sourceTree = "<group>"; };
@@ -432,6 +443,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 584789EC2652A1A2000E45FB /* Logging in Frameworks */,
58871D1E25D535A3002297FA /* WireGuardKit in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -665,6 +677,7 @@
58B993B02608A34500BA7811 /* LoginContentView.swift */,
58FEEB57260B662E00A621A8 /* AutomaticKeyboardResponder.swift */,
5868BD32261DCD2600E6027F /* CustomSplitViewController.swift */,
+ 584789DF26529D72000E45FB /* SSLPinningURLSessionDelegate.swift */,
);
path = MullvadVPN;
sourceTree = "<group>";
@@ -703,6 +716,8 @@
58F3C0A824A50C0E003E76BE /* Assets */ = {
isa = PBXGroup;
children = (
+ 584789B7264D4A2A000E45FB /* new_le_root_cert.cer */,
+ 584789B4264D4A2A000E45FB /* old_le_root_cert.cer */,
58F3C0A524A50155003E76BE /* relays.json */,
);
path = Assets;
@@ -744,6 +759,7 @@
name = MullvadVPNTests;
packageProductDependencies = (
58871D1D25D535A3002297FA /* WireGuardKit */,
+ 584789EB2652A1A2000E45FB /* Logging */,
);
productName = MullvadVPNTests;
productReference = 58B0A2A0238EE67E00BC001D /* MullvadVPNTests.xctest */;
@@ -897,6 +913,8 @@
586ADD4723FC13F400CE9E87 /* countries.geo.json in Resources */,
58CE5E6E224146210008646E /* LaunchScreen.storyboard in Resources */,
58CE5E6B224146210008646E /* Assets.xcassets in Resources */,
+ 584789B8264D4A2A000E45FB /* old_le_root_cert.cer in Resources */,
+ 584789BE264D4A2A000E45FB /* new_le_root_cert.cer in Resources */,
58CE5E69224146200008646E /* Main.storyboard in Resources */,
58E5BC2624FEB6DB00A53A76 /* AccountViewController.xib in Resources */,
58B9814E24FEA70D00C0D59E /* WireguardKeysViewController.xib in Resources */,
@@ -907,7 +925,9 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 584789B9264D4A2A000E45FB /* old_le_root_cert.cer in Resources */,
58F3C0A724A50C02003E76BE /* relays.json in Resources */,
+ 584789BF264D4A2A000E45FB /* new_le_root_cert.cer in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -937,6 +957,7 @@
5807E2C3243203E700F5FF30 /* String+Split.swift in Sources */,
5896AE82246ACE84005B36CB /* KeychainReturn.swift in Sources */,
58B0A2A8238EE68200BC001D /* RelaySelectorTests.swift in Sources */,
+ 584789E626529DEF000E45FB /* SSLPinningURLSessionDelegate.swift in Sources */,
584E96BE240FD4DB00D3334F /* Location.swift in Sources */,
5857F23F24C844AD00CF6F47 /* Locking.swift in Sources */,
5857F23424C8443700CF6F47 /* AsyncOperation.swift in Sources */,
@@ -985,6 +1006,7 @@
580EE21524B3231200F9D8A1 /* OperationBlockObserver.swift in Sources */,
58BFA5C622A7C97F00A6173D /* RelayCache.swift in Sources */,
582BB1B1229569620055B6EF /* CustomNavigationBar.swift in Sources */,
+ 584789E026529D72000E45FB /* SSLPinningURLSessionDelegate.swift in Sources */,
588D2FE3248AC27F00E313F7 /* AsyncOperation.swift in Sources */,
5877153023981F7B001F8237 /* WireguardKeysViewController.swift in Sources */,
5850367F25A481D800A43E93 /* IPAddressRange+Codable.swift in Sources */,
@@ -1092,6 +1114,7 @@
buildActionMask = 2147483647;
files = (
58CB0EE124B86751001EF0D8 /* MullvadRest.swift in Sources */,
+ 584789E126529D72000E45FB /* SSLPinningURLSessionDelegate.swift in Sources */,
580EE21F24B3237F00F9D8A1 /* OutputOperation.swift in Sources */,
5850366825A47AC700A43E93 /* IPAddressRange+Codable.swift in Sources */,
58F7D310250FA12E0097BE4E /* AnyIPEndpoint.swift in Sources */,
@@ -1618,6 +1641,11 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
+ 584789EB2652A1A2000E45FB /* Logging */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 585834F624D2BC1F00A8AF56 /* XCRemoteSwiftPackageReference "swift-log" */;
+ productName = Logging;
+ };
585834F724D2BC1F00A8AF56 /* Logging */ = {
isa = XCSwiftPackageProductDependency;
package = 585834F624D2BC1F00A8AF56 /* XCRemoteSwiftPackageReference "swift-log" */;
diff --git a/ios/MullvadVPN/MullvadRest.swift b/ios/MullvadVPN/MullvadRest.swift
index ae002dab75..4d6b6da45d 100644
--- a/ios/MullvadVPN/MullvadRest.swift
+++ b/ios/MullvadVPN/MullvadRest.swift
@@ -8,6 +8,7 @@
import Foundation
import Network
+import Security
import WireGuardKit
/// REST API v1 base URL
@@ -415,8 +416,26 @@ struct RestSessionEndpoint<Input, Response> where Input: RestPayload {
// MARK: - REST interface
-struct MullvadRest {
- let session = URLSession(configuration: .ephemeral)
+class MullvadRest {
+ let session: URLSession
+
+ private let sessionDelegate: SSLPinningURLSessionDelegate
+
+ /// Returns array of trusted root certificates
+ private static var trustedRootCertificates: [SecCertificate] {
+ let oldRootCertificate = Bundle.main.path(forResource: "old_le_root_cert", ofType: "cer")!
+ let newRootCertificate = Bundle.main.path(forResource: "new_le_root_cert", ofType: "cer")!
+
+ return [oldRootCertificate, newRootCertificate].map { (path) -> SecCertificate in
+ let data = FileManager.default.contents(atPath: path)!
+ return SecCertificateCreateWithData(nil, data as CFData)!
+ }
+ }
+
+ init() {
+ sessionDelegate = SSLPinningURLSessionDelegate(trustedRootCertificates: Self.trustedRootCertificates)
+ session = URLSession(configuration: .ephemeral, delegate: sessionDelegate, delegateQueue: nil)
+ }
func createAccount() -> RestSessionEndpoint<EmptyPayload, AccountResponse> {
return RestSessionEndpoint(session: session, endpoint: Self.createAccount())
diff --git a/ios/MullvadVPN/SSLPinningURLSessionDelegate.swift b/ios/MullvadVPN/SSLPinningURLSessionDelegate.swift
new file mode 100644
index 0000000000..73df25f55d
--- /dev/null
+++ b/ios/MullvadVPN/SSLPinningURLSessionDelegate.swift
@@ -0,0 +1,73 @@
+//
+// SSLPinningURLSessionDelegate.swift
+// MullvadVPN
+//
+// Created by pronebird on 17/05/2021.
+// Copyright © 2021 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import Logging
+
+class SSLPinningURLSessionDelegate: NSObject, URLSessionDelegate {
+
+ private let trustedRootCertificates: [SecCertificate]
+ private let logger = Logger(label: "SSLPinningURLSessionDelegate")
+
+ init(trustedRootCertificates: [SecCertificate]) {
+ self.trustedRootCertificates = trustedRootCertificates
+ }
+
+ // MARK: - URLSessionDelegate
+
+ func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
+ let evaluation: (disposition: URLSession.AuthChallengeDisposition, credential: URLCredential?)
+
+ if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
+ if let serverTrust = challenge.protectionSpace.serverTrust, self.verifyServerTrust(serverTrust) {
+ evaluation = (.useCredential, URLCredential(trust: serverTrust))
+ } else {
+ evaluation = (.cancelAuthenticationChallenge, nil)
+ }
+ } else {
+ evaluation = (.rejectProtectionSpace, nil)
+ }
+
+ completionHandler(evaluation.disposition, evaluation.credential)
+ }
+
+
+ // MARK: - Private
+
+ private func verifyServerTrust(_ serverTrust: SecTrust) -> Bool {
+ // Set trusted root certificates
+ var secResult = SecTrustSetAnchorCertificates(serverTrust, trustedRootCertificates as CFArray)
+ guard secResult == errSecSuccess else {
+ self.logger.error("SecTrustSetAnchorCertificates failure: \(self.formatErrorMessage(code: secResult))")
+ return false
+ }
+
+ // Tell security framework to only trust the provided root certificates
+ secResult = SecTrustSetAnchorCertificatesOnly(serverTrust, true)
+ guard secResult == errSecSuccess else {
+ self.logger.error("SecTrustSetAnchorCertificatesOnly failure: \(self.formatErrorMessage(code: secResult))")
+ return false
+ }
+
+ var error: CFError?
+ if SecTrustEvaluateWithError(serverTrust, &error) {
+ return true
+ } else {
+ self.logger.error("SecTrustEvaluateWithError failure: \(error?.localizedDescription ?? "<nil>")")
+ return false
+ }
+ }
+
+ private func formatErrorMessage(code: OSStatus) -> String {
+ let message = SecCopyErrorMessageString(code, nil) as String? ?? "<nil>"
+
+ return "\(message) (code: \(code))"
+ }
+
+
+}
diff --git a/ios/update-relays.sh b/ios/update-relays.sh
index efb0a8a65c..97fd3beafc 100755
--- a/ios/update-relays.sh
+++ b/ios/update-relays.sh
@@ -9,7 +9,11 @@ RELAYS_FILE="$PROJECT_DIR/Assets/relays.json"
if [ $CONFIGURATION == "Release" ]; then
echo "Remove relays file"
- rm "$RELAYS_FILE" || true
+ if [ -f "$RELAYS_FILE" ]; then
+ rm "$RELAYS_FILE"
+ else
+ echo "Relays file does not exist"
+ fi
fi
if [ ! -f "$RELAYS_FILE" ]; then