Certificate is not standards compliant on MacOS/iOS, error -9802, strict TLS Trust evaluation failed

This is a little rant about Apple and how they implemented their security checks around certificates. TL;DR: You can’t have a certificate that is valid for more than 825 days.

Like many others, I have a private trusted CA certificate installed on my MacOS. It’s in the keychain an marked as trusted. So far everything worked well, but one day I got error messages when connecting to a Jabber server (XMPP with Starttls). I got it for all software that used the MacOS TLS stack, but not for software like Firefox. So it had to be something Apple specific. The CA certificate was shown as trusted, the intermediate certificate was accepted, but the leaf certificate (certificate of the server) had an error tagged on to it:

certificate is not standards compliant

Wow, what a helpful error message! Now I definitely know what to do. So I started investigating what the problem could be. The first few hits you get when searching the web are totally red herrings, but I didn’t know at that time: crypto/x509: “certificate is not standards compliant” on MacOS and SSL certificate is not Standards compliant“. Of course after reading that I thought I knew what the problem was: The new certificate transparency rules.

Red herring: Certificate transparency

My CA doesn’t have a certificate transparency log, that would make sense I thought at first… but then read about how you have to apply at Apple to get on the list, which obviously isn’t what other people do as there were only official CAs on the list. Searching a little further you find some posts that say internal/private CAs are excluded from certificate transparency in Chrome. That makes sense, but what about Apple? Couldn’t find any information there. So just to debug this, let’s just try to get rid of Certificate Transparency on my local MacOS and see if that helps. How do you do that? Well, only via device management profiles (aka .mobileconfig file). I knew those from my MDM analysis days, so I fired up Apple Configurator to create a new profile. In the profile I was really able to set domains (wildcard-style) that should be excluded from certificate transparency, great! So I exported the profile to a .mobileconfig file and tried to install it. However, when trying to install it I got the following error:

Profile installation failed. A Certificate Transparency Settings payload can only be included in a device profile.

Try searching for that error message on the web. There wasn’t a single hit at the time of writing… So what’s a device profile? I vaguely remembered, but I had to look it up first. The documentation of Apple on how to install profiles didn’t help either. Then I thought maybe I need to sign the .mobileconfig to make it install as a device profile. So I took a random cert/private key from a trusted CA I had (yes, you can take any) and signed the .mobileconfig. It worked and it’s weird, but when you sign with any certificate, the profile is actually shown as “verified”. However, that didn’t help either, couldn’t install as device profile and I couldn’t remember how I used to do it. I searched for a while but couldn’t find a solution.

Unhelpful error messages

Ok, I was stuck there, it would take even more time to figure out how to install that device profile. Let’s go back to square one. So I checked the console for Safari (which only says “can’t established secure connection”) specific errors regarding the certificate “not being standards compliant”. I checked Wireshark (which was pointless, I know, I should only see an “encrypted alert” packet, but I was desperate). I copy pasted some swift code I could run in the “swift repl” interactive console to get an error message:

import Foundation

let url = URL(string: "https://foo.example.org/")!
let (data, _) = try await URLSession.shared.data(from: url)
print(data)

But the only unhelpful messages I got was:

2024-01-30 18:02:02.103330+0100 repl_swift[29733:501906] Connection 1: strict TLS Trust evaluation failed(-9802)
2024-01-30 18:02:02.103370+0100 repl_swift[29733:501906] Connection 1: TLS Trust encountered error 3:-9802
2024-01-30 18:02:02.103376+0100 repl_swift[29733:501906] Connection 1: encountered error(3:-9802)
2024-01-30 18:02:02.206271+0100 repl_swift[29733:501906] Connection 2: strict TLS Trust evaluation failed(-9802)
2024-01-30 18:02:02.206310+0100 repl_swift[29733:501906] Connection 2: TLS Trust encountered error 3:-9802
2024-01-30 18:02:02.206319+0100 repl_swift[29733:501906] Connection 2: encountered error(3:-9802)
2024-01-30 18:02:02.259575+0100 repl_swift[29733:501906] [boringssl] boringssl_context_handle_fatal_alert(1991) [C3.1.1.1:2][0x100307470] read alert, level: fatal, description: protocol version
2024-01-30 18:02:02.263724+0100 repl_swift[29733:501906] [boringssl] boringssl_session_handshake_incomplete(88) [C3.1.1.1:2][0x100307470] SSL library error
2024-01-30 18:02:02.263782+0100 repl_swift[29733:501906] [boringssl] boringssl_session_handshake_error_print(43) [C3.1.1.1:2][0x100307470] Error: 4328543672:error:1000042e:SSL routines:OPENSSL_internal:TLSV1_ALERT_PROTOCOL_VERSION:/AppleInternal/Library/BuildRoots/9ba9e62a-acc5-11ee-9f46-d64f9dd5e0b3/Library/Caches/com.apple.xbs/Sources/boringssl/ssl/tls_record.cc:594:SSL alert number 70
2024-01-30 18:02:02.263797+0100 repl_swift[29733:501906] [boringssl] nw_protocol_boringssl_handshake_negotiate_proceed(774) [C3.1.1.1:2][0x100307470] handshake failed at state 12288: not completed
2024-01-30 18:02:02.264613+0100 repl_swift[29733:501906] Connection 3: received failure notification
2024-01-30 18:02:02.264780+0100 repl_swift[29733:501906] Connection 3: failed to connect 3:-9836, reason -1
2024-01-30 18:02:02.264808+0100 repl_swift[29733:501906] Connection 3: encountered error(3:-9836)
2024-01-30 18:02:02.265218+0100 repl_swift[29733:501906] Task <9E99888A-E626-467B-A532-FDC6E7BC7F90>.<1> HTTP load failed, 0/0 bytes (error code: -1200 [3:-9836])
2024-01-30 18:02:02.269813+0100 repl_swift[29733:501905] Task <9E99888A-E626-467B-A532-FDC6E7BC7F90>.<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://foo.example.org/, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <9E99888A-E626-467B-A532-FDC6E7BC7F90>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask <9E99888A-E626-467B-A532-FDC6E7BC7F90>.<1>"
), NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://foo.example.org/, NSUnderlyingError=0x600000c18600 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, _kCFNetworkCFStreamSSLErrorOriginalValue=-9836, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9836, _NSURLErrorNWPathKey=satisfied (Path is satisfied), viable, interface: utun3, dns}}, _kCFStreamErrorCodeKey=-9836}

Please be aware that only the first three lines are relevant for our problem (TLS1.3 connection failing because “strict TLS Trust evaluation failed”) and that the error message is not helpful at all. A different error message indeed, but searching for that on the web didn’t help either. Everything after the first 3 lines is just boringssl having issues connecting with TLS1.0 etc. So this means there is no error that would help me figure it out, again. Thanks Apple.

Solution

So I asked on Mastodon and someone pointed out the following link: Requirements for trusted certificates in iOS 13 and macOS 10.15. Which had the solution: TLS server certificates must have a validity period of 825 days or fewer (as expressed in the NotBefore and NotAfter fields of the certificate). As soon as we changed to shorter certificate validity, the errors were gone.

All in all I would say this is a pretty bad example for Apple. Even though they say on their Technical Note TN2232 – HTTPS Server Trust Evaluation that you should never disable TLS verification. The openssl examples in there also don’t help in this case. I can see why developers would give up if they don’t get proper error messages with reasons included. Or error codes that can be looked up. Or documentation that easily tells you howto debug it and get details. Or just one single proper documentation what Apple’s view on “standards compliant certificate” could be.

Bad practice, dark pattern. There, I wrote it.

Shellshock fix – bash compiling for OSX

By now probably all of you heard of the shellshock vulnerability. Just as a small heads-up, I wasn’t able to compile the bash version 4.3 on Mac OSX as the last few patches simply don’t work for me. But here’s how you can compile, test and install version 4.2 on your OSX:

#adopted from an original post (that was deleted) from http://www.linus-neumann.de/2014/09/26/clean-your-mac-from-shellshock-by-updating-bash/

PATCH_COMMAND=patch
#No better results with gnu-patch from mac ports -> /opt/local/bin/gpatch


#VERSION_TO_COMPILE=4.1
#VERSION_TO_COMPILE_NO_DOT=41
#VERSION_NUMBER_OF_PATCHES=17

VERSION_TO_COMPILE=4.2
VERSION_TO_COMPILE_NO_DOT=42
VERSION_NUMBER_OF_PATCHES=53

#patches starting from 029 don't work for me in version 4.3
#VERSION_TO_COMPILE=4.3
#VERSION_TO_COMPILE_NO_DOT=43
#VERSION_NUMBER_OF_PATCHES=30


echo "* Downloading bash source code"
wget --quiet http://ftpmirror.gnu.org/bash/bash-$VERSION_TO_COMPILE.tar.gz
tar xzf bash-$VERSION_TO_COMPILE.tar.gz 
cd bash-$VERSION_TO_COMPILE

echo "* Downloading and applying all patches"
for i in $(seq -f "%03g" 1 $VERSION_NUMBER_OF_PATCHES); do
   echo "Downloading and applying patch number $i for bash-$VERSION_TO_COMPILE"
   wget --quiet http://ftp.gnu.org/pub/gnu/bash/bash-$VERSION_TO_COMPILE-patches/bash$VERSION_TO_COMPILE_NO_DOT-$i
   $PATCH_COMMAND -p0 < bash$VERSION_TO_COMPILE_NO_DOT-$i
   #sleep 0.5
done

echo "* configuring and building bash binary"
sleep 1
./configure
make

echo "* writing bash test script"
#The following script will only work when your cwd has the bash binary,
#so you can execute ./bash
#mostly taken from shellshocker.net:
cat << EOF > /tmp/tmp-bash-test-file.sh
    #CVE-2014-6271
    echo "* If the following lines contain the word 'vulnerable' your bash is not fixed:"
    env x='() { :;}; echo vulnerable' ./bash -c "echo no worries so far"
    #CVE-2014-7169
    echo "* If the following lines print the actual date rather than the string 'date' you are vulnerable:"
    env X='() { ()=>\' ./bash -c "echo date"; cat echo;
    #unknown
    echo "* If the following lines contain the word 'vulnerable' your bash is not fixed:"
    env X=' () { }; echo vulnerable' ./bash -c 'echo no worries so far'
    #CVE-2014-7186
    echo "* If the following lines contain the word 'vulnerable' your bash is not fixed:"
    ./bash -c 'true <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF' || echo "vulnerable CVE-2014-7186 , redir_stack"
    #CVE-2014-7187
    echo "* If the following lines contain the word 'vulnerable' your bash is not fixed:"
    (for x in {1..200} ; do echo "for x$x in ; do :"; done; for x in {1..200} ; do echo done ; done) | ./bash || echo "vulnerable CVE-2014-7187 , word_lineno"
    #CVE-2014-6278
    echo "* If the following lines contain the word 'vulnerable' your bash is not fixed:"
    shellshocker='() { echo vulnerable; }' ./bash -c shellshocker
    #CVE-2014-6277
    echo "* If the following lines contain the word 'vulnerable' your bash is not fixed:"
    ./bash -c "f() { x() { _;}; x() { _;} <<a; }" 2>/dev/null || echo vulnerable
    #more tests, probably often testing the same as above, but better safe than sorry
    echo "* If the following lines contain the word 'vulnerable' your bash is not fixed:"
    env X='() { _; } >_[$($())] { echo vulnerable; }' ./bash -c : 
    echo "* If the following lines contain the word 'vulnerable' your bash is not fixed:"
    foo='() { echo vulnerable; }' ./bash -c foo
EOF

echo ""
echo "* Starting a new bash process to check for vulnerabilities"
echo ""
sleep 1
./bash /tmp/tmp-bash-test-file.sh

echo ""
echo "* If the compiled bash binary is not vulnerable, you want to install that binary in your system:"
echo "cd bash-$VERSION_TO_COMPILE"
echo "sudo make install"
echo "sudo mv /bin/bash /bin/old_vulnerable_bash && sudo ln /usr/local/bin/bash /bin/bash"

cheers,
floyd