Skip to content

Commit 23bd9ec

Browse files
authored
CBL-7654 : Fix MultipeerCertificateAuthenticator created with root certs fails to validate peer certs (Port) (#3478)
* Directly ported the fix from release/3.3 branch (5d13587) * The cause of the issue is in CBLTrustCheck that doesn't return an error when AnchorTrusted problem is found. * In addition, ported MultipeerReplicatorTest enhancement that uses different CN on each test from release/3.3 branch (3da8ee8)
1 parent 0578692 commit 23bd9ec

File tree

5 files changed

+63
-54
lines changed

5 files changed

+63
-54
lines changed

Objective-C/Internal/Replicator/CBLTrustCheck.mm

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,11 @@ - (NSString*) description {
8080
return [NSString stringWithFormat: @"%@[%@:%@]", self.class, (_host ?: @""), port];
8181
}
8282

83-
// Checks whether the reported problems with a SecTrust are OK.
84-
// Currently do not accept any problems.
83+
/**
84+
Validates whether the issues reported by SecTrust are acceptable if trust validation
85+
result is not kSecTrustResultProceed or kSecTrustResultUnspecified.
86+
Note: Used only when no pinned certificate or self-signed certificate restriction is set.
87+
*/
8588
- (BOOL) shouldAcceptProblems: (NSError**)outError {
8689
NSDictionary* resultDict = CFBridgingRelease(SecTrustCopyResult(_trust));
8790
NSArray* detailsArray = resultDict[@"TrustResultDetails"];
@@ -95,15 +98,21 @@ - (BOOL) shouldAcceptProblems: (NSError**)outError {
9598
[details[problem] isEqual: @[@(CSSMERR_APPLETP_HOSTNAME_MISMATCH)]])
9699
#endif
97100
) {
101+
if (!_host) {
102+
continue; // When no host specified, accept and check next problem
103+
}
98104
MYReturnError(outError, NSURLErrorServerCertificateUntrusted, NSURLErrorDomain,
99105
@"Server TLS certificate has a hostname mismatch.");
100-
} else if ([problem isEqualToString: @"AnchorTrusted"]) {
101-
MYReturnError(outError, NSURLErrorServerCertificateHasUnknownRoot, NSURLErrorDomain,
102-
@"Server TLS certificate has an unknown root or is self-signed.");
106+
return NO;
103107
} else {
104-
// Any other problem:
105-
MYReturnError(outError, NSURLErrorServerCertificateUntrusted, NSURLErrorDomain,
106-
@"Server TLS certificate is untrusted (problem = %@)", problem);
108+
if ([problem isEqualToString: @"AnchorTrusted"]) {
109+
MYReturnError(outError, NSURLErrorServerCertificateHasUnknownRoot, NSURLErrorDomain,
110+
@"Server TLS certificate has an unknown root or is self-signed.");
111+
} else {
112+
// Any other problem:
113+
MYReturnError(outError, NSURLErrorServerCertificateUntrusted, NSURLErrorDomain,
114+
@"Server TLS certificate is untrusted (problem = %@)", problem);
115+
}
107116
return NO;
108117
}
109118
}
@@ -174,38 +183,26 @@ - (nullable NSURLCredential*) checkTrust: (NSError**)outError {
174183

175184
// If using pinned cert, accept cert if it matches the pinned cert:
176185
if (_pinnedCertData) {
177-
CFIndex count = SecTrustGetCertificateCount(_trust);
178-
#if __IPHONE_OS_VERSION_MAX_REQUIRED >= 150000
179-
if (@available(iOS 15.0, *)) {
180-
NSData* certData = nil;
181-
for (CFIndex i = 0; i < count; i++) {
182-
CFArrayRef certs = SecTrustCopyCertificateChain(_trust);
183-
SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, i);
184-
certData = CFBridgingRelease(SecCertificateCopyData(cert));
186+
CFArrayRef certs = SecTrustCopyCertificateChain(_trust);
187+
CFIndex count = CFArrayGetCount(certs);
188+
for (CFIndex i = 0; i < count; i++) {
189+
SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, i);
190+
NSData* certData = CFBridgingRelease(SecCertificateCopyData(cert));
191+
if ([_pinnedCertData isEqual:certData]) {
185192
CFRelease(certs);
186-
if ([_pinnedCertData isEqual: certData]) {
187-
[self forceTrusted];
188-
return credential;
189-
}
190-
}
191-
} else
192-
#endif
193-
{
194-
for (CFIndex i = 0; i < count; i++) {
195-
#pragma clang diagnostic push
196-
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
197-
SecCertificateRef cert = SecTrustGetCertificateAtIndex(_trust, i);
198-
#pragma clang diagnostic pop
199-
if ([_pinnedCertData isEqual: CFBridgingRelease(SecCertificateCopyData(cert))]) {
200-
[self forceTrusted];
201-
return credential;
202-
}
193+
[self forceTrusted];
194+
return credential;
203195
}
204196
}
205-
MYReturnError(outError, NSURLErrorServerCertificateHasUnknownRoot, NSURLErrorDomain,
197+
198+
CFRelease(certs);
199+
MYReturnError(outError,
200+
NSURLErrorServerCertificateHasUnknownRoot,
201+
NSURLErrorDomain,
206202
@"Server TLS Certificate does not match the pinned cert.");
207203
return nil;
208204
}
205+
209206
#ifdef COUCHBASE_ENTERPRISE
210207
else if (_acceptOnlySelfSignedCert) {
211208
NSError* err;

Objective-C/Internal/Replicator/CBLWebSocket.mm

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -973,7 +973,10 @@ - (SecTrustRef) getTrustFromReadStream {
973973
kCFStreamPropertySSLPeerTrust);
974974
}
975975

976-
// Notify the received cert and verify the cert if using custom verification logic
976+
// Handles SSL certificate validation for the peer.
977+
// Performs custom trust evaluation if enabled, notifies certificate to
978+
// CBLWebSocketContext's callback and C4Socket and closes the connection on failure.
979+
// Returns YES if the certificate is accepted.
977980
- (BOOL) checkSSLCert {
978981
_shouldCheckSSLCert = NO;
979982

@@ -982,10 +985,16 @@ - (BOOL) checkSSLCert {
982985

983986
SecCertificateRef cert = [self certificateFromTrust: trust];
984987
[self notifyServerCertificateReceived: cert];
985-
988+
986989
NSError* error = nil;
990+
991+
// Validate trust only when using custom certificate validation
992+
// (kCFStreamSSLValidatesCertificateChain is disabled).
993+
// If system validation is enabled, the certificates have already been verified,
994+
// and any validation failure will trigger NSStreamEventErrorOccurred without
995+
// calling this method.
987996
if ([self usesCustomTLSCertValidation]) {
988-
if (![self validateTrust:trust error:&error]) {
997+
if (![self validateTrust: trust error: &error]) {
989998
[self closeWithError: error];
990999
return NO;
9911000
}
@@ -1018,18 +1027,17 @@ - (BOOL) validateTrust: (SecTrustRef)trust error: (NSError**)error {
10181027
NSURL* url = _logic.URL;
10191028
CBLTrustCheck* check = [[CBLTrustCheck alloc] initWithTrust: trust host: url.host port: url.port.shortValue];
10201029

1021-
NSURLCredential* credentials = nil;
10221030
Value pinnedCert = _options[kC4ReplicatorOptionPinnedServerCert];
10231031
if (pinnedCert) {
10241032
check.pinnedCertData = slice(pinnedCert.asData()).copiedNSData();
1025-
credentials = [check checkTrust: error];
10261033
}
10271034
#ifdef COUCHBASE_ENTERPRISE
10281035
else if (_options[kC4ReplicatorOptionOnlySelfSignedServerCert].asBool()) {
10291036
check.acceptOnlySelfSignedCert = YES;
1030-
credentials = [check checkTrust: error];
10311037
}
10321038
#endif
1039+
1040+
NSURLCredential* credentials = [check checkTrust: error];
10331041
if (!credentials) {
10341042
CBLWarn(WebSocket, @"%@: TLS handshake failed: certificate verification error: %@", self, (*error).localizedDescription);
10351043
return false;

Objective-C/Tests/MultipeerReplicatorTest.m

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,15 +129,18 @@ - (CBLTLSIdentity*) issuer {
129129

130130
- (CBLTLSIdentity*) createIdentity {
131131
Assert(_identityCount++ <= kTestMaxIdentity);
132-
133132
NSError* error;
134-
NSString* name = [self identityNameForNumber: _identityCount];
135-
NSDictionary* attrs = @{ kCBLCertAttrCommonName: name };
133+
NSString* label = [self identityNameForNumber: _identityCount];
134+
135+
uint64_t timestamp = (uint64_t)([[NSDate date] timeIntervalSince1970] * 1000);
136+
NSString* cn = [NSString stringWithFormat: @"%@-%llu", label, timestamp];
137+
NSDictionary* attrs = @{ kCBLCertAttrCommonName: cn };
138+
136139
CBLTLSIdentity* identity = [CBLTLSIdentity createIdentityForKeyUsages: kTestKeyUsages
137140
attributes: attrs
138141
expiration: nil
139142
issuer: [self issuer]
140-
label: name
143+
label: label
141144
error: &error];
142145

143146
AssertNotNil(identity);
@@ -406,6 +409,7 @@ - (void) testConfiguration {
406409
4. Check that the configuration cannot be created as the collections are empty.
407410
*/
408411
- (void) testConfigurationValidation {
412+
// When exception is thrown, the object will not get released:
409413
self.disableObjectLeakCheck = YES;
410414

411415
CBLTLSIdentity* identity = [self createIdentity];
@@ -1085,8 +1089,6 @@ - (void) runConflictResolverTestWithResolver: (nullable MultipeerConflictResolve
10851089
[self waitForReplicatorStatus: repl1 peerID: repl2.peerID activityLevel: kCBLReplicatorIdle];
10861090
[self waitForReplicatorStatus: repl2 peerID: repl1.peerID activityLevel: kCBLReplicatorIdle];
10871091

1088-
NSLog(@"-----------------------------------------");
1089-
10901092
// Stops:
10911093
[self stopMultipeerReplicator: repl1];
10921094
[self stopMultipeerReplicator: repl2];
@@ -1176,7 +1178,6 @@ - (void) runConflictResolverTestWithResolver: (nullable MultipeerConflictResolve
11761178
18. Check the doc1 in both peers and verify that the its body is {"greeting": "howdy"}.
11771179
*/
11781180
- (void) testDefaultConflictResolver {
1179-
CBLLogSinks.console = [[CBLConsoleLogSink alloc] initWithLevel: kCBLLogLevelVerbose];
11801181
[self runConflictResolverTestWithResolver: nil verifyBlock: ^BOOL (CBLDocument* resolvedDoc) {
11811182
return [[resolvedDoc stringForKey: @"greeting"] isEqualToString: @"howdy"];
11821183
}];

Objective-C/Tests/TrustCheckTest.m

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ - (void) testTrustCHeckWithRootCerts {
228228
SecCertificateRef secCert3 = [self createSecCertFromPEM: cert3];
229229

230230
SecTrustRef trust;
231-
SecPolicyRef policy = SecPolicyCreateSSL(true, (__bridge CFStringRef)_host);
231+
SecPolicyRef policy = SecPolicyCreateBasicX509();
232232

233233
// Validate leaf cert:
234234

@@ -239,8 +239,7 @@ - (void) testTrustCHeckWithRootCerts {
239239
NSError* error;
240240
NSURLCredential* credential;
241241

242-
243-
CBLTrustCheck* trustCheck = [[CBLTrustCheck alloc] initWithTrust: trust host: _host port: 443];
242+
CBLTrustCheck* trustCheck = [[CBLTrustCheck alloc] initWithTrust: trust];
244243
credential = [trustCheck checkTrust: &error];
245244
AssertNil(credential);
246245

@@ -261,7 +260,7 @@ - (void) testTrustCHeckWithRootCerts {
261260
status = SecTrustCreateWithCertificates((__bridge CFTypeRef)certs, policy, &trust);
262261
AssertEqual(status, errSecSuccess);
263262

264-
trustCheck = [[CBLTrustCheck alloc] initWithTrust: trust host: _host port: 443];
263+
trustCheck = [[CBLTrustCheck alloc] initWithTrust: trust];
265264
credential = [trustCheck checkTrust: &error];
266265
AssertNil(credential);
267266

Swift/Tests/MultipeerReplicatorTest.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,14 +121,18 @@ class MultipeerReplicatorTest: CBLTestCase {
121121
assert(identityCount <= kTestMaxIdentity)
122122
identityCount += 1
123123

124-
let name = identityNameForNumber(identityCount)
125-
let attrs = [certAttrCommonName: name]
124+
let label = identityNameForNumber(identityCount)
125+
126+
let timestamp = UInt64(Date().timeIntervalSince1970 * 1000)
127+
let cn = "\(label)-\(timestamp)"
128+
let attrs = [certAttrCommonName: cn]
129+
126130
return try TLSIdentity.createIdentity(
127131
for: kTestKeyUsages,
128132
attributes: attrs,
129133
expiration: nil,
130134
issuer: self.issuer,
131-
label: name)
135+
label: label)
132136
}
133137

134138
typealias CollectionConfigBlock = (inout MultipeerCollectionConfiguration) -> Void

0 commit comments

Comments
 (0)