Restore and fix former PEM caching PR (#235)

This reverts commit 995009121a.

* Improve error handling

* Trust intermediate CA

* Update CHANGELOG
This commit is contained in:
Davide De Rosa 2021-11-25 12:36:17 +01:00 committed by GitHub
parent e8f7778179
commit 7a85d3cac7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 165 additions and 98 deletions

View File

@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Revert to OpenSSL. [#233](https://github.com/passepartoutvpn/tunnelkit/pull/233) - Revert to OpenSSL. [#233](https://github.com/passepartoutvpn/tunnelkit/pull/233)
### Fixed
- Restore and fix "Avoid caching PEMs on disk" (roop). [#213](https://github.com/passepartoutvpn/tunnelkit/pull/213)
## 4.0.1 (2021-11-18) ## 4.0.1 (2021-11-18)
### Fixed ### Fixed
@ -33,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Avoid caching PEMs on disk (roop). [#213](https://github.com/passepartoutvpn/tunnelkit/pull/213)
- Upgrade OpenSSL to 1.1.1l. - Upgrade OpenSSL to 1.1.1l.
### Fixed ### Fixed

View File

@ -44,13 +44,17 @@ typedef NS_ENUM(NSInteger, OpenVPNErrorCode) {
OpenVPNErrorCodeCryptoHMAC = 102, OpenVPNErrorCodeCryptoHMAC = 102,
OpenVPNErrorCodeCryptoEncryption = 103, OpenVPNErrorCodeCryptoEncryption = 103,
OpenVPNErrorCodeCryptoAlgorithm = 104, OpenVPNErrorCodeCryptoAlgorithm = 104,
OpenVPNErrorCodeTLSCertificateAuthority = 201, OpenVPNErrorCodeTLSCARead = 201,
OpenVPNErrorCodeTLSHandshake = 202, OpenVPNErrorCodeTLSCAUse = 202,
OpenVPNErrorCodeTLSClientCertificate = 204, OpenVPNErrorCodeTLSCAPeerVerification = 203,
OpenVPNErrorCodeTLSClientKey = 205, OpenVPNErrorCodeTLSClientCertificateRead = 204,
OpenVPNErrorCodeTLSServerCertificate = 206, OpenVPNErrorCodeTLSClientCertificateUse = 205,
OpenVPNErrorCodeTLSServerEKU = 207, OpenVPNErrorCodeTLSClientKeyRead = 206,
OpenVPNErrorCodeTLSServerHost = 208, OpenVPNErrorCodeTLSClientKeyUse = 207,
OpenVPNErrorCodeTLSHandshake = 210,
OpenVPNErrorCodeTLSServerCertificate = 211,
OpenVPNErrorCodeTLSServerEKU = 212,
OpenVPNErrorCodeTLSServerHost = 213,
OpenVPNErrorCodeDataPathOverflow = 301, OpenVPNErrorCodeDataPathOverflow = 301,
OpenVPNErrorCodeDataPathPeerIdMismatch = 302, OpenVPNErrorCodeDataPathPeerIdMismatch = 302,
OpenVPNErrorCodeDataPathCompression = 303 OpenVPNErrorCodeDataPathCompression = 303

View File

@ -50,7 +50,7 @@ static const char *const TLSBoxServerEKU = "TLS Web Server Authentication";
int TLSBoxVerifyPeer(int ok, X509_STORE_CTX *ctx) { int TLSBoxVerifyPeer(int ok, X509_STORE_CTX *ctx) {
if (!ok) { if (!ok) {
NSError *error = OpenVPNErrorWithCode(OpenVPNErrorCodeTLSCertificateAuthority); NSError *error = OpenVPNErrorWithCode(OpenVPNErrorCodeTLSCAPeerVerification);
[[NSNotificationCenter defaultCenter] postNotificationName:TLSBoxPeerVerificationErrorNotification [[NSNotificationCenter defaultCenter] postNotificationName:TLSBoxPeerVerificationErrorNotification
object:nil object:nil
userInfo:@{OpenVPNErrorKey: error}]; userInfo:@{OpenVPNErrorKey: error}];
@ -62,9 +62,9 @@ const NSInteger TLSBoxDefaultSecurityLevel = 0;
@interface TLSBox () @interface TLSBox ()
@property (nonatomic, strong) NSString *caPath; @property (nonatomic, strong) NSString *caPEM;
@property (nonatomic, strong) NSString *clientCertificatePath; @property (nonatomic, strong) NSString *clientCertificatePEM;
@property (nonatomic, strong) NSString *clientKeyPath; @property (nonatomic, strong) NSString *clientKeyPEM;
@property (nonatomic, assign) BOOL checksEKU; @property (nonatomic, assign) BOOL checksEKU;
@property (nonatomic, assign) BOOL checksSANHost; @property (nonatomic, assign) BOOL checksSANHost;
@property (nonatomic, strong) NSString *hostname; @property (nonatomic, strong) NSString *hostname;
@ -80,6 +80,10 @@ const NSInteger TLSBoxDefaultSecurityLevel = 0;
@end @end
static BIO *create_BIO_from_PEM(NSString *pem) {
return BIO_new_mem_buf([pem cStringUsingEncoding:NSASCIIStringEncoding], (int)[pem length]);
}
@implementation TLSBox @implementation TLSBox
+ (NSString *)md5ForCertificatePath:(NSString *)path error:(NSError * _Nullable __autoreleasing * _Nullable)error + (NSString *)md5ForCertificatePath:(NSString *)path error:(NSError * _Nullable __autoreleasing * _Nullable)error
@ -109,6 +113,33 @@ const NSInteger TLSBoxDefaultSecurityLevel = 0;
return hex; 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 + (NSString *)decryptedPrivateKeyFromPath:(NSString *)path passphrase:(NSString *)passphrase error:(NSError * _Nullable __autoreleasing *)error
{ {
BIO *bio; BIO *bio;
@ -169,17 +200,17 @@ const NSInteger TLSBoxDefaultSecurityLevel = 0;
return nil; return nil;
} }
- (instancetype)initWithCAPath:(NSString *)caPath - (instancetype)initWithCA:(nonnull NSString *)caPEM
clientCertificatePath:(NSString *)clientCertificatePath clientCertificate:(nullable NSString *)clientCertificatePEM
clientKeyPath:(NSString *)clientKeyPath clientKey:(nullable NSString *)clientKeyPEM
checksEKU:(BOOL)checksEKU checksEKU:(BOOL)checksEKU
checksSANHost:(BOOL)checksSANHost checksSANHost:(BOOL)checksSANHost
hostname:(nullable NSString *)hostname hostname:(nullable NSString *)hostname
{ {
if ((self = [super init])) { if ((self = [super init])) {
self.caPath = caPath; self.caPEM = caPEM;
self.clientCertificatePath = clientCertificatePath; self.clientCertificatePEM = clientCertificatePEM;
self.clientKeyPath = clientKeyPath; self.clientKeyPEM = clientKeyPEM;
self.checksEKU = checksEKU; self.checksEKU = checksEKU;
self.checksSANHost = checksSANHost; self.checksSANHost = checksSANHost;
self.bufferCipherText = allocate_safely(TLSBoxMaxBufferLength); self.bufferCipherText = allocate_safely(TLSBoxMaxBufferLength);
@ -214,31 +245,89 @@ const NSInteger TLSBoxDefaultSecurityLevel = 0;
SSL_CTX_set_security_level(self.ctx, (int)self.securityLevel); SSL_CTX_set_security_level(self.ctx, (int)self.securityLevel);
} }
if (!SSL_CTX_load_verify_locations(self.ctx, [self.caPath cStringUsingEncoding:NSASCIIStringEncoding], NULL)) { if (self.caPEM) {
ERR_print_errors_fp(stdout); BIO *bio = create_BIO_from_PEM(self.caPEM);
if (error) { if (!bio) {
*error = OpenVPNErrorWithCode(OpenVPNErrorCodeTLSCertificateAuthority);
}
return NO;
}
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) { if (error) {
*error = OpenVPNErrorWithCode(OpenVPNErrorCodeTLSClientCertificate); *error = OpenVPNErrorWithCode(OpenVPNErrorCodeTLSCARead);
} }
return NO; 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)) {
ERR_print_errors_fp(stdout);
if (error) {
*error = OpenVPNErrorWithCode(OpenVPNErrorCodeTLSCAUse);
}
X509_free(ca);
return NO;
}
X509_free(ca);
}
if (self.clientKeyPath) { if (self.clientCertificatePEM) {
if (!SSL_CTX_use_PrivateKey_file(self.ctx, [self.clientKeyPath cStringUsingEncoding:NSASCIIStringEncoding], SSL_FILETYPE_PEM)) { BIO *bio = create_BIO_from_PEM(self.clientCertificatePEM);
ERR_print_errors_fp(stdout); if (!bio) {
if (error) {
*error = OpenVPNErrorWithCode(OpenVPNErrorCodeTLSClientCertificateRead);
}
return NO;
}
X509 *cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
if (!cert) {
if (error) {
*error = OpenVPNErrorWithCode(OpenVPNErrorCodeTLSClientCertificateRead);
}
BIO_free(bio);
return NO;
}
BIO_free(bio);
if (!SSL_CTX_use_certificate(self.ctx, cert)) {
ERR_print_errors_fp(stdout);
if (error) {
*error = OpenVPNErrorWithCode(OpenVPNErrorCodeTLSClientCertificateUse);
}
X509_free(cert);
return NO;
}
X509_free(cert);
if (self.clientKeyPEM) {
BIO *bio = create_BIO_from_PEM(self.clientKeyPEM);
if (!bio) {
if (error) { if (error) {
*error = OpenVPNErrorWithCode(OpenVPNErrorCodeTLSClientKey); *error = OpenVPNErrorWithCode(OpenVPNErrorCodeTLSClientKeyRead);
} }
return NO; return NO;
} }
EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
if (!pkey) {
if (error) {
*error = OpenVPNErrorWithCode(OpenVPNErrorCodeTLSClientKeyRead);
}
EVP_PKEY_free(pkey);
return NO;
}
BIO_free(bio);
if (!SSL_CTX_use_PrivateKey(self.ctx, pkey)) {
ERR_print_errors_fp(stdout);
if (error) {
*error = OpenVPNErrorWithCode(OpenVPNErrorCodeTLSClientKeyUse);
}
EVP_PKEY_free(pkey);
return NO;
}
EVP_PKEY_free(pkey);
} }
} }

View File

@ -55,15 +55,16 @@ extern const NSInteger TLSBoxDefaultSecurityLevel;
@property (nonatomic, assign) NSInteger securityLevel; // TLSBoxDefaultSecurityLevel for default @property (nonatomic, assign) NSInteger securityLevel; // TLSBoxDefaultSecurityLevel for default
+ (nullable NSString *)md5ForCertificatePath:(NSString *)path error:(NSError **)error; + (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 *)decryptedPrivateKeyFromPath:(NSString *)path passphrase:(NSString *)passphrase error:(NSError **)error;
+ (nullable NSString *)decryptedPrivateKeyFromPEM:(NSString *)pem passphrase:(NSString *)passphrase error:(NSError **)error; + (nullable NSString *)decryptedPrivateKeyFromPEM:(NSString *)pem passphrase:(NSString *)passphrase error:(NSError **)error;
- (instancetype)initWithCAPath:(NSString *)caPath - (instancetype)initWithCA:(nonnull NSString *)caPEM
clientCertificatePath:(nullable NSString *)clientCertificatePath clientCertificate:(nullable NSString *)clientCertificatePEM
clientKeyPath:(nullable NSString *)clientKeyPath clientKey:(nullable NSString *)clientKeyPEM
checksEKU:(BOOL)checksEKU checksEKU:(BOOL)checksEKU
checksSANHost:(BOOL)checksSANHost checksSANHost:(BOOL)checksSANHost
hostname:(nullable NSString *)hostname; hostname:(nullable NSString *)hostname;
- (BOOL)startWithError:(NSError **)error; - (BOOL)startWithError:(NSError **)error;

View File

@ -241,7 +241,7 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider {
let session: OpenVPNSession let session: OpenVPNSession
do { do {
session = try OpenVPNSession(queue: tunnelQueue, configuration: cfg.sessionConfiguration, cachesURL: cachesURL) session = try OpenVPNSession(queue: tunnelQueue, configuration: cfg.sessionConfiguration)
refreshDataCount() refreshDataCount()
} catch let e { } catch let e {
completionHandler(e) completionHandler(e)
@ -903,7 +903,9 @@ extension OpenVPNTunnelProvider {
case .cryptoEncryption, .cryptoHMAC: case .cryptoEncryption, .cryptoHMAC:
return .encryptionData return .encryptionData
case .tlsCertificateAuthority, .tlsClientCertificate, .tlsClientKey: case .tlscaRead, .tlscaUse, .tlscaPeerVerification,
.tlsClientCertificateRead, .tlsClientCertificateUse,
.tlsClientKeyRead, .tlsClientKeyUse:
return .tlsInitialization return .tlsInitialization
case .tlsServerCertificate, .tlsServerEKU, .tlsServerHost: case .tlsServerCertificate, .tlsServerEKU, .tlsServerHost:

View File

@ -72,14 +72,6 @@ public class OpenVPNSession: Session {
case reconnect case reconnect
} }
private struct Caches {
static let ca = "ca.pem"
static let clientCertificate = "cert.pem"
static let clientKey = "key.pem"
}
// MARK: Configuration // MARK: Configuration
/// The session base configuration. /// The session base configuration.
@ -174,22 +166,6 @@ public class OpenVPNSession: Session {
private var authenticator: OpenVPN.Authenticator? private var authenticator: OpenVPN.Authenticator?
// MARK: Caching
private let cachesURL: URL
private var caURL: URL {
return cachesURL.appendingPathComponent(Caches.ca)
}
private var clientCertificateURL: URL {
return cachesURL.appendingPathComponent(Caches.clientCertificate)
}
private var clientKeyURL: URL {
return cachesURL.appendingPathComponent(Caches.clientKey)
}
// MARK: Init // MARK: Init
/** /**
@ -198,14 +174,13 @@ public class OpenVPNSession: Session {
- Parameter queue: The `DispatchQueue` where to run the session loop. - Parameter queue: The `DispatchQueue` where to run the session loop.
- Parameter configuration: The `Configuration` to use for this session. - Parameter configuration: The `Configuration` to use for this session.
*/ */
public init(queue: DispatchQueue, configuration: OpenVPN.Configuration, cachesURL: URL) throws { public init(queue: DispatchQueue, configuration: OpenVPN.Configuration) throws {
guard let ca = configuration.ca else { guard let _ = configuration.ca else {
throw ConfigurationError.missingConfiguration(option: "ca") throw ConfigurationError.missingConfiguration(option: "ca")
} }
self.queue = queue self.queue = queue
self.configuration = configuration self.configuration = configuration
self.cachesURL = cachesURL
withLocalOptions = true withLocalOptions = true
keys = [:] keys = [:]
@ -226,25 +201,10 @@ public class OpenVPNSession: Session {
} else { } else {
controlChannel = OpenVPN.ControlChannel() controlChannel = OpenVPN.ControlChannel()
} }
// cache PEMs locally (mandatory for OpenSSL)
let fm = FileManager.default
try ca.pem.write(to: caURL, atomically: true, encoding: .ascii)
if let container = configuration.clientCertificate {
try container.pem.write(to: clientCertificateURL, atomically: true, encoding: .ascii)
} else {
try? fm.removeItem(at: clientCertificateURL)
}
if let container = configuration.clientKey {
try container.pem.write(to: clientKeyURL, atomically: true, encoding: .ascii)
} else {
try? fm.removeItem(at: clientKeyURL)
}
} }
deinit { deinit {
cleanup() cleanup()
cleanupCache()
} }
// MARK: Session // MARK: Session
@ -355,13 +315,6 @@ public class OpenVPNSession: Session {
stopError = nil stopError = nil
} }
func cleanupCache() {
let fm = FileManager.default
for url in [caURL, clientCertificateURL, clientKeyURL] {
try? fm.removeItem(at: url)
}
}
// MARK: Loop // MARK: Loop
// Ruby: start // Ruby: start
@ -623,9 +576,13 @@ public class OpenVPNSession: Session {
private func hardResetPayload() -> Data? { private func hardResetPayload() -> Data? {
guard !(configuration.usesPIAPatches ?? false) else { guard !(configuration.usesPIAPatches ?? false) else {
guard let ca = configuration.ca else {
log.error("Configuration doesn't have a CA")
return nil
}
let caMD5: String let caMD5: String
do { do {
caMD5 = try TLSBox.md5(forCertificatePath: caURL.path) caMD5 = try TLSBox.md5(forCertificatePEM: ca.pem)
} catch { } catch {
log.error("CA MD5 could not be computed, skipping custom HARD_RESET") log.error("CA MD5 could not be computed, skipping custom HARD_RESET")
return nil return nil
@ -766,6 +723,11 @@ public class OpenVPNSession: Session {
return return
} }
guard let ca = configuration.ca else {
log.error("Configuration doesn't have a CA")
return
}
// start new TLS handshake // start new TLS handshake
if ((packet.code == .hardResetServerV2) && (negotiationKey.state == .hardReset)) || if ((packet.code == .hardResetServerV2) && (negotiationKey.state == .hardReset)) ||
((packet.code == .softResetV1) && (negotiationKey.state == .softReset)) { ((packet.code == .softResetV1) && (negotiationKey.state == .softReset)) {
@ -789,9 +751,9 @@ public class OpenVPNSession: Session {
log.debug("Start TLS handshake") log.debug("Start TLS handshake")
let tls = TLSBox( let tls = TLSBox(
caPath: caURL.path, ca: ca.pem,
clientCertificatePath: (configuration.clientCertificate != nil) ? clientCertificateURL.path : nil, clientCertificate: configuration.clientCertificate?.pem,
clientKeyPath: (configuration.clientKey != nil) ? clientKeyURL.path : nil, clientKey: configuration.clientKey?.pem,
checksEKU: configuration.checksEKU ?? false, checksEKU: configuration.checksEKU ?? false,
checksSANHost: configuration.checksSANHost ?? false, checksSANHost: configuration.checksSANHost ?? false,
hostname: configuration.sanHost hostname: configuration.sanHost
@ -1253,7 +1215,6 @@ public class OpenVPNSession: Session {
switch method { switch method {
case .shutdown: case .shutdown:
self?.doShutdown(error: error) self?.doShutdown(error: error)
self?.cleanupCache()
case .reconnect: case .reconnect:
self?.doReconnect(error: error) self?.doReconnect(error: error)

View File

@ -118,6 +118,11 @@ class EncryptionTests: XCTestCase {
let exp = "e2fccccaba712ccc68449b1c56427ac1" let exp = "e2fccccaba712ccc68449b1c56427ac1"
print(md5) print(md5)
XCTAssertEqual(md5, exp) 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() { func testPrivateKeyDecryption() {