Objective-C: eveluate certificate signed by our own PKI (root CA) when connecting TLS TCP

* resolved *

My problem is related to the following question:
Objective-C: How to check SecCertificateRef with a public key of a subscriber?

We have our own PKI and our own rootCA, which we trust. With this rootCA, we sign certificates that are delivered to personal servers. Now I want to connect to the iOS application and check if the certificate delivered from the server is signed by our CA.

My application should be able to connect to n servers with these certificates (maybe using a service with a zero config) using the TCP connection established by GCDAsyncSocket . I have a public part of the certificate in my application that I would like to add to my CertChain, so the application will trust them when it connects.

I tried a lot, but I still can not pass SecTrustEvaluate(trust, &result); with a valid result. (I want to use this in a productive , so please don't tell me anything about disabling verification)

My certificates:
in application: rootCA, oldServerCA (cer)
on the server (through trust): homeServer, oldServer

My certificate chain:
rootCA signed homeServer
oldServerCA signed oldServer

My code parts:
added updates

 - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port; { // Configure SSL/TLS settings NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:3]; // Allow self-signed certificates [settings setObject:[NSNumber numberWithBool:YES] forKey:GCDAsyncSocketManuallyEvaluateTrust]; [sock startTLS:settings]; // get the certificates as data for further operations NSString *certFilePath1 = [[NSBundle mainBundle] pathForResource:@"rootCA" ofType:@"cer"]; // also tried it with 'der', same result NSData *certData1 = [NSData dataWithContentsOfFile:certFilePath1]; NSString *certFilePath2 = [[NSBundle mainBundle] pathForResource:@"oldServerCA" ofType:@"cer"]; NSData *certData2 = [NSData dataWithContentsOfFile:certFilePath2]; // if data exists, use it if(certData1 && certData2) { SecCertificateRef cert1; cert1 = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) certData1); SecCertificateRef cert2; cert2 = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) certData2); // only working for "cer" NSString *name = [NSString stringWithUTF8String:CFStringGetCStringPtr(SecCertificateCopySubjectSummary(cert1), kCFStringEncodingUTF8)]; // maybe I understood the usage of "name" in "kSecAttrApplicationTag" wrong? OSStatus status = SecItemAdd((__bridge CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys: (__bridge id)(kSecClassKey), kSecClass, (__bridge id)kSecAttrKeyTypeRSA, kSecAttrKeyType, (__bridge id)kSecAttrKeyClassPublic, kSecAttrKeyClass, kCFBooleanTrue, kSecAttrIsPermanent, [name dataUsingEncoding:NSUTF8StringEncoding], kSecAttrApplicationTag, certData1, kSecValueData, kCFBooleanTrue, kSecReturnPersistentRef, nil], NULL); //don't need public key ref // Setting "cer" is successfully and delivers "noErr" in first run, then "errKCDuplicateItem" NSLog(@"evaluate with status %d", (int)status); NSString *name2 = [NSString stringWithUTF8String:CFStringGetCStringPtr(SecCertificateCopySubjectSummary(cert2), kCFStringEncodingUTF8)]; OSStatus status2 = SecItemAdd((__bridge CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys: (__bridge id)(kSecClassKey), kSecClass, (__bridge id)kSecAttrKeyTypeRSA, kSecAttrKeyType, (__bridge id)kSecAttrKeyClassPublic, kSecAttrKeyClass, kCFBooleanTrue, kSecAttrIsPermanent, [name2 dataUsingEncoding:NSUTF8StringEncoding], kSecAttrApplicationTag, certData2, kSecValueData, kCFBooleanTrue, kSecReturnPersistentRef, nil], NULL); //don't need public key ref NSLog(@"evaluate with status %d", (int)status2); // log here -> certificates were loaded. Fine // create references of each to proof them seperatly const void *ref[] = {cert1}; CFArrayRef aryRef = CFArrayCreate(NULL, ref, 1, NULL); const void *ref2[] = {cert2}; CFArrayRef aryRef2 = CFArrayCreate(NULL, ref2, 1, NULL); // need this way to get sock.sslContext, otherways it NULL (see implementation of GCDAsyncSocket) [sock performBlock:^{ SSLContextRef sslContext = sock.sslContext; OSStatus status = SSLSetCertificate(sslContext, aryRef); // the status is everywhere always -909 -> badReqErr /*bad parameter or invalid state for operation*/ if(status == noErr) NSLog(@"successfully set ssl certificates"); else NSLog(@"setting ssl certificates failed"); status = SSLSetCertificate(sock.sslContext, aryRef2); if(status == noErr) NSLog(@"successfully set ssl certificates"); else NSLog(@"setting ssl certificates failed"); status = SSLSetEncryptionCertificate(sock.sslContext, aryRef); if(status == noErr) NSLog(@"successfully set ssl certificates"); else NSLog(@"setting ssl certificates failed"); }]; } @synchronized( self ) { if( isConnected == NO ) { if(gcdAsyncSocket && [gcdAsyncSocket isConnected]) { isConnected = YES; [gcdAsyncSocket readDataWithTimeout:READ_TIMEOUT tag:0]; [NSThread detachNewThreadSelector:@selector(readDataToData:withTimeout:tag:) toTarget:gcdAsyncSocket withObject:nil]; [gcdAsyncSocket readDataToData:[GCDAsyncSocket LFData] withTimeout:READ_TIMEOUT tag:0]; [del onConnect]; } } } } 

ok ... if it doesn't work here then check manually ...

 - (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler { // https://code.csdn.net/OS_Mirror/CocoaAsyncSocket/file_diff/a4b9c4981b3c022ca89d0cdaadecc70b825ad4f1...5d58af30d2d8a3e0f7219487e72f1b4b2c3b4894/GCD/Xcode/SimpleHTTPClient/Desktop/SimpleHTTPClient/SimpleHTTPClientAppDelegate.m dispatch_queue_t bgQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(bgQueue, ^{ // This is where you would (eventually) invoke SecTrustEvaluate. // Presumably, if you're using manual trust evaluation, you're likely doing extra stuff here. // For example, allowing a specific self-signed certificate that is known to the app. NSString *certFilePath1 = [[NSBundle mainBundle] pathForResource:@"rootCA" ofType:@"cer"]; NSData *certData1 = [NSData dataWithContentsOfFile:certFilePath1]; NSString *certFilePath2 = [[NSBundle mainBundle] pathForResource:@"oldServerCA" ofType:@"cer"]; NSData *certData2 = [NSData dataWithContentsOfFile:certFilePath2]; if(certData1 && certData2) { CFArrayRef arrayRefTrust = SecTrustCopyProperties(trust); SecTrustResultType result = kSecTrustResultUnspecified; // usualy should work already here OSStatus status = SecTrustEvaluate(trust, &result); NSLog(@"evaluate with result %d and status %d", result, (int)status); NSLog(@"trust properties: %@", arrayRefTrust); /* log: evaluate with result 5 and status 0 trust properties: ( { type = error; value = "Root certificate is not trusted."; // expected, when top part was not working } */ SecCertificateRef cert1; cert1 = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) certData1); SecCertificateRef cert2; cert2 = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) certData2); const void *ref[] = {cert1}; CFIndex count = SecTrustGetCertificateCount(trust); // CFMutableArrayRef aryRef = CFArrayCreateMutable(NULL, count + 1, NULL); // CFArrayAppendValue(aryRef, ref); CFArrayCreate(NULL, ref, 2, NULL); // # # # # // so check one by one... BOOL isMatching = NO; for (int i = 0; i < count; i++) { SecCertificateRef certRef = SecTrustGetCertificateAtIndex(trust, i); NSString *name = [NSString stringWithUTF8String:CFStringGetCStringPtr(SecCertificateCopySubjectSummary(certRef), kCFStringEncodingUTF8)]; // only working for "cer" NSLog(@"remote cert at index %d is '%@'", i, name); /* first is 'homeserver', second is 'oldServer' */ // const void *ref[] = {certRef, cert1, cert2}; // CFArrayRef aryCheck = CFArrayCreate(NULL, ref, 3, NULL); // check against the new cert (rootCA) const void *ref[] = {certRef, cert1}; CFArrayRef aryCheck = CFArrayCreate(NULL, ref, 2, NULL); SecTrustRef trustManual; OSStatus certStatus = SecTrustCreateWithCertificates(aryCheck, SecPolicyCreateBasicX509(), &trustManual); // certStatus always noErr NSLog(@"certStatus: %d", (int)certStatus); SecTrustResultType result; OSStatus status = SecTrustEvaluate(trustManual, &result); CFArrayRef arrayRef = SecTrustCopyProperties(trustManual); NSLog(@"evaluate with result %d and status %d", result, (int)status); NSLog(@"trust properties: %@", arrayRef); /* log: evaluate with result 5 and status 0 trust properties: ( { type = error; value = "Root certificate is not trusted."; } */ // always else-part because result is "kSecTrustResultRecoverableTrustFailure" if (status == noErr && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified)) { isMatching = YES; NSLog(@"certificates matches"); } else { NSLog(@"certificates differs"); } } if (isMatching || (status == noErr && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified))) { completionHandler(YES); } else { completionHandler(NO); } } completionHandler(NO); }); } 

UPDATE 1

deleted

 [settings setObject:[NSNumber numberWithBool:YES] forKey:GCDAsyncSocketManuallyEvaluateTrust]; 

using now

 SecCertificateRef cert1, cert2; // init certs, see top part // according to @SeanBaker "Certs[0] would be nil (you don't want to do client auth), and certs[1...] would be the root certificates you want to trust in establishing the connection" const void *certs[] = {NULL, cert1, cert2}; // const void *certs[] = {nil, cert1, cert2}; CFArrayRef aryCerts = CFArrayCreate(NULL, certs, 3, NULL); [settings setObject:(__bridge NSArray*)aryCerts forKey:(NSString *)kCFStreamSSLCertificates]; 

but getting OSStatus -50 ( /*error in user parameter list*/ ) in

 // 2. kCFStreamSSLCertificates value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLCertificates]; if ([value isKindOfClass:[NSArray class]]) { CFArrayRef certs = (__bridge CFArrayRef)value; status = SSLSetCertificate(sslContext, certs); ... 

it looks like I'm using it incorrectly, but I see no error: / (not often using the main framework)

If you need more information, just ask. Every hint can save lives :)

+5
source share
3 answers

I solved the problem by setting the certificates as anchorCertificates of trust in manual verification - (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler , but thanks for your tips and effort: ) will give you some reward for this.

  NSString *certFilePath1 = [[NSBundle mainBundle] pathForResource:@"rootCA" ofType:@"cer"]; NSData *certData1 = [NSData dataWithContentsOfFile:certFilePath1]; NSString *certFilePath2 = [[NSBundle mainBundle] pathForResource:@"oldServerCA" ofType:@"cer"]; NSData *certData2 = [NSData dataWithContentsOfFile:certFilePath2]; OSStatus status = -1; SecTrustResultType result = kSecTrustResultDeny; if(certData1 && certData2) { SecCertificateRef cert1; cert1 = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) certData1); SecCertificateRef cert2; cert2 = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) certData2); const void *ref[] = {cert1, cert2}; CFArrayRef ary = CFArrayCreate(NULL, ref, 2, NULL); SecTrustSetAnchorCertificates(trust, ary); status = SecTrustEvaluate(trust, &result); } else { NSLog(@"local certificates could not be loaded"); completionHandler(NO); } if ((status == noErr && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified))) { completionHandler(YES); } else { CFArrayRef arrayRefTrust = SecTrustCopyProperties(trust); NSLog(@"error in connection occured\n%@", arrayRefTrust); completionHandler(NO); } 
+3
source

I myself use a special certificate to verify several servers used by our messaging application in development mode.

If you have access to the p12 file (the included private key and therefore the signed identifier), you can check the server certificate with kCFStreamSSLCertificates

Otherwise (in the case of only the public key) you have the opportunity to check through the same name kCFStreamSSLPeerName.

In your piece of code, one thing you are doing wrong is how you deliver certificates to the GCDAsyncSocket module. and therefore find the error you were talking about.

The correct way is as follows:

  NSArray *myCerts = [[NSArray alloc] initWithObjects:(__bridge id)identity1, (__bridge id)myReturnedCertificate1, nil]; [settings setObject:myCerts forKey:(NSString *)kCFStreamSSLCertificates]; 

According to the documentation ID, Apple is required when using kCFStreamSSLCertificates:

You must specify a secIdentityRef object in certRefs [0] that identifies the certificate of the sheet and its corresponding private key. Specifying a root certificate is optional;


Detailed information:

The following are the steps to follow if you are using custom CA certificate certificates. Please note: the example is based on GCDAsyncSocket

  • Store your public part certificate in the application resource pack.
  • Read the above certificate and add the certificate to the keychain
  • Implement delegate function

(void) socket: (GCDAsyncSocket *) sock didConnectToHost: (NSString *) host port: (uint16_t) port;

Inside this function, specify your certificate in GCDAsyncSocket

  NSArray *myCerts = [[NSArray alloc] initWithObjects:(__bridge id)identity1, (__bridge id)myReturnedCertificate1, nil]; [settings setObject:myCerts forKey:(NSString *)kCFStreamSSLCertificates]; 

Use YES (not recommended) or NO below, based on the fact that you want to check trust manually?

  [settings setObject:[NSNumber numberWithBool:YES] forKey:GCDAsyncSocketManuallyEvaluateTrust]; 
  1. If you decide to manually verify trust, override the following delegation method.

(void) socket: (GCDAsyncSocket *) sock didReceiveTrust: (SecTrustRef) trust completeHandler: (void (^) (BOOL shouldTrustPeer)) completeHandler

As part of this function, you should read all the certificates from the trust and try to reconcile with the certificate that you provided to the application.

Code example:


 - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port; { // Configure SSL/TLS settings NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:3]; // get the certificates as data for further operations SecIdentityRef identity1 = nil; SecTrustRef trust1 = nil; NSData *certData1 = [[NSData alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"[Dev] InHouse_Certificates" ofType:@"p12"]]; CFDataRef myCertData1 = (__bridge_retained CFDataRef)(certData1); [self extractIdentityAndTrust:myCertData1 withIdentity:&identity1 withTrust:&trust1 withPassword:CFSTR("1234")]; NSString* summaryString1 = [self copySummaryString:&identity1]; SecIdentityRef identity2 = nil; SecTrustRef trust2 = nil; NSData *certData2 = [[NSData alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"[Dis] InHouse_Certificates" ofType:@"p12"]]; CFDataRef myCertData2 = (__bridge_retained CFDataRef)(certData2); [self extractIdentityAndTrust:myCertData2 withIdentity:&identity2 withTrust:&trust2 withPassword:CFSTR("1234")]; NSString* summaryString2 = [self copySummaryString:&identity2]; // if data exists, use it if(myCertData1 && myCertData2) { //Delete if already exist. Just temporary SecItemDelete((__bridge CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys: (__bridge id)(kSecClassKey), kSecClass, (__bridge id)kSecAttrKeyTypeRSA, kSecAttrKeyType, (__bridge id)kSecAttrKeyClassPublic, kSecAttrKeyClass, kCFBooleanTrue, kSecAttrIsPermanent, [summaryString1 dataUsingEncoding:NSUTF8StringEncoding], kSecAttrApplicationTag, certData1, kSecValueData, kCFBooleanTrue, kSecReturnPersistentRef, nil]); OSStatus status1 = SecItemAdd((__bridge CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys: (__bridge id)(kSecClassKey), kSecClass, (__bridge id)kSecAttrKeyTypeRSA, kSecAttrKeyType, (__bridge id)kSecAttrKeyClassPublic, kSecAttrKeyClass, kCFBooleanTrue, kSecAttrIsPermanent, [summaryString1 dataUsingEncoding:NSUTF8StringEncoding], kSecAttrApplicationTag, certData1, kSecValueData, kCFBooleanTrue, kSecReturnPersistentRef, nil], NULL); //don't need public key ref // Setting "cer" is successfully and delivers "noErr" in first run, then "errKCDuplicateItem" NSLog(@"evaluate with status %d", (int)status1); //Delete if already exist. Just temporary SecItemDelete((__bridge CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys: (__bridge id)(kSecClassKey), kSecClass, (__bridge id)kSecAttrKeyTypeRSA, kSecAttrKeyType, (__bridge id)kSecAttrKeyClassPublic, kSecAttrKeyClass, kCFBooleanTrue, kSecAttrIsPermanent, [summaryString2 dataUsingEncoding:NSUTF8StringEncoding], kSecAttrApplicationTag, certData2, kSecValueData, kCFBooleanTrue, kSecReturnPersistentRef, nil]); //NSString *name2 = [NSString stringWithUTF8String:CFStringGetCStringPtr(SecCertificateCopySubjectSummary(cert2), kCFStringEncodingUTF8)]; OSStatus status2 = SecItemAdd((__bridge CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys: (__bridge id)(kSecClassKey), kSecClass, (__bridge id)kSecAttrKeyTypeRSA, kSecAttrKeyType, (__bridge id)kSecAttrKeyClassPublic, kSecAttrKeyClass, kCFBooleanTrue, kSecAttrIsPermanent, [summaryString2 dataUsingEncoding:NSUTF8StringEncoding], kSecAttrApplicationTag, certData2, kSecValueData, kCFBooleanTrue, kSecReturnPersistentRef, nil], NULL); //don't need public key ref NSLog(@"evaluate with status %d", (int)status2); SecCertificateRef myReturnedCertificate1 = NULL; OSStatus status3 = SecIdentityCopyCertificate (identity1, &myReturnedCertificate1); SecCertificateRef myReturnedCertificate2 = NULL; OSStatus status4 = SecIdentityCopyCertificate (identity2, &myReturnedCertificate2); NSArray *myCerts = [[NSArray alloc] initWithObjects:(__bridge id)identity1, (__bridge id)myReturnedCertificate1, nil]; [settings setObject:myCerts forKey:(NSString *)kCFStreamSSLCertificates]; // Allow self-signed certificates [settings setObject:[NSNumber numberWithBool:YES] forKey:GCDAsyncSocketManuallyEvaluateTrust]; [sock startTLS:settings]; } } 

If for some reason you decide to evaluate trust manually.

 - (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler { dispatch_queue_t bgQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(bgQueue, ^{ // This is where you would (eventually) invoke SecTrustEvaluate. SecIdentityRef identity1 = nil; SecTrustRef trust1 = nil; NSData *certData1 = [[NSData alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"[Dev] InHouse_Certificates" ofType:@"p12"]]; CFDataRef myCertData1 = (__bridge_retained CFDataRef)(certData1); [self extractIdentityAndTrust:myCertData1 withIdentity:&identity1 withTrust:&trust1 withPassword:CFSTR("1234")]; SecIdentityRef identity2 = nil; SecTrustRef trust2 = nil; NSData *certData2 = [[NSData alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"[Dis] InHouse_Certificates" ofType:@"p12"]]; CFDataRef myCertData2 = (__bridge_retained CFDataRef)(certData2); [self extractIdentityAndTrust:myCertData2 withIdentity:&identity2 withTrust:&trust2 withPassword:CFSTR("1234")]; if(myCertData1 && myCertData2) { CFArrayRef arrayRefTrust = SecTrustCopyProperties(trust); SecTrustResultType result = kSecTrustResultUnspecified; // usualy should work already here OSStatus status = SecTrustEvaluate(trust, &result); NSLog(@"evaluate with result %d and status %d", result, (int)status); NSLog(@"trust properties: %@", arrayRefTrust); /* log: evaluate with result 5 and status 0 trust properties: ( { type = error; value = "Root certificate is not trusted."; // expected, when top part was not working } */ SecCertificateRef myReturnedCertificate1 = NULL; OSStatus status3 = SecIdentityCopyCertificate (identity1, &myReturnedCertificate1); SecCertificateRef myReturnedCertificate2 = NULL; OSStatus status4 = SecIdentityCopyCertificate (identity2, &myReturnedCertificate2); const void *ref[] = {myReturnedCertificate1}; CFIndex count = SecTrustGetCertificateCount(trust); // CFMutableArrayRef aryRef = CFArrayCreateMutable(NULL, count + 1, NULL); // CFArrayAppendValue(aryRef, ref); CFArrayCreate(NULL, ref, 2, NULL); // # # # # // so check one by one... BOOL isMatching = NO; for (int i = 0; i < count; i++) { SecCertificateRef certRef = SecTrustGetCertificateAtIndex(trust, i); NSString *name = [NSString stringWithUTF8String:CFStringGetCStringPtr(SecCertificateCopySubjectSummary(certRef), kCFStringEncodingUTF8)]; NSLog(@"remote cert at index %d is '%@'", i, name); const void *ref[] = {certRef, myReturnedCertificate1}; CFArrayRef aryCheck = CFArrayCreate(NULL, ref, 2, NULL); SecTrustRef trustManual; OSStatus certStatus = SecTrustCreateWithCertificates(aryCheck, SecPolicyCreateBasicX509(), &trustManual); // certStatus always noErr NSLog(@"certStatus: %d", (int)certStatus); SecTrustResultType result; OSStatus status = SecTrustEvaluate(trustManual, &result); CFArrayRef arrayRef = SecTrustCopyProperties(trustManual); NSLog(@"evaluate with result %d and status %d", result, (int)status); NSLog(@"trust properties: %@", arrayRef); /* log: evaluate with result 5 and status 0 trust properties: ( { type = error; value = "Root certificate is not trusted."; } */ if (status == noErr && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified)) { isMatching = YES; NSLog(@"certificates matches"); } else { NSLog(@"certificates differs"); } } if (isMatching || (status == noErr && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified))) { completionHandler(YES); } else { completionHandler(NO); } } completionHandler(NO); }); } 

Update:

According to Apple documentation:

You must specify a secIdentityRef object in certRefs [0] that identifies the certificate of the sheet and its corresponding private key. Specifying a root certificate is optional;

As suggested by Apple, if you use the in.cer certificate format, you must map both certificates using a peer domain name (fully qualified domain name).

You can use this function to check the common field name in peers. If you call this function and the common name in the certificate does not match the value you specified in the peerName parameter, then the confirmation message is not executed and returns errSSLXCertChainInvalid. Using this function is optional.

+6
source

Why measure confidence manually? Could you instead install the CA certificate as the only trusted root for GCDAsyncSocket to evaluate in the SSL settings and let it do the validation for you?

In such a model, you could (1) reduce your own coding efforts [and risk] as well (2) trust the certificates signed by your private CA for this connection [vs also trust the public CAs in the default trust store].

0
source

Source: https://habr.com/ru/post/1212953/


All Articles