Ask user to Install and trust Self Signed Certificate from Private CA

All the HTTP Clients allow access to HTTPS web services with self-signed certs, by letting the user explicitly addd and confirm trust in the unverified certificate. I would like to implement something similar in my app.


This concerns a Home Automation app, connecting to IoT devices/accessories for bathrooms and Kitechens on the local network. We would like to use secure connections. The situation:

- The devices do not have SSL certs from a trusted authority, as each device needs to have their own certificate. We do have a CA authority that is Private and issues the device certificates along with Intermediate certificate. How do we install and trust certificates from a Private CA?


What I would our app to ask the user if it should trust the certificate on the device. It should ask this only once when connecting to that device for the first time, then store the certificate and on each subsequent session verify the cert to prevent Man-in-the-middle attackers using a substitute certificate.


We have Root CA certificate and an Intermediate Certificate that needs to be trusted. Everytime is install these certificates on my iPhones I cannot see them in About > Certificate Trust Settings, but I can see them in Profiles of the iOS settings, where it shows the profile as unverfied.


UPDATE:- I now have Company Root CA Certificate installed, as well as Company Issuing CA and Company Intermediate CA certificate installed and all are shown as verified. I have tusted the Company Root CA in Certificate Trust Settings. But our iOS app is still not able to communicate with the devices using SSL, getting error:


"An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={_kCFStreamErrorCodeKey=-9802, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?


What am I missing now?

Replies

The problem with using CA-issued certificates in a network accessory is one of identity. The CA can issue a certificate for a specific accessory but then how does iOS know that this accessory is the one that the CA issued it for?

On the wider Internet we solve this problem using the DNS. The default TLS server trust evaluation policy (

SecPolicyCreateSSL
) knows the DNS name of the server you’re trying to connect to, and verifies that that DNS name matches the name in the certificate (historically in the Common Name field, but now better placed in the Subject Alternative Name extension).

Using the DNS for a network accessory is not feasible. Again, this isn’t about infrastructure — your accessory can have a local DNS name using Bonjour infrastructure [1] — but about identity. How can you be sure that the

my-accessory.local.
on your network is the right accessory.

So there’s no way to achieve this goal using the default HTTPS server trust evaluation. You will have to customise, and once you customise you no longer need to install the CA’s root certificate on the device as a whole; you can just put a copy of it within your app and apply that during the customisation process.

You can learn more about this customisation process in Technote 2232 HTTPS Server Trust Evaluation.

There’s two potential gotchas about here:

  • Not all APIs allow you to customise HTTPS server trust evaluation.

  • Your customisation will only work within your app. They don’t apply in, say, Safari.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

[1] Namely:

Hello Quinn,


I modifies the server trust but still I am not able to complete the request, I can see that after modifying trustResult is kSecTrustResultUnspecified but still request fails , below is the code and error:


-(BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain

{

if(self.SSLPinningMode == AFSSLPinningModeCertificate)

{

return [self shouldTrustServerTrust:serverTrust];

}

else

{

return [super evaluateServerTrust:serverTrust forDomain:domain];

}

}


- (BOOL)shouldTrustServerTrust:(SecTrustRef)serverTrust

{

// load up the bundled root CA


NSString *certificatePath1 = [[NSBundle mainBundle] pathForResource:@"CertCA" ofType:@"der"];

NSData *certificateData1 = [NSData dataWithContentsOfFile:certificatePath1];


NSString *certificatePath2 = [[NSBundle mainBundle] pathForResource:@"TrustedRoot" ofType:@"der"];

NSData *certificateData2 = [NSData dataWithContentsOfFile:certificatePath2];


NSArray *pinnedCertificatesToTest = [NSArray arrayWithObjects:certificateData2, certificateData1, nil];


NSMutableArray *pinnedCertificates = [NSMutableArray array];

for (NSData *certificateData in pinnedCertificatesToTest) {

[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];

}


// establish a chain of trust anchored on our bundled certificate

OSStatus anchorCertificateStatus = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);

NSAssert(anchorCertificateStatus == errSecSuccess, @"Failed to specify custom anchor certificate");



// trust also built-in certificates besides the specified CA

OSStatus trustBuiltinCertificatesStatus = SecTrustSetAnchorCertificatesOnly(serverTrust, false);

NSAssert(trustBuiltinCertificatesStatus == errSecSuccess, @"Failed to reenable trusting built-in anchor certificates");


// verify that trust

SecTrustResultType trustResult;

OSStatus evalStatus = SecTrustEvaluate(serverTrust, &trustResult);

NSAssert(evalStatus == errSecSuccess, @"Failed to evaluate certificate trust");


// did our custom trust chain evaluate successfully

return (trustResult == kSecTrustResultProceed || trustResult == kSecTrustResultUnspecified);

}


Printing description of error:

Error Domain=NSURLErrorDomain Code=0 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x1c0309a20>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey=(

"<cert(0x107149400) s: dev-sio i: Test SHA2 Intermediate CA-1>",

"<cert(0x10714c400) s: Test SHA2 Intermediate CA-1 i: Test Root CA>",

"<cert(0x1070f0200) s: Test Root CA i: Test Root CA>"

), NSUnderlyingError=0x1c024c9f0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x1c0309a20>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=(

"<cert(0x107149400) s: dev-sio i: Test SHA2 Intermediate CA-1>",

"<cert(0x10714c400) s: Test SHA2 Intermediate CA-1 i: Test Root CA>",

"<cert(0x1070f0200) s: Test Root CA i: Test Root CA>"

)}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://dev-sio/, NSErrorFailingURLStringKey=https://dev-sio/, NSErrorClientCertificateStateKey=0}


What am I doing wrong?

For those following along at home, I’m going to respond to KAPPLE via another channel.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"