diff --git a/Sources/CTunnelKitOpenVPNProtocol/TLSBox.m b/Sources/CTunnelKitOpenVPNProtocol/TLSBox.m index f8928d2..b47c0b4 100644 --- a/Sources/CTunnelKitOpenVPNProtocol/TLSBox.m +++ b/Sources/CTunnelKitOpenVPNProtocol/TLSBox.m @@ -62,7 +62,7 @@ const NSInteger TLSBoxDefaultSecurityLevel = 0; @interface TLSBox () -@property (nonatomic, strong) NSString *caPEM; +@property (nonatomic, strong) NSString *caPath; @property (nonatomic, strong) NSString *clientCertificatePEM; @property (nonatomic, strong) NSString *clientKeyPEM; @property (nonatomic, assign) BOOL checksEKU; @@ -113,33 +113,6 @@ static BIO *create_BIO_from_PEM(NSString *pem) { return hex; } -+ (NSString *)md5ForCertificatePEM:(NSString *)pem error:(NSError * _Nullable __autoreleasing * _Nullable)error -{ - const EVP_MD *alg = EVP_get_digestbyname("MD5"); - uint8_t md[16]; - unsigned int len; - - BIO *bio = create_BIO_from_PEM(pem); - if (!bio) { - return NULL; - } - X509 *cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); - if (!cert) { - BIO_free(bio); - return NULL; - } - X509_digest(cert, alg, md, &len); - X509_free(cert); - BIO_free(bio); - NSCAssert2(len == sizeof(md), @"Unexpected MD5 size (%d != %lu)", len, sizeof(md)); - - NSMutableString *hex = [[NSMutableString alloc] initWithCapacity:2 * sizeof(md)]; - for (int i = 0; i < sizeof(md); ++i) { - [hex appendFormat:@"%02x", md[i]]; - } - return hex; -} - + (NSString *)decryptedPrivateKeyFromPath:(NSString *)path passphrase:(NSString *)passphrase error:(NSError * _Nullable __autoreleasing *)error { BIO *bio; @@ -200,15 +173,15 @@ static BIO *create_BIO_from_PEM(NSString *pem) { return nil; } -- (instancetype)initWithCA:(nonnull NSString *)caPEM - clientCertificate:(nullable NSString *)clientCertificatePEM - clientKey:(nullable NSString *)clientKeyPEM - checksEKU:(BOOL)checksEKU - checksSANHost:(BOOL)checksSANHost - hostname:(nullable NSString *)hostname +- (instancetype)initWithCAPath:(nonnull NSString *)caPath + clientCertificate:(nullable NSString *)clientCertificatePEM + clientKey:(nullable NSString *)clientKeyPEM + checksEKU:(BOOL)checksEKU + checksSANHost:(BOOL)checksSANHost + hostname:(nullable NSString *)hostname { if ((self = [super init])) { - self.caPEM = caPEM; + self.caPath = caPath; self.clientCertificatePEM = clientCertificatePEM; self.clientKeyPEM = clientKeyPEM; self.checksEKU = checksEKU; @@ -245,34 +218,14 @@ static BIO *create_BIO_from_PEM(NSString *pem) { SSL_CTX_set_security_level(self.ctx, (int)self.securityLevel); } - if (self.caPEM) { - BIO *bio = create_BIO_from_PEM(self.caPEM); - if (!bio) { - if (error) { - *error = OpenVPNErrorWithCode(OpenVPNErrorCodeTLSCARead); - } - return NO; - } - X509 *ca = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL); - if (!ca) { - if (error) { - *error = OpenVPNErrorWithCode(OpenVPNErrorCodeTLSCARead); - } - BIO_free(bio); - return NO; - } - BIO_free(bio); - X509_STORE *trustedStore = SSL_CTX_get_cert_store(self.ctx); - X509_STORE_set_flags(trustedStore, X509_V_FLAG_PARTIAL_CHAIN); - if (!X509_STORE_add_cert(trustedStore, ca)) { + if (self.caPath) { + if (!SSL_CTX_load_verify_locations(self.ctx, [self.caPath cStringUsingEncoding:NSASCIIStringEncoding], NULL)) { ERR_print_errors_fp(stdout); if (error) { *error = OpenVPNErrorWithCode(OpenVPNErrorCodeTLSCAUse); } - X509_free(ca); return NO; } - X509_free(ca); } if (self.clientCertificatePEM) { diff --git a/Sources/CTunnelKitOpenVPNProtocol/include/TLSBox.h b/Sources/CTunnelKitOpenVPNProtocol/include/TLSBox.h index 3d9e54d..a80c170 100644 --- a/Sources/CTunnelKitOpenVPNProtocol/include/TLSBox.h +++ b/Sources/CTunnelKitOpenVPNProtocol/include/TLSBox.h @@ -55,16 +55,15 @@ extern const NSInteger TLSBoxDefaultSecurityLevel; @property (nonatomic, assign) NSInteger securityLevel; // TLSBoxDefaultSecurityLevel for default + (nullable NSString *)md5ForCertificatePath:(NSString *)path error:(NSError **)error; -+ (nullable NSString *)md5ForCertificatePEM:(NSString *)pem error:(NSError **)error; + (nullable NSString *)decryptedPrivateKeyFromPath:(NSString *)path passphrase:(NSString *)passphrase error:(NSError **)error; + (nullable NSString *)decryptedPrivateKeyFromPEM:(NSString *)pem passphrase:(NSString *)passphrase error:(NSError **)error; -- (instancetype)initWithCA:(nonnull NSString *)caPEM - clientCertificate:(nullable NSString *)clientCertificatePEM - clientKey:(nullable NSString *)clientKeyPEM - checksEKU:(BOOL)checksEKU - checksSANHost:(BOOL)checksSANHost - hostname:(nullable NSString *)hostname; +- (instancetype)initWithCAPath:(nonnull NSString *)caPath + clientCertificate:(nullable NSString *)clientCertificatePEM + clientKey:(nullable NSString *)clientKeyPEM + checksEKU:(BOOL)checksEKU + checksSANHost:(BOOL)checksSANHost + hostname:(nullable NSString *)hostname; - (BOOL)startWithError:(NSError **)error; diff --git a/Sources/TunnelKitOpenVPNAppExtension/OpenVPNTunnelProvider.swift b/Sources/TunnelKitOpenVPNAppExtension/OpenVPNTunnelProvider.swift index 9b96227..e977158 100644 --- a/Sources/TunnelKitOpenVPNAppExtension/OpenVPNTunnelProvider.swift +++ b/Sources/TunnelKitOpenVPNAppExtension/OpenVPNTunnelProvider.swift @@ -241,7 +241,7 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider { let session: OpenVPNSession do { - session = try OpenVPNSession(queue: tunnelQueue, configuration: cfg.sessionConfiguration) + session = try OpenVPNSession(queue: tunnelQueue, configuration: cfg.sessionConfiguration, cachesURL: cachesURL) refreshDataCount() } catch let e { completionHandler(e) diff --git a/Sources/TunnelKitOpenVPNProtocol/OpenVPNSession.swift b/Sources/TunnelKitOpenVPNProtocol/OpenVPNSession.swift index 35bade5..6a4486b 100644 --- a/Sources/TunnelKitOpenVPNProtocol/OpenVPNSession.swift +++ b/Sources/TunnelKitOpenVPNProtocol/OpenVPNSession.swift @@ -72,6 +72,10 @@ public class OpenVPNSession: Session { case reconnect } + private struct Caches { + static let ca = "ca.pem" + } + // MARK: Configuration /// The session base configuration. @@ -166,6 +170,14 @@ public class OpenVPNSession: Session { private var authenticator: OpenVPN.Authenticator? + // MARK: Caching + + private let cachesURL: URL + + private var caURL: URL { + return cachesURL.appendingPathComponent(Caches.ca) + } + // MARK: Init /** @@ -174,13 +186,14 @@ public class OpenVPNSession: Session { - Parameter queue: The `DispatchQueue` where to run the session loop. - Parameter configuration: The `Configuration` to use for this session. */ - public init(queue: DispatchQueue, configuration: OpenVPN.Configuration) throws { - guard let _ = configuration.ca else { + public init(queue: DispatchQueue, configuration: OpenVPN.Configuration, cachesURL: URL) throws { + guard let ca = configuration.ca else { throw ConfigurationError.missingConfiguration(option: "ca") } self.queue = queue self.configuration = configuration + self.cachesURL = cachesURL withLocalOptions = true keys = [:] @@ -201,10 +214,14 @@ public class OpenVPNSession: Session { } else { controlChannel = OpenVPN.ControlChannel() } + + // cache CA locally (mandatory for OpenSSL) + try ca.pem.write(to: caURL, atomically: true, encoding: .ascii) } deinit { cleanup() + cleanupCache() } // MARK: Session @@ -315,6 +332,13 @@ public class OpenVPNSession: Session { stopError = nil } + func cleanupCache() { + let fm = FileManager.default + for url in [caURL] { + try? fm.removeItem(at: url) + } + } + // MARK: Loop // Ruby: start @@ -576,13 +600,13 @@ public class OpenVPNSession: Session { private func hardResetPayload() -> Data? { guard !(configuration.usesPIAPatches ?? false) else { - guard let ca = configuration.ca else { + guard let _ = configuration.ca else { log.error("Configuration doesn't have a CA") return nil } let caMD5: String do { - caMD5 = try TLSBox.md5(forCertificatePEM: ca.pem) + caMD5 = try TLSBox.md5(forCertificatePath: caURL.path) } catch { log.error("CA MD5 could not be computed, skipping custom HARD_RESET") return nil @@ -723,7 +747,7 @@ public class OpenVPNSession: Session { return } - guard let ca = configuration.ca else { + guard let _ = configuration.ca else { log.error("Configuration doesn't have a CA") return } @@ -751,7 +775,7 @@ public class OpenVPNSession: Session { log.debug("Start TLS handshake") let tls = TLSBox( - ca: ca.pem, + caPath: caURL.path, clientCertificate: configuration.clientCertificate?.pem, clientKey: configuration.clientKey?.pem, checksEKU: configuration.checksEKU ?? false, @@ -1215,6 +1239,7 @@ public class OpenVPNSession: Session { switch method { case .shutdown: self?.doShutdown(error: error) + self?.cleanupCache() case .reconnect: self?.doReconnect(error: error) diff --git a/Tests/TunnelKitOpenVPNTests/EncryptionTests.swift b/Tests/TunnelKitOpenVPNTests/EncryptionTests.swift index 090e5fb..0502a4e 100644 --- a/Tests/TunnelKitOpenVPNTests/EncryptionTests.swift +++ b/Tests/TunnelKitOpenVPNTests/EncryptionTests.swift @@ -118,11 +118,6 @@ class EncryptionTests: XCTestCase { let exp = "e2fccccaba712ccc68449b1c56427ac1" print(md5) XCTAssertEqual(md5, exp) - - let pem = try! String(contentsOfFile: path, encoding: .ascii) - let md5FromPEM = try! TLSBox.md5(forCertificatePEM: pem) - print(md5FromPEM) - XCTAssertEqual(md5FromPEM, exp) } func testPrivateKeyDecryption() {