diff --git a/README.md b/README.md index 1c0dd3df..dee51ab9 100644 --- a/README.md +++ b/README.md @@ -13,15 +13,17 @@ Website: [davidederosa.com][me-website] The client is known to work with [OpenVPNĀ®][openvpn] 2.3+ servers. Key renegotiation and replay protection are also included, but full-fledged configuration files (.ovpn) are not currently supported. - [x] Handshake and tunneling over UDP or TCP -- [x] Client-initiated renegotiation -- [x] Replay protection (hardcoded window) -- [x] Data encryption +- [x] Ciphers - AES-CBC (128 and 256 bit) - AES-GCM (128 and 256 bit) -- [x] HMAC digest +- [x] HMAC digests - SHA-1 - SHA-256 -- [x] TLS CA validation +- [x] TLS handshake + - CA validation + - Client certificate +- [x] Key renegotiation (client-initiated) +- [x] Replay protection (hardcoded window) The library does not currently support compression, so you must disable it server-side in order to avoid a confusing loss of data packets. The `TunnelKitProvider.Configuration.LZOFraming` option is deprecated and only provided for interoperability with `comp-lzo no`. @@ -73,7 +75,7 @@ For the VPN to work properly, the `BasicTunnel` demo requires: both in the main app and the tunnel extension target. -In order to test connection to your own server, modify the file `Demo/BasicTunnel-[iOS|macOS]/ViewController.swift` and make sure to set `builder.ca` to the PEM encoded certificate of your VPN server's CA (or `nil` if none). +In order to test connection to your own server, modify the file `Demo/BasicTunnel-[iOS|macOS]/ViewController.swift` and make sure to set `builder.ca` to the PEM encoded certificate of your VPN server's CA (or `nil` to skip CA validation, however discouraged). Example: diff --git a/TunnelKit.xcodeproj/project.pbxproj b/TunnelKit.xcodeproj/project.pbxproj index 417322ab..e4b93079 100644 --- a/TunnelKit.xcodeproj/project.pbxproj +++ b/TunnelKit.xcodeproj/project.pbxproj @@ -57,8 +57,8 @@ 0EC1BBA620D712DE007C4C7B /* DNSResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC1BBA420D71190007C4C7B /* DNSResolver.swift */; }; 0EC1BBA820D7D803007C4C7B /* ConnectionStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC1BBA720D7D803007C4C7B /* ConnectionStrategy.swift */; }; 0EC1BBA920D7D803007C4C7B /* ConnectionStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC1BBA720D7D803007C4C7B /* ConnectionStrategy.swift */; }; - 0ECE3528212EB7770040F253 /* Certificate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECE3527212EB7770040F253 /* Certificate.swift */; }; - 0ECE352A212EB88E0040F253 /* Certificate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECE3527212EB7770040F253 /* Certificate.swift */; }; + 0ECE3528212EB7770040F253 /* CryptoContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECE3527212EB7770040F253 /* CryptoContainer.swift */; }; + 0ECE352A212EB88E0040F253 /* CryptoContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECE3527212EB7770040F253 /* CryptoContainer.swift */; }; 0EE7A79520F61EDC00B42E6A /* PacketMacros.h in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7A79420F61EDC00B42E6A /* PacketMacros.h */; }; 0EE7A79620F61EDC00B42E6A /* PacketMacros.h in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7A79420F61EDC00B42E6A /* PacketMacros.h */; }; 0EE7A79820F6296F00B42E6A /* PacketMacros.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7A79720F6296F00B42E6A /* PacketMacros.m */; }; @@ -206,7 +206,7 @@ 0EBBF2FF2085196000E36B40 /* NWTCPConnectionState+Description.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NWTCPConnectionState+Description.swift"; sourceTree = ""; }; 0EC1BBA420D71190007C4C7B /* DNSResolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DNSResolver.swift; sourceTree = ""; }; 0EC1BBA720D7D803007C4C7B /* ConnectionStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionStrategy.swift; sourceTree = ""; }; - 0ECE3527212EB7770040F253 /* Certificate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Certificate.swift; sourceTree = ""; }; + 0ECE3527212EB7770040F253 /* CryptoContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptoContainer.swift; sourceTree = ""; }; 0EE7A79420F61EDC00B42E6A /* PacketMacros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PacketMacros.h; sourceTree = ""; }; 0EE7A79720F6296F00B42E6A /* PacketMacros.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PacketMacros.m; sourceTree = ""; }; 0EE7A79D20F6488400B42E6A /* DataPathEncryption.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DataPathEncryption.h; sourceTree = ""; }; @@ -451,8 +451,8 @@ isa = PBXGroup; children = ( 0EBBF2E32084FDF400E36B40 /* Transport */, - 0ECE3527212EB7770040F253 /* Certificate.swift */, 0EC1BBA720D7D803007C4C7B /* ConnectionStrategy.swift */, + 0ECE3527212EB7770040F253 /* CryptoContainer.swift */, 0EC1BBA420D71190007C4C7B /* DNSResolver.swift */, 0EBBF2E42084FE6F00E36B40 /* GenericSocket.swift */, 0EFEB4AA200760EC00F81029 /* InterfaceObserver.swift */, @@ -848,7 +848,7 @@ 0EFEB4AC200760EC00F81029 /* InterfaceObserver.swift in Sources */, 0EFEB46D2006D3C800F81029 /* Data+Manipulation.swift in Sources */, 0EFEB47B2006D3C800F81029 /* TunnelKitProvider.swift in Sources */, - 0ECE3528212EB7770040F253 /* Certificate.swift in Sources */, + 0ECE3528212EB7770040F253 /* CryptoContainer.swift in Sources */, 0EFEB4742006D3C800F81029 /* CoreConfiguration.swift in Sources */, 0E07595F20EF6D1400F38FD8 /* CryptoCBC.m in Sources */, 0EC1BBA820D7D803007C4C7B /* ConnectionStrategy.swift in Sources */, @@ -899,7 +899,7 @@ 0EFEB4A22006D7F300F81029 /* CoreConfiguration.swift in Sources */, 0EFEB4952006D7F300F81029 /* SecureRandom.swift in Sources */, 0EFEB49A2006D7F300F81029 /* MSS.m in Sources */, - 0ECE352A212EB88E0040F253 /* Certificate.swift in Sources */, + 0ECE352A212EB88E0040F253 /* CryptoContainer.swift in Sources */, 0EFEB48D2006D7F300F81029 /* SessionProxy+EncryptionBridge.swift in Sources */, 0EFEB4922006D7F300F81029 /* ZeroingData.m in Sources */, 0E07596020EF6D1400F38FD8 /* CryptoCBC.m in Sources */, diff --git a/TunnelKit/Sources/AppExtension/Certificate.swift b/TunnelKit/Sources/AppExtension/CryptoContainer.swift similarity index 90% rename from TunnelKit/Sources/AppExtension/Certificate.swift rename to TunnelKit/Sources/AppExtension/CryptoContainer.swift index 8ec39538..14acc141 100644 --- a/TunnelKit/Sources/AppExtension/Certificate.swift +++ b/TunnelKit/Sources/AppExtension/CryptoContainer.swift @@ -1,5 +1,5 @@ // -// Certificate.swift +// CryptoContainer.swift // TunnelKit // // Created by Davide De Rosa on 8/22/18. @@ -37,10 +37,10 @@ import Foundation -/// Represents a TLS certificate in PEM format. -public struct Certificate: Equatable { +/// Represents a cryptographic container in PEM format. +public struct CryptoContainer: Equatable { - /// The content of the certificates in PEM format (ASCII). + /// The content in PEM format (ASCII). public let pem: String /// :nodoc: @@ -55,7 +55,7 @@ public struct Certificate: Equatable { // MARK: Equatable /// :nodoc: - public static func ==(lhs: Certificate, rhs: Certificate) -> Bool { + public static func ==(lhs: CryptoContainer, rhs: CryptoContainer) -> Bool { return lhs.pem == rhs.pem } } diff --git a/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift b/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift index aec95d6f..454ab8a7 100644 --- a/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift +++ b/TunnelKit/Sources/AppExtension/TunnelKitProvider+Configuration.swift @@ -152,7 +152,13 @@ extension TunnelKitProvider { public var digest: SessionProxy.Digest /// The optional CA certificate to validate server against. Set to `nil` to disable CA validation (default). - public var ca: Certificate? + public var ca: CryptoContainer? + + /// The optional client certificate to authenticate with. Set to `nil` to disable client authentication (default). + public var clientCertificate: CryptoContainer? + + /// The optional key for `clientCertificate`. Set to `nil` if client authentication unused (default). + public var clientKey: CryptoContainer? /// The MTU of the link. public var mtu: Int @@ -211,13 +217,26 @@ extension TunnelKitProvider { throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.digestAlgorithm)]") } - let ca: Certificate? - if let caPEM = providerConfiguration[S.ca] as? String { - ca = Certificate(pem: caPEM) + let ca: CryptoContainer? + let clientCertificate: CryptoContainer? + let clientKey: CryptoContainer? + if let pem = providerConfiguration[S.ca] as? String { + ca = CryptoContainer(pem: pem) } else { ca = nil } + if let pem = providerConfiguration[S.clientCertificate] as? String { + guard let keyPEM = providerConfiguration[S.clientKey] as? String else { + throw ProviderError.configuration(field: "protocolConfiguration.providerConfiguration[\(S.clientKey)]") + } + clientCertificate = CryptoContainer(pem: pem) + clientKey = CryptoContainer(pem: keyPEM) + } else { + clientCertificate = nil + clientKey = nil + } + prefersResolvedAddresses = providerConfiguration[S.prefersResolvedAddresses] as? Bool ?? false resolvedAddresses = providerConfiguration[S.resolvedAddresses] as? [String] guard let endpointProtocolsStrings = providerConfiguration[S.endpointProtocols] as? [String], !endpointProtocolsStrings.isEmpty else { @@ -243,6 +262,8 @@ extension TunnelKitProvider { self.cipher = cipher self.digest = digest self.ca = ca + self.clientCertificate = clientCertificate + self.clientKey = clientKey mtu = providerConfiguration[S.mtu] as? Int ?? 1250 LZOFraming = providerConfiguration[S.LZOFraming] as? Bool ?? false renegotiatesAfterSeconds = providerConfiguration[S.renegotiatesAfter] as? Int @@ -277,6 +298,8 @@ extension TunnelKitProvider { cipher: cipher, digest: digest, ca: ca, + clientCertificate: clientCertificate, + clientKey: clientKey, mtu: mtu, LZOFraming: LZOFraming, renegotiatesAfterSeconds: renegotiatesAfterSeconds, @@ -304,6 +327,10 @@ extension TunnelKitProvider { static let ca = "CA" + static let clientCertificate = "ClientCertificate" + + static let clientKey = "ClientKey" + static let mtu = "MTU" static let LZOFraming = "LZOFraming" @@ -336,7 +363,13 @@ extension TunnelKitProvider { public let digest: SessionProxy.Digest /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.ca` - public let ca: Certificate? + public let ca: CryptoContainer? + + /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.clientCertificate` + public let clientCertificate: CryptoContainer? + + /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.clientKey` + public let clientKey: CryptoContainer? /// - Seealso: `TunnelKitProvider.ConfigurationBuilder.mtu` public let mtu: Int @@ -405,6 +438,12 @@ extension TunnelKitProvider { if let ca = ca { dict[S.ca] = ca.pem } + if let clientCertificate = clientCertificate { + dict[S.clientCertificate] = clientCertificate.pem + } + if let clientKey = clientKey { + dict[S.clientKey] = clientKey.pem + } if let resolvedAddresses = resolvedAddresses { dict[S.resolvedAddresses] = resolvedAddresses } @@ -464,6 +503,11 @@ extension TunnelKitProvider { } else { log.info("CA verification: disabled") } + if let _ = clientCertificate { + log.info("Client verification: enabled") + } else { + log.info("Client verification: disabled") + } log.info("MTU: \(mtu)") log.info("LZO framing: \(LZOFraming ? "enabled" : "disabled")") if let renegotiatesAfterSeconds = renegotiatesAfterSeconds { @@ -491,7 +535,10 @@ extension TunnelKitProvider.Configuration: Equatable { builder.cipher = cipher builder.digest = digest builder.ca = ca + builder.clientCertificate = clientCertificate + builder.clientKey = clientKey builder.mtu = mtu + builder.LZOFraming = LZOFraming builder.renegotiatesAfterSeconds = renegotiatesAfterSeconds builder.shouldDebug = shouldDebug builder.debugLogKey = debugLogKey @@ -505,6 +552,8 @@ extension TunnelKitProvider.Configuration: Equatable { (lhs.cipher == rhs.cipher) && (lhs.digest == rhs.digest) && (lhs.ca == rhs.ca) && + (lhs.clientCertificate == rhs.clientCertificate) && + (lhs.clientKey == rhs.clientKey) && (lhs.mtu == rhs.mtu) && (lhs.LZOFraming == rhs.LZOFraming) && (lhs.renegotiatesAfterSeconds == rhs.renegotiatesAfterSeconds) diff --git a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift index 57967742..96c1fa6b 100644 --- a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift +++ b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift @@ -82,14 +82,12 @@ open class TunnelKitProvider: NEPacketTunnelProvider { private let prngSeedLength = 64 - private let caTmpFilename = "CA.pem" - private var cachesURL: URL { return URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0]) } - - private var tmpCaURL: URL { - return cachesURL.appendingPathComponent(caTmpFilename) + + private func temporaryURL(forKey key: String) -> URL { + return cachesURL.appendingPathComponent("\(key).pem") } // MARK: Tunnel configuration @@ -169,10 +167,13 @@ open class TunnelKitProvider: NEPacketTunnelProvider { } let caPath: String? + let clientCertificatePath: String? + let clientKeyPath: String? if let ca = cfg.ca { do { - try ca.write(to: tmpCaURL) - caPath = tmpCaURL.path + let url = temporaryURL(forKey: Configuration.Keys.ca) + try ca.write(to: url) + caPath = url.path } catch { completionHandler(ProviderError.certificateSerialization) return @@ -180,6 +181,30 @@ open class TunnelKitProvider: NEPacketTunnelProvider { } else { caPath = nil } + if let clientCertificate = cfg.clientCertificate { + do { + let url = temporaryURL(forKey: Configuration.Keys.clientCertificate) + try clientCertificate.write(to: url) + clientCertificatePath = url.path + } catch { + completionHandler(ProviderError.certificateSerialization) + return + } + } else { + clientCertificatePath = nil + } + if let clientKey = cfg.clientKey { + do { + let url = temporaryURL(forKey: Configuration.Keys.clientKey) + try clientKey.write(to: url) + clientKeyPath = url.path + } catch { + completionHandler(ProviderError.certificateSerialization) + return + } + } else { + clientKeyPath = nil + } cfg.print(appVersion: appVersion) @@ -188,6 +213,8 @@ open class TunnelKitProvider: NEPacketTunnelProvider { sessionConfiguration.cipher = cfg.cipher sessionConfiguration.digest = cfg.digest sessionConfiguration.caPath = caPath + sessionConfiguration.clientCertificatePath = clientCertificatePath + sessionConfiguration.clientKeyPath = clientKeyPath sessionConfiguration.LZOFraming = cfg.LZOFraming if let renegotiatesAfterSeconds = cfg.renegotiatesAfterSeconds { sessionConfiguration.renegotiatesAfter = Double(renegotiatesAfterSeconds) @@ -336,7 +363,9 @@ open class TunnelKitProvider: NEPacketTunnelProvider { // stopped externally, unrecoverable else { let fm = FileManager.default - try? fm.removeItem(at: tmpCaURL) + for key in [Configuration.Keys.ca, Configuration.Keys.clientCertificate, Configuration.Keys.clientKey] { + try? fm.removeItem(at: temporaryURL(forKey: key)) + } cancelTunnelWithError(error) } } diff --git a/TunnelKit/Sources/Core/Errors.h b/TunnelKit/Sources/Core/Errors.h index c474cd75..953ca7a1 100644 --- a/TunnelKit/Sources/Core/Errors.h +++ b/TunnelKit/Sources/Core/Errors.h @@ -39,15 +39,17 @@ extern NSString *const TunnelKitErrorDomain; typedef NS_ENUM(NSInteger, TunnelKitErrorCode) { - TunnelKitErrorCodeCryptoBoxRandomGenerator = 101, - TunnelKitErrorCodeCryptoBoxHMAC, - TunnelKitErrorCodeCryptoBoxEncryption, - TunnelKitErrorCodeCryptoBoxAlgorithm, - TunnelKitErrorCodeTLSBoxCA = 201, - TunnelKitErrorCodeTLSBoxHandshake, - TunnelKitErrorCodeTLSBoxGeneric, - TunnelKitErrorCodeDataPathOverflow = 301, - TunnelKitErrorCodeDataPathPeerIdMismatch + TunnelKitErrorCodeCryptoBoxRandomGenerator = 101, + TunnelKitErrorCodeCryptoBoxHMAC = 102, + TunnelKitErrorCodeCryptoBoxEncryption = 103, + TunnelKitErrorCodeCryptoBoxAlgorithm = 104, + TunnelKitErrorCodeTLSBoxCA = 201, + TunnelKitErrorCodeTLSBoxHandshake = 202, + TunnelKitErrorCodeTLSBoxGeneric = 203, + TunnelKitErrorCodeTLSBoxClientCertificate = 204, + TunnelKitErrorCodeTLSBoxClientKey = 205, + TunnelKitErrorCodeDataPathOverflow = 301, + TunnelKitErrorCodeDataPathPeerIdMismatch = 302 }; static inline NSError *TunnelKitErrorWithCode(TunnelKitErrorCode code) { diff --git a/TunnelKit/Sources/Core/SessionProxy+Configuration.swift b/TunnelKit/Sources/Core/SessionProxy+Configuration.swift index 77382fbd..67d659ff 100644 --- a/TunnelKit/Sources/Core/SessionProxy+Configuration.swift +++ b/TunnelKit/Sources/Core/SessionProxy+Configuration.swift @@ -91,6 +91,12 @@ extension SessionProxy { // @available(*, deprecated) public var LZOFraming: Bool + /// The path to the optional client certificate for TLS negotiation (PEM format). + public var clientCertificatePath: String? + + /// The path to the private key for the certificate at `clientCertificatePath` (PEM format). + public var clientKeyPath: String? + /// Sends periodical keep-alive packets if set. public var keepAliveInterval: TimeInterval? @@ -104,6 +110,8 @@ extension SessionProxy { cipher = .aes128cbc digest = .sha1 caPath = nil + clientCertificatePath = nil + clientKeyPath = nil LZOFraming = false keepAliveInterval = nil renegotiatesAfter = nil @@ -121,6 +129,8 @@ extension SessionProxy { cipher: cipher, digest: digest, caPath: caPath, + clientCertificatePath: clientCertificatePath, + clientKeyPath: clientKeyPath, LZOFraming: LZOFraming, keepAliveInterval: keepAliveInterval, renegotiatesAfter: renegotiatesAfter @@ -146,6 +156,12 @@ extension SessionProxy { /// - Seealso: `SessionProxy.ConfigurationBuilder.caPath` public let caPath: String? + /// - Seealso: `SessionProxy.ConfigurationBuilder.clientCertificatePath` + public let clientCertificatePath: String? + + /// - Seealso: `SessionProxy.ConfigurationBuilder.clientKeyPath` + public let clientKeyPath: String? + /// - Seealso: `SessionProxy.ConfigurationBuilder.LZOFraming` public let LZOFraming: Bool diff --git a/TunnelKit/Sources/Core/SessionProxy.swift b/TunnelKit/Sources/Core/SessionProxy.swift index 5039b388..9f1148b1 100644 --- a/TunnelKit/Sources/Core/SessionProxy.swift +++ b/TunnelKit/Sources/Core/SessionProxy.swift @@ -767,7 +767,11 @@ public class SessionProxy { log.debug("Remote sessionId is \(remoteSessionId.toHex())") log.debug("Start TLS handshake") - negotiationKey.tlsOptional = TLSBox(caPath: configuration.caPath) + negotiationKey.tlsOptional = TLSBox( + caPath: configuration.caPath, + clientCertificatePath: configuration.clientCertificatePath, + clientKeyPath: configuration.clientKeyPath + ) do { try negotiationKey.tls.start() } catch let e { diff --git a/TunnelKit/Sources/Core/TLSBox.h b/TunnelKit/Sources/Core/TLSBox.h index 0ae8505f..291f33b9 100644 --- a/TunnelKit/Sources/Core/TLSBox.h +++ b/TunnelKit/Sources/Core/TLSBox.h @@ -49,7 +49,9 @@ extern NSString *const TLSBoxPeerVerificationErrorNotification; // @interface TLSBox : NSObject -- (nonnull instancetype)initWithCAPath:(nullable NSString *)caPath; +- (nonnull instancetype)initWithCAPath:(NSString *)caPath + clientCertificatePath:(NSString *)clientCertificatePath + clientKeyPath:(NSString *)clientKeyPath; - (BOOL)startWithError:(NSError **)error; diff --git a/TunnelKit/Sources/Core/TLSBox.m b/TunnelKit/Sources/Core/TLSBox.m index 3e937010..078cc7a2 100644 --- a/TunnelKit/Sources/Core/TLSBox.m +++ b/TunnelKit/Sources/Core/TLSBox.m @@ -59,6 +59,8 @@ int TLSBoxVerifyPeer(int ok, X509_STORE_CTX *ctx) { @interface TLSBox () @property (nonatomic, strong) NSString *caPath; +@property (nonatomic, strong) NSString *clientCertificatePath; +@property (nonatomic, strong) NSString *clientKeyPath; @property (nonatomic, assign) BOOL isConnected; @property (nonatomic, unsafe_unretained) SSL_CTX *ctx; @@ -75,13 +77,15 @@ @implementation TLSBox - (instancetype)init { - return [self initWithCAPath:nil]; + return [self initWithCAPath:nil clientCertificatePath:nil clientKeyPath:nil]; } -- (instancetype)initWithCAPath:(NSString *)caPath +- (instancetype)initWithCAPath:(NSString *)caPath clientCertificatePath:(NSString *)clientCertificatePath clientKeyPath:(NSString *)clientKeyPath { if ((self = [super init])) { self.caPath = caPath; + self.clientCertificatePath = clientCertificatePath; + self.clientKeyPath = clientKeyPath; self.bufferCipherText = allocate_safely(TLSBoxMaxBufferLength); } return self; @@ -106,13 +110,11 @@ - (void)dealloc - (BOOL)startWithError:(NSError *__autoreleasing *)error { if (!TLSBoxIsOpenSSLLoaded) { -// OPENSSL_init_ssl(0, NULL); - TLSBoxIsOpenSSLLoaded = YES; } self.ctx = SSL_CTX_new(TLS_client_method()); - SSL_CTX_set_options(self.ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_COMPRESSION); + SSL_CTX_set_options(self.ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION); if (self.caPath) { SSL_CTX_set_verify(self.ctx, SSL_VERIFY_PEER, TLSBoxVerifyPeer); if (!SSL_CTX_load_verify_locations(self.ctx, [self.caPath cStringUsingEncoding:NSASCIIStringEncoding], NULL)) { @@ -126,7 +128,26 @@ - (BOOL)startWithError:(NSError *__autoreleasing *)error else { SSL_CTX_set_verify(self.ctx, SSL_VERIFY_NONE, NULL); } - SSL_CTX_set1_curves_list(self.ctx, "X25519:prime256v1:secp521r1:secp384r1:secp256k1"); + + if (self.clientCertificatePath) { + if (!SSL_CTX_use_certificate_file(self.ctx, [self.clientCertificatePath cStringUsingEncoding:NSASCIIStringEncoding], SSL_FILETYPE_PEM)) { + ERR_print_errors_fp(stdout); + if (error) { + *error = TunnelKitErrorWithCode(TunnelKitErrorCodeTLSBoxClientCertificate); + } + return NO; + } + + if (self.clientKeyPath) { + if (!SSL_CTX_use_PrivateKey_file(self.ctx, [self.clientKeyPath cStringUsingEncoding:NSASCIIStringEncoding], SSL_FILETYPE_PEM)) { + ERR_print_errors_fp(stdout); + if (error) { + *error = TunnelKitErrorWithCode(TunnelKitErrorCodeTLSBoxClientKey); + } + return NO; + } + } + } self.ssl = SSL_new(self.ctx); diff --git a/TunnelKitTests/AppExtensionTests.swift b/TunnelKitTests/AppExtensionTests.swift index 8e480fb8..7c5c2621 100644 --- a/TunnelKitTests/AppExtensionTests.swift +++ b/TunnelKitTests/AppExtensionTests.swift @@ -68,7 +68,7 @@ class AppExtensionTests: XCTestCase { builder.cipher = .aes128cbc builder.digest = .sha256 - builder.ca = Certificate(pem: "abcdef") + builder.ca = CryptoContainer(pem: "abcdef") cfg = builder.build() let proto = try? cfg.generatedTunnelProtocol(withBundleIdentifier: identifier, endpoint: endpoint)