How to trust self signed certificate programmatically ?

My app is using a self signed certificate and we are planning to use the same in production. To avoid asking user to install the certificate manually, can I add the certificate to the list of trusted anchors using SecTrustSetAnchorCertificates(_: _:). Is this a correct approach ?

Please find the code below:

  func addAnchorToTrust(trust: SecTrust, certificate: SecCertificate) -> SecTrust {
        let array: NSMutableArray = NSMutableArray()
        array.add(certificate)
        SecTrustSetAnchorCertificates(trust, array)
        return trust
    }

    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping  (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

        guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust, let serverTrust = challenge.protectionSpace.serverTrust else {
            completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
            return
        }
        let filePath = Bundle.main.path(forResource: "httpsPublicCertificate", ofType: "cer")

        guard let file = filePath, let savedCertificateData = NSData(contentsOfFile: file), let rootCert = SecCertificateCreateWithData(kCFAllocatorDefault, savedCertificateData) else {
            completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
            return
        }
        let sslInput = addAnchorToTrust(trust: serverTrust, certificate: rootCert)
        var result: SecTrustResultType = SecTrustResultType.unspecified
        let error: OSStatus = SecTrustEvaluate(sslInput, &result)

        if (error != 0){
            completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
            return
        }
        guard self.validate(trust: serverTrust, with: SecPolicyCreateBasicX509()), let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0)
        else {
            completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
            return
        }
        let serverCertificateData = SecCertificateCopyData(serverCertificate)
        let serverCertificateDataPtr = CFDataGetBytePtr(serverCertificateData);
        let serverCertificateDataSize = CFDataGetLength(serverCertificateData);
        let serverCertificateNSData = NSData(bytes: serverCertificateDataPtr, length: serverCertificateDataSize)
        if serverCertificateNSData.isEqual(to: savedCertificateData as Data) {
            completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:serverTrust))
            return
        }
    }

    private func validate(trust: SecTrust, with policy: SecPolicy) -> Bool {
        let status = SecTrustSetPolicies(trust, policy)
        guard status == errSecSuccess else { return false }
        return SecTrustEvaluateWithError(trust, nil)

    }

Accepted Reply

The communication is between app and a local accessory

OK, well, that’s a critical tidbit.

The situation with accessories on the local network is a tricky one. It’s something I’ve discussed with developers many times before, so I took the time today to write it up properly. Please have a read through TLS For Accessory Developers and post back here if you have any questions.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • This write up is really helpful. Thanks a lot :)

Add a Comment

Replies

My app is using a self signed [HTTPS] certificate and we are planning to use the same in production.

I strongly recommend against doing that because:

  • HTTPS certificates are cheap and easy to acquire.

  • Overriding HTTPS server trust evaluation is error prone, and errors in your code can result in security vulnerabilities.

  • Many of our high-level APIs, including URLSession which you seem to be using here, require you to apply an App Transport Security exception for this to work. See the NSAppTransportSecurity documentation for details.

    Note If you ship via the App Store, be aware that ATS exceptions may require justification to App Review.

  • Some of our APIs do not let you override HTTPS server trust evaluation at all. If you go down this path and, later on, you decide that you want to use one of those APIs, you’ve kinda painted yourself into a corner.


As to your technical question:

can I add the certificate to the list of trusted anchors using SecTrustSetAnchorCertificates(_: _:).

Yes.

Although the code you posted is far from ideal. I’m happy to help you fix it but, honestly, I’d be much happier if you just deleted it and got yourself a Real Certificate™.

If you want to continue developing your app while your server-side folks sort out the certificate issue, follow the advice in QA1948 HTTPS and Test Servers.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • The communication is between app and a local accessory, So there is no fixed DNS. So we are using a self signed certificate. Using this code we are able to establish connection but the connection fails intermittently. Getting below error:

    `[connection] nw_socket_get_input_frames [C52:3] recvmsg(fd 37, 1024 bytes) [54: Connection reset by peer]

    Connection 52: received failure notification

    Connection 52: received ECONNRESET with incomplete TLS handshake - generating errSSLClosedNoNotify

    Connection 52: failed to connect 3:-9816, reason -1

    Connection 52: encountered error(3:-9816)

    connection] nw_socket_handle_socket_event [C53:3] Socket SO_ERROR [54: Connection reset by peer]

    Connection 53: received failure notification

    [boringssl] boringssl_metrics_log_metric_block_invoke(144) Failed to log metrics

    Connection 53: failed to connect 3:-9816, reason -1

    Connection 53: encountered error(3:-9816)

    Connection 54: received failure notification

    [boringssl] boringssl_metrics_log_metric_block_invoke(144) Failed to log metrics

    Connection 54: failed to connect 3:-9816, reason -1

    Connection 54: encountered error(3:-9816)

     Task .<1> HTTP load failed, 0/0 bytes (error code: -1200 [3:-9816])

     Task .<1> finished with error [-1200] Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSErrorFailingURLStringKey=https://ip:port/someEndPoint, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask .<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(

        "LocalDataTask .<1>"

    ), NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://ip:port/someEndPoint, NSUnderlyingError=0x600000f22010 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificate`

    Could you please help to solve this issue

Add a Comment

The communication is between app and a local accessory

OK, well, that’s a critical tidbit.

The situation with accessories on the local network is a tricky one. It’s something I’ve discussed with developers many times before, so I took the time today to write it up properly. Please have a read through TLS For Accessory Developers and post back here if you have any questions.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • This write up is really helpful. Thanks a lot :)

Add a Comment