Skip to content
This repository has been archived by the owner on Sep 29, 2024. It is now read-only.

Commit

Permalink
SAN host check (#168)
Browse files Browse the repository at this point in the history
* Check if host is present in certificates SAN list

* Save .tlsServerHost error as .tlsServerVerification into last error

Co-authored-by: Davide De Rosa <keeshux@gmail.com>
  • Loading branch information
jaroslavas and keeshux authored May 8, 2020
1 parent 56eda27 commit 1ceeb8d
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Added

- Support for SAN hostname in certificates (jaroslavas). [#168](https://github.com/passepartoutvpn/tunnelkit/pull/168)

### Fixed

- IPv6 traffic broken on Mojave. [#146](https://github.com/passepartoutvpn/tunnelkit/issues/146), [#169](https://github.com/passepartoutvpn/tunnelkit/pull/169)
Expand Down
1 change: 1 addition & 0 deletions TunnelKit/Sources/Core/Errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ typedef NS_ENUM(NSInteger, TunnelKitErrorCode) {
TunnelKitErrorCodeTLSClientKey = 205,
TunnelKitErrorCodeTLSServerCertificate = 206,
TunnelKitErrorCodeTLSServerEKU = 207,
TunnelKitErrorCodeTLSServerHost = 208,
TunnelKitErrorCodeDataPathOverflow = 301,
TunnelKitErrorCodeDataPathPeerIdMismatch = 302,
TunnelKitErrorCodeDataPathCompression = 303,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,11 @@ extension OpenVPNTunnelProvider {
static let renegotiatesAfter = "RenegotiatesAfter"

static let checksEKU = "ChecksEKU"


static let checksSANHost = "checksSANHost"

static let sanHost = "sanHost"

static let randomizeEndpoint = "RandomizeEndpoint"

static let usesPIAPatches = "UsesPIAPatches"
Expand Down Expand Up @@ -510,6 +514,12 @@ private extension OpenVPN.Configuration {
if let checksEKU = providerConfiguration[S.checksEKU] as? Bool {
builder.checksEKU = checksEKU
}
if let checksSANHost = providerConfiguration[S.checksSANHost] as? Bool {
builder.checksSANHost = checksSANHost
}
if let sanHost = providerConfiguration[S.sanHost] as? String {
builder.sanHost = sanHost
}
if let randomizeEndpoint = providerConfiguration[S.randomizeEndpoint] as? Bool {
builder.randomizeEndpoint = randomizeEndpoint
}
Expand Down Expand Up @@ -590,6 +600,12 @@ private extension OpenVPN.Configuration {
if let checksEKU = checksEKU {
dict[S.checksEKU] = checksEKU
}
if let checksSANHost = checksSANHost {
dict[S.checksSANHost] = checksSANHost
}
if let sanHost = sanHost {
dict[S.sanHost] = sanHost
}
if let randomizeEndpoint = randomizeEndpoint {
dict[S.randomizeEndpoint] = randomizeEndpoint
}
Expand Down Expand Up @@ -667,6 +683,11 @@ private extension OpenVPN.Configuration {
} else {
log.info("\tServer EKU verification: disabled")
}
if checksSANHost ?? false {
log.info("\tHost SAN verification: enabled (\(sanHost ?? "-"))")
} else {
log.info("\tHost SAN verification: disabled")
}
if randomizeEndpoint ?? false {
log.info("\tRandomize endpoint: true")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -842,7 +842,7 @@ extension OpenVPNTunnelProvider {
case .tlsCertificateAuthority, .tlsClientCertificate, .tlsClientKey:
return .tlsInitialization

case .tlsServerCertificate, .tlsServerEKU:
case .tlsServerCertificate, .tlsServerEKU, .tlsServerHost:
return .tlsServerVerification

case .tlsHandshake:
Expand Down
16 changes: 16 additions & 0 deletions TunnelKit/Sources/Protocols/OpenVPN/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,12 @@ extension OpenVPN {
/// If true, checks EKU of server certificate.
public var checksEKU: Bool?

/// If true, checks if hostname (sanHost) is present in certificates SAN.
public var checksSANHost: Bool?

/// The server hostname used for checking certificate SAN.
public var sanHost: String?

/// Picks endpoint from `remotes` randomly.
public var randomizeEndpoint: Bool?

Expand Down Expand Up @@ -300,6 +306,8 @@ extension OpenVPN {
hostname: hostname,
endpointProtocols: endpointProtocols,
checksEKU: checksEKU,
checksSANHost: checksSANHost,
sanHost: sanHost,
randomizeEndpoint: randomizeEndpoint,
usesPIAPatches: usesPIAPatches,
authToken: authToken,
Expand Down Expand Up @@ -382,6 +390,12 @@ extension OpenVPN {
/// - Seealso: `ConfigurationBuilder.checksEKU`
public let checksEKU: Bool?

/// - Seealso: `ConfigurationBuilder.checksSANHost`
public let checksSANHost: Bool?

/// - Seealso: `ConfigurationBuilder.sanHost`
public let sanHost: String?

/// - Seealso: `ConfigurationBuilder.randomizeEndpoint`
public let randomizeEndpoint: Bool?

Expand Down Expand Up @@ -466,6 +480,8 @@ extension OpenVPN.Configuration {
builder.hostname = hostname
builder.endpointProtocols = endpointProtocols
builder.checksEKU = checksEKU
builder.checksSANHost = checksSANHost
builder.sanHost = sanHost
builder.randomizeEndpoint = randomizeEndpoint
builder.usesPIAPatches = usesPIAPatches
builder.authToken = authToken
Expand Down
4 changes: 3 additions & 1 deletion TunnelKit/Sources/Protocols/OpenVPN/OpenVPNSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -787,7 +787,9 @@ public class OpenVPNSession: Session {
caPath: caURL.path,
clientCertificatePath: (configuration.clientCertificate != nil) ? clientCertificateURL.path : nil,
clientKeyPath: (configuration.clientKey != nil) ? clientKeyURL.path : nil,
checksEKU: configuration.checksEKU ?? false
checksEKU: configuration.checksEKU ?? false,
checksSANHost: configuration.checksSANHost ?? false,
hostname: configuration.sanHost
)
if let tlsSecurityLevel = configuration.tlsSecurityLevel {
tls.securityLevel = tlsSecurityLevel
Expand Down
4 changes: 3 additions & 1 deletion TunnelKit/Sources/Protocols/OpenVPN/TLSBox.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ extern const NSInteger TLSBoxDefaultSecurityLevel;
- (instancetype)initWithCAPath:(NSString *)caPath
clientCertificatePath:(nullable NSString *)clientCertificatePath
clientKeyPath:(nullable NSString *)clientKeyPath
checksEKU:(BOOL)checksEKU;
checksEKU:(BOOL)checksEKU
checksSANHost:(BOOL)checksSANHost
hostname:(nullable NSString *)hostname;

- (BOOL)startWithError:(NSError **)error;

Expand Down
79 changes: 79 additions & 0 deletions TunnelKit/Sources/Protocols/OpenVPN/TLSBox.m
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ @interface TLSBox ()
@property (nonatomic, strong) NSString *clientCertificatePath;
@property (nonatomic, strong) NSString *clientKeyPath;
@property (nonatomic, assign) BOOL checksEKU;
@property (nonatomic, assign) BOOL checksSANHost;
@property (nonatomic, strong) NSString *hostname;
@property (nonatomic, assign) BOOL isConnected;

@property (nonatomic, unsafe_unretained) SSL_CTX *ctx;
Expand Down Expand Up @@ -174,14 +176,18 @@ - (instancetype)initWithCAPath:(NSString *)caPath
clientCertificatePath:(NSString *)clientCertificatePath
clientKeyPath:(NSString *)clientKeyPath
checksEKU:(BOOL)checksEKU
checksSANHost:(BOOL)checksSANHost
hostname:(nullable NSString *)hostname
{
if ((self = [super init])) {
self.caPath = caPath;
self.clientCertificatePath = clientCertificatePath;
self.clientKeyPath = clientKeyPath;
self.checksEKU = checksEKU;
self.checksSANHost = checksSANHost;
self.bufferCipherText = allocate_safely(TLSBoxMaxBufferLength);
self.securityLevel = TLSBoxDefaultSecurityLevel;
self.hostname = hostname;
}
return self;
}
Expand Down Expand Up @@ -275,6 +281,13 @@ - (NSData *)pullCipherTextWithError:(NSError *__autoreleasing *)error
}
return nil;
}

if (self.checksSANHost && ![self verifySANHostWithSSL:self.ssl]) {
if (error) {
*error = TunnelKitErrorWithCode(TunnelKitErrorCodeTLSServerHost);
}
return nil;
}
}
if (ret > 0) {
return [NSData dataWithBytes:self.bufferCipherText length:ret];
Expand Down Expand Up @@ -397,4 +410,70 @@ - (BOOL)verifyEKUWithSSL:(SSL *)ssl
return isValid;
}

#pragma mark SAN

- (BOOL)verifySANHostWithSSL:(SSL *)ssl {
X509 *cert = SSL_get_peer_certificate(self.ssl);
if (!cert) {
return NO;
}

GENERAL_NAMES* names = NULL;
unsigned char* utf8 = NULL;
names = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0 );
if(!names) {
X509_free(cert);
return NO;
}

int i = 0, count = sk_GENERAL_NAME_num(names);
if(!count) {
X509_free(cert);
GENERAL_NAMES_free(names);
return NO;
}
BOOL isValid = NO;

for( i = 0; i < count; ++i ) {
GENERAL_NAME* entry = sk_GENERAL_NAME_value(names, i);
if(!entry) {
continue;
}
if(GEN_DNS != entry->type) {
continue;
}

int len1 = 0, len2 = -1;
len1 = ASN1_STRING_to_UTF8(&utf8, entry->d.dNSName);
if(!utf8) {
continue;
}
len2 = (int)strlen((const char*)utf8);

if(len1 != len2) {
OPENSSL_free(utf8);
utf8 = NULL;
continue;
}

if(utf8 && len1 && len2 && (len1 == len2) && strcmp((const char *)utf8, self.hostname.UTF8String) == 0) {
isValid = YES;
break;
}

OPENSSL_free(utf8);
utf8 = NULL;
}

X509_free(cert);

if(names) {
GENERAL_NAMES_free(names);
}
if(utf8) {
OPENSSL_free(utf8);
}
return isValid;
}

@end

0 comments on commit 1ceeb8d

Please sign in to comment.