Trusted certificate is not trusted

I am building a command line app to interface to a Bosch Smart Home Controller (SHC) using URLSession and running into a problem with certificate authentication.

Sending a request to the SHC results in a -1202 error "The certificate for this server is invalid..." which was expected as it's counted as a self-signed cert.

In URLSessionDelegate SecTrustEvaluateWithError returned the CFError.localisedDescription Smart Home Controller Productive Root CA” certificate is not trusted

So I used SecItemAdd to add this certificate to my login keychain and then set it to "Always Trust", but the error still persists.

routines:OPENSSL_internal:SSLV3_ALERT_BAD_CERTIFICATE:/AppleInternal/Library/BuildRoots/a8fc4767-fd9e-11ee-8f2e-b26cde007628/Library/Caches/com.apple.xbs/Sources/boringssl/ssl/tls_record.cc:592:SSL alert number 42

I've tried various workarounds and also added an intermediate certificate received from the SHC to my login keychain with "Always Trust" set but the error persists - am I missing something?

Answered by DTS Engineer in 796425022
So I used SecItemAdd to add this certificate to my login keychain and then set it to "Always Trust"

Yeah, don’t do that. To start, it doesn’t work reliably [1] but, more importantly, it seriously undermines the security of your Mac. Your goal is to write code that trusts this accessory, but marking its certificate as trusted extends that trust to all software on your Mac. That’s not good.

The correct solution is to override HTTPS server trust evaluation in your URLSession code. I talk about this in TLS For Accessory Developers.

Share and Enjoy

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

[1] Last I checked it only works if the certificate claims to be a CA — that is, it contains a Basic Constraints extension — but it’s been years since I looked into this in depth, and we’ve significantly tightened up trust evaluation in that time.

So I used SecItemAdd to add this certificate to my login keychain and then set it to "Always Trust"

Yeah, don’t do that. To start, it doesn’t work reliably [1] but, more importantly, it seriously undermines the security of your Mac. Your goal is to write code that trusts this accessory, but marking its certificate as trusted extends that trust to all software on your Mac. That’s not good.

The correct solution is to override HTTPS server trust evaluation in your URLSession code. I talk about this in TLS For Accessory Developers.

Share and Enjoy

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

[1] Last I checked it only works if the certificate claims to be a CA — that is, it contains a Basic Constraints extension — but it’s been years since I looked into this in depth, and we’ve significantly tightened up trust evaluation in that time.

Thanks, I had tried something like that, even just forcing shouldAllowHTTPSConnection(trust: response to true to see if that was the problem, but I still always get the same The certificate for this server is invalid. You might be connecting to a server that is pretending to be “192.168.86.200 ...” error.

NSAllowsLocalNetworking property is not set as (a) I'm running macOS 14.5 so it should not be necessary, and (b) I can't work out how to set Info.plist values in a command line app that has no Bundle....

I can't work out how to set Info.plist values

You don’t need to, because:

  • As you noted, local network privacy isn’t a concern on macOS 14.

  • ATS only applies to bundled executions, like apps and app extensions. It doesn’t apply to command-line tools.

Consider the code below. I put that in a new command-line tool project (using Xcode 16.0b3 on macOS 14.5) and it’s able to successfully access self-signed.badssl.com. Does that work for you?

Share and Enjoy

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


import Foundation

class DisableAllSecurity: NSObject, URLSessionTaskDelegate {

    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {
        switch challenge.protectionSpace.authenticationMethod {
        case NSURLAuthenticationMethodServerTrust:
            let trust = challenge.protectionSpace.serverTrust!
            let credential = URLCredential(trust: trust)
            return (.useCredential, credential)
        default:
            return (.performDefaultHandling, nil)
        }
    }
}

func main() async {
    do {
        print("will run task")
        let url = URL(string: "https://self-signed.badssl.com")!
        let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 60.0)
        let delegate = DisableAllSecurity()
        let (data, response) = try await URLSession.shared.data(for: request, delegate: delegate)
        let httpResponse = response as! HTTPURLResponse
        print("did run task, status: \(httpResponse.statusCode), bytes: \(data.count)")
    } catch let error as NSError {
        print("did not run task, error: \(error.domain) / \(error.code)")
    }
}

await main()

Sorry that's not working, something strange going on here...

I created a new command line tool in Xcode using that exact same code just replacing https://self-signed.badssl.com with https:192.168.86.200:8444/smarthome/rooms

I also checked that the challenge was NSURLAuthenticationMethodServerTrust

Response is the same as I had before...

will run task
did not run task, error: NSURLErrorDomain / -1202
boringssl_context_handle_fatal_alert(2,072) [C1.1.1:2][0x157e07280] read alert, level: fatal, description: bad certificate
boringssl_session_handshake_incomplete(210) [C1.1.1:2][0x157e07280] SSL library error
boringssl_session_handshake_error_print(44) [C1.1.1:2][0x157e07280] Error: 5770378176:error:10000412:SSL routines:OPENSSL_internal:SSLV3_ALERT_BAD_CERTIFICATE:/AppleInternal/Library/BuildRoots/a8fc4767-fd9e-11ee-8f2e-b26cde007628/Library/Caches/com.apple.xbs/Sources/boringssl/ssl/tls_record.cc:592:SSL alert number 42
nw_protocol_boringssl_handshake_negotiate_proceed(779) [C1.1.1:2][0x157e07280] handshake failed at state 12,288: not completed
Connection 1: received failure notification
Connection 1: failed to connect 3:18,446,744,073,709,541,808, reason 18,446,744,073,709,551,615
Connection 1: encountered error(3:18,446,744,073,709,541,808)
Task <857020EA-0B48-44DF-ABE3-E869745E0696>.<1> HTTP load failed, 0/0 bytes (error code: 18,446,744,073,709,550,414 [3:18,446,744,073,709,541,808])
Task <857020EA-0B48-44DF-ABE3-E869745E0696>.<1> finished with error [18,446,744,073,709,550,414] Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “192.168.86.200” which could put your confidential information at risk." UserInfo={NSErrorFailingURLStringKey=https://192.168.86.200:8444/smarthome/rooms, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <857020EA-0B48-44DF-ABE3-E869745E0696>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <857020EA-0B48-44DF-ABE3-E869745E0696>.<1>"
), NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending to be “192.168.86.200” which could put your confidential information at risk., NSErrorFailingURLKey=https://192.168.86.200:8444/smarthome/rooms, NSUnderlyingError=0x60000010ccf0 {Error Domain=kCFErrorDomainCFNetwork Code=-1202 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=1, _kCFNetworkCFStreamSSLErrorOriginalValue=-9808, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9808, _NSURLErrorNWPathKey=satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, dns, uses wifi}}, _kCFStreamErrorCodeKey=-9808}
Program ended with exit code: 0

I'm running macOS 14.5 and Xcode 15 (15A240d). ..

The only difference I can see is my hard-coded IP address, although I tried with the device name (https://shc403ac8.local:8444/smarthome/rooms) and that didn't work either ... do you have any other ideas? I could try the Xcode 16 beta but I'm not sure that's going the issue...

Thanks

Accepted Answer

Doh!! I have found it....

I am also receiving a NSURLAuthenticationMethodClientCertificate challenge that was falling into the section for .performDefaultHandling and was causing the error as no certificate was exchanged from my side (although the error messages imply that the problem was the certificate from the accessory).

I needed to create an identity from the certificate and key I used to set up the connection to the accessory, store it in my keychain and then return that as the client credential from the challenge... and it works

Thanks for your help, this security and identity stuff is certainly a long learning curve...

Trusted certificate is not trusted
 
 
Q