I don't think it's a root cert issue per se since Baltimore CyberTrust is indeed present in keychain and works fine in Chrome (which uses the native certs, unlike Firefox which bundles its own). Interestingly though Safari doesn't validate the chain despite the cert appearing to validate fine in the info pane as mentioned in [1]. Native curl doesn't like the chain either.
Aside from your mentioned workaround, you might consider shipping openssl with its own cert bundle and a libcurl built against that.
[1]
community.cloudflare.com/t/safari-6-0-3-...ers-works-fine/48534
Edit: Chrome seems to have a more robust implementation of certificate verification [2] that also accounts for some flaws in OS X/Security.framework. The only special case for Baltimore certs is about one cross-signed by GTE CyberTrust (which did expire in 2018). Cloudflare only sends up to the ECC CA-3 intermediate though so that issue shouldn't apply (and validation still fails even with GTE deleted), so I'm still not quite sure why curl (and safari) don't validate. Curl & Safari seem to use apple's native secure transport implementation over openssl, so I'm guessing the issue lies somewhere there – looking at curl source as an example, it directly calls into secure transport's SSLHandshake() and relies on the system's cert verification rather than manually verifying the cert chain via the SecTrust* apis as Chrome does.
[2]
chromium.googlesource.com/chromium/chrom...rify_proc_mac.cc#442
Edit: Oh that chromium source code is long out of date. The newer version of cert verification [3] has a much better comment
Code:
OS X lacks proper path discovery; it will take the input certs and never
// backtrack the graph attempting to discover valid paths.
// This can create issues in some situations:
// - When OS X changes the trust store, there may be a chain
// A -> B -> C -> D
// where OS X trusts D (on some versions) and trusts C (on some versions).
// If a server supplies a chain A, B, C (cross-signed by D), then this chain
// will successfully validate on systems that trust D, but fail for systems
// that trust C. If the server supplies a chain of A -> B, then it forces
// all clients to fetch C (via AIA) if they trust D, and not all clients
// (notably, Firefox and Android) will do this, thus breaking them.
// An example of this is the Verizon Business Services root - GTE CyberTrust
// and Baltimore CyberTrust roots represent old and new roots that cause
// issues depending on which version of OS X being used.
//
// - A server may be (misconfigured) to send an expired intermediate
// certificate. On platforms with path discovery, the graph traversal
// will back up to immediately before this intermediate, and then
// attempt an AIA fetch or retrieval from local store. However, OS X
// does not do this, and thus prevents access. While this is ostensibly
// a server misconfiguration issue, the fact that it works on other
// platforms is a jarring inconsistency for users.
//
// - When OS X trusts both C and D (simultaneously), it's possible that the
// version of C signed by D is signed using a weak algorithm (e.g. SHA-1),
// while the version of C in the trust store's signature doesn't matter.
// Since a 'strong' chain exists, it would be desirable to prefer this
// chain.
//
// - A variant of the above example, it may be that the version of B sent by
// the server is signed using a weak algorithm, but the version of B
// present in the AIA of A is signed using a strong algorithm. Since a
// 'strong' chain exists, it would be desirable to prefer this chain.
//
// - A user keychain may contain a less desirable intermediate or root.
// OS X gives the user keychains higher priority than the system keychain,
// so it may build a weak chain.
//
// Because of this, the code below first attempts to validate the peer's
// identity using the supplied chain. If it is not trusted (e.g. the OS only
// trusts C, but the version of C signed by D was sent, and D is not trusted),
// or if it contains a weak chain, it will begin lopping off certificates
// from the end of the chain and attempting to verify. If a stronger, trusted
// chain is found, it is used, otherwise, the algorithm continues until only
// the peer's certificate remains.
//
// If the loop does not find a trusted chain, the loop will be repeated with
// the keychain search order altered to give priority to the System Roots
// keychain.
//
// This does cause a performance hit for these users, but only in cases where
// OS X is building weaker chains than desired, or when it would otherwise
// fail the connection.
Again, none of those conditions seem to apply directly; it'd still be quite interesting to find out why these chains in particular fail.
[3]
chromium.googlesource.com/chromium/src/+...t_verify_proc_mac.cc