X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_ssl/lib/sslCrypto.c?ds=sidebyside diff --git a/Security/libsecurity_ssl/lib/sslCrypto.c b/Security/libsecurity_ssl/lib/sslCrypto.c new file mode 100644 index 00000000..07457375 --- /dev/null +++ b/Security/libsecurity_ssl/lib/sslCrypto.c @@ -0,0 +1,569 @@ +/* + * Copyright (c) 2006-2014 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +/* + * sslCrypto.c - interface between SSL and crypto libraries + */ + +#include "sslCrypto.h" +#include "sslContext.h" +#include "sslMemory.h" +#include "sslUtils.h" +#include "sslDebug.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include "utilities/SecCFRelease.h" + +#if TARGET_OS_IPHONE +#include +#include +#endif + +/* + * Get algorithm id for a SSLPubKey object. + */ +CFIndex sslPubKeyGetAlgorithmID(SecKeyRef pubKey) +{ +#if TARGET_OS_IPHONE + return SecKeyGetAlgorithmID(pubKey); +#else + return SecKeyGetAlgorithmId(pubKey); +#endif +} + +/* + * Get algorithm id for a SSLPrivKey object. + */ +CFIndex sslPrivKeyGetAlgorithmID(SecKeyRef privKey) +{ +#if TARGET_OS_IPHONE + return SecKeyGetAlgorithmID(privKey); +#else + return SecKeyGetAlgorithmId(privKey); +#endif +} + + +OSStatus +sslCreateSecTrust( + SSLContext *ctx, + CFArrayRef certChain, + bool arePeerCerts, + SecTrustRef *pTrust) /* RETURNED */ +{ + OSStatus status = errSecAllocate; + CFStringRef peerDomainName = NULL; + CFTypeRef policies = NULL; + SecTrustRef trust = NULL; + const char *peerDomainNameData = NULL; + size_t peerDomainNameLen = 0; + + if(ctx->protocolSide==kSSLClientSide) { + tls_handshake_get_peer_hostname(ctx->hdsk, &peerDomainNameData, &peerDomainNameLen); + } + + if (CFArrayGetCount(certChain) == 0) { + status = errSSLBadCert; + goto errOut; + } + + if (arePeerCerts) { + if (peerDomainNameLen && peerDomainNameData) { + CFIndex len = peerDomainNameLen; + if (peerDomainNameData[len - 1] == 0) { + len--; + //secwarning("peerDomainName is zero terminated!"); + } + /* @@@ Double check that this is the correct encoding. */ + require(peerDomainName = CFStringCreateWithBytes(kCFAllocatorDefault, + (const UInt8 *)peerDomainNameData, len, + kCFStringEncodingUTF8, false), errOut); + } + } + /* If we are the client, our peer certificates must satisfy the + ssl server policy. */ + bool server = ctx->protocolSide == kSSLClientSide; + require(policies = SecPolicyCreateSSL(server, peerDomainName), errOut); + + require_noerr(status = SecTrustCreateWithCertificates(certChain, policies, + &trust), errOut); + + /* If we have trustedAnchors we set them here. */ + if (ctx->trustedCerts) { + require_noerr(status = SecTrustSetAnchorCertificates(trust, + ctx->trustedCerts), errOut); + require_noerr(status = SecTrustSetAnchorCertificatesOnly(trust, + ctx->trustedCertsOnly), errOut); + } + + status = errSecSuccess; + +errOut: + CFReleaseSafe(peerDomainName); + CFReleaseSafe(policies); + + *pTrust = trust; + + return status; +} + +/* Return the first certificate reference from the supplied array + * whose data matches the given certificate, or NULL if none match. + */ +static +SecCertificateRef +sslGetMatchingCertInArray( + SecCertificateRef certRef, + CFArrayRef certArray) +{ + SecCertificateRef matchedCert = NULL; + + if (certRef == NULL || certArray == NULL) { + return NULL; + } + + CFDataRef certData = SecCertificateCopyData(certRef); + if (certData) { + CFIndex idx, count = CFArrayGetCount(certArray); + for(idx=0; idxtrustedCerts. + * + * If arePeerCerts is true, host name verification is enabled and we + * save the resulting SecTrustRef in ctx->peerSecTrust. Otherwise + * we're just validating our own certs; no host name checking and + * peerSecTrust is transient. + */ +static OSStatus sslVerifyCertChain( + SSLContext *ctx, + CFArrayRef certChain, + bool arePeerCerts) +{ + OSStatus status; + SecTrustRef trust = NULL; + + assert(certChain); + + if (arePeerCerts) { + /* renegotiate - start with a new SecTrustRef */ + CFReleaseNull(ctx->peerSecTrust); + } + + status = sslCreateSecTrust(ctx, certChain, arePeerCerts, &trust); + + if (!ctx->enableCertVerify) { + /* trivial case, this is caller's responsibility */ + status = errSecSuccess; + goto errOut; + } + + SecTrustResultType secTrustResult; + require_noerr(status = SecTrustEvaluate(trust, &secTrustResult), errOut); + switch (secTrustResult) { + case kSecTrustResultUnspecified: + /* cert chain valid, no special UserTrust assignments */ + case kSecTrustResultProceed: + /* cert chain valid AND user explicitly trusts this */ + status = errSecSuccess; + break; + case kSecTrustResultDeny: + case kSecTrustResultConfirm: + case kSecTrustResultRecoverableTrustFailure: + default: + if(ctx->allowAnyRoot) { + sslErrorLog("***Warning: accepting unverified cert chain\n"); + status = errSecSuccess; + } + else { + /* + * If the caller provided a list of trusted leaf certs, check them here + */ + if(ctx->trustedLeafCerts) { + if (sslGetMatchingCertInArray((SecCertificateRef)CFArrayGetValueAtIndex(certChain, 0), + ctx->trustedLeafCerts)) { + status = errSecSuccess; + goto errOut; + } + } + status = errSSLXCertChainInvalid; + } + /* Do we really need to return things like: + errSSLNoRootCert + errSSLUnknownRootCert + errSSLCertExpired + errSSLCertNotYetValid + errSSLHostNameMismatch + for our client to see what went wrong, or should we just always + return + errSSLXCertChainInvalid + when something is wrong? */ + break; + } + +errOut: + if (arePeerCerts) + ctx->peerSecTrust = trust; + else + CFReleaseSafe(trust); + + return status; +} + +/* Extract public SecKeyRef from Certificate Chain */ +static +int sslCopyPeerPubKey(const SSLCertificate *certchain, + SecKeyRef *pubKey) +{ + int err; + check(pubKey); + SecTrustRef trust = NULL; + const SSLCertificate *cert; + CFMutableArrayRef certArray = NULL; + CFDataRef certData = NULL; + SecCertificateRef cfCert = NULL; + + err = errSSLInternal; + + certArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + cert = certchain; + while(cert) { + require((certData = CFDataCreate(kCFAllocatorDefault, cert->derCert.data, cert->derCert.length)), out); + require((cfCert = SecCertificateCreateWithData(kCFAllocatorDefault, certData)), out); + CFArrayAppendValue(certArray, cfCert); + CFReleaseNull(cfCert); + CFReleaseNull(certData); + cert=cert->next; + } + + require_noerr((err=SecTrustCreateWithCertificates(certArray, NULL, &trust)), out); + SecKeyRef key = SecTrustCopyPublicKey(trust); + require_action(key, out, err=-9808); // errSSLBadCert + + *pubKey = key; + + err = errSecSuccess; + +out: + CFReleaseSafe(certData); + CFReleaseSafe(cfCert); + CFReleaseSafe(trust); + CFReleaseSafe(certArray); + + return err; +} + +/* Extract the pubkey from a cert chain, and send it to the tls_handshake context */ +static int tls_set_peer_pubkey(tls_handshake_t hdsk, const SSLCertificate *certchain) +{ + int err; + CFIndex algId; + SecKeyRef pubkey = NULL; + CFDataRef modulus = NULL; + CFDataRef exponent = NULL; + CFDataRef ecpubdata = NULL; + +#if 0 + { /* dump certs */ + int i=0; + int j; + const SSLCertificate *tmp = certchain; + while(tmp) { + printf("cert%d[] = {", i); + for(j=0; jderCert.length; j++) { + if((j&0xf)==0) + printf("\n"); + printf("0x%02x, ", tmp->derCert.data[j]); + } + printf("}\n"); + tmp=tmp->next; + i++; + } + } +#endif + + require_noerr((err=sslCopyPeerPubKey(certchain, &pubkey)), errOut); + +#if TARGET_OS_IPHONE + algId = SecKeyGetAlgorithmID(pubkey); +#else + algId = SecKeyGetAlgorithmId(pubkey); +#endif + + err = errSSLCrypto; + + switch(algId) { + case kSecRSAAlgorithmID: + { + require((modulus = SecKeyCopyModulus(pubkey)), errOut); + require((exponent = SecKeyCopyExponent(pubkey)), errOut); + + tls_buffer mod; + tls_buffer exp; + + mod.data = (uint8_t *)CFDataGetBytePtr(modulus); + mod.length = CFDataGetLength(modulus); + + exp.data = (uint8_t *)CFDataGetBytePtr(exponent); + exp.length = CFDataGetLength(exponent); + + err = tls_handshake_set_peer_rsa_public_key(hdsk, &mod, &exp); + break; + } + case kSecECDSAAlgorithmID: + { + tls_named_curve curve = SecECKeyGetNamedCurve(pubkey); + require((ecpubdata = SecECKeyCopyPublicBits(pubkey)), errOut); + + tls_buffer pubdata; + pubdata.data = (uint8_t *)CFDataGetBytePtr(ecpubdata); + pubdata.length = CFDataGetLength(ecpubdata); + + err = tls_handshake_set_peer_ec_public_key(hdsk, curve, &pubdata); + + break; + } + default: + break; + } + +errOut: + CFReleaseSafe(pubkey); + CFReleaseSafe(modulus); + CFReleaseSafe(exponent); + CFReleaseSafe(ecpubdata); + + return err; +} + +/* Convert cert in DER format into an CFArray of SecCertificateRef */ +CFArrayRef +tls_get_peer_certs(const SSLCertificate *certs) +{ + const SSLCertificate *cert; + + CFMutableArrayRef certArray = NULL; + CFDataRef certData = NULL; + SecCertificateRef cfCert = NULL; + + certArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + require(certArray, out); + cert = certs; + while(cert) { + require((certData = CFDataCreate(kCFAllocatorDefault, cert->derCert.data, cert->derCert.length)), out); + require((cfCert = SecCertificateCreateWithData(kCFAllocatorDefault, certData)), out); + CFArrayAppendValue(certArray, cfCert); + CFReleaseNull(cfCert); + CFReleaseNull(certData); + cert=cert->next; + } + + return certArray; + +out: + CFReleaseNull(cfCert); + CFReleaseNull(certData); + CFReleaseNull(certArray); + return NULL; +} + +int +tls_verify_peer_cert(SSLContext *ctx) +{ + int err; + const SSLCertificate *certs; + + certs = tls_handshake_get_peer_certificates(ctx->hdsk); + CFReleaseNull(ctx->peerCert); + ctx->peerCert = tls_get_peer_certs(certs); + + err = sslVerifyCertChain(ctx, ctx->peerCert, true); + tls_handshake_trust_t trust; + switch (err) { + case errSecSuccess: + trust = tls_handshake_trust_ok; + break; + case errSSLUnknownRootCert: + case errSSLNoRootCert: + trust = tls_handshake_trust_unknown_root; + break; + case errSSLCertExpired: + case errSSLCertNotYetValid: + trust = tls_handshake_trust_cert_expired; + break; + case errSSLXCertChainInvalid: + default: + trust = tls_handshake_trust_cert_invalid; + break; + } + + tls_handshake_set_peer_trust(ctx->hdsk, trust); + + if(err) + goto out; + + /* Set the public key */ + tls_set_peer_pubkey(ctx->hdsk, certs); + + /* Now that cert verification is done, update context state */ + /* (this code was formerly in SSLProcessHandshakeMessage, */ + /* directly after the return from SSLProcessCertificate) */ + if(ctx->protocolSide == kSSLServerSide) { + /* + * Schedule return to the caller to verify the client's identity. + * Note that an error during processing will cause early + * termination of the handshake. + */ + if (ctx->breakOnClientAuth) { + err = errSSLClientAuthCompleted; + } + } else { + /* + * Schedule return to the caller to verify the server's identity. + * Note that an error during processing will cause early + * termination of the handshake. + */ + if (ctx->breakOnServerAuth) { + err = errSSLServerAuthCompleted; + } + } + +out: + + return err; +} + +/* + * After ciphersuite negotiation is complete, verify that we have + * the capability of actually performing the selected cipher. + * Currently we just verify that we have a cert and private signing + * key, if needed, and that the signing key's algorithm matches the + * expected key exchange method. + * + * This is currently called from FindCipherSpec(), after it sets + * ctx->selectedCipherSpec to a (supposedly) valid value, and from + * sslBuildCipherSpecArray(), in server mode (pre-negotiation) only. + */ + +#if 0 +OSStatus sslVerifySelectedCipher(SSLContext *ctx) +{ + + if(ctx->protocolSide == kSSLClientSide) { + return errSecSuccess; + } +#if SSL_PAC_SERVER_ENABLE + if((ctx->masterSecretCallback != NULL) && + (ctx->sessionTicket.data != NULL)) { + /* EAP via PAC resumption; we can do it */ + return errSecSuccess; + } +#endif /* SSL_PAC_SERVER_ENABLE */ + + CFIndex requireAlg; + switch (ctx->selectedCipherSpecParams.keyExchangeMethod) { + case SSL_RSA: + case SSL_RSA_EXPORT: + case SSL_DH_RSA: + case SSL_DH_RSA_EXPORT: + case SSL_DHE_RSA: + case SSL_DHE_RSA_EXPORT: + requireAlg = kSecRSAAlgorithmID; + break; + case SSL_DHE_DSS: + case SSL_DHE_DSS_EXPORT: + case SSL_DH_DSS: + case SSL_DH_DSS_EXPORT: + requireAlg = kSecDSAAlgorithmID; + break; + case SSL_DH_anon: + case SSL_DH_anon_EXPORT: + case TLS_PSK: + requireAlg = kSecNullAlgorithmID; /* no signing key */ + break; + /* + * When SSL_ECDSA_SERVER is true and we support ECDSA on the server side, + * we'll need to add some logic here... + */ +#if SSL_ECDSA_SERVER + case SSL_ECDHE_ECDSA: + case SSL_ECDHE_RSA: + case SSL_ECDH_ECDSA: + case SSL_ECDH_RSA: + case SSL_ECDH_anon: + requireAlg = kSecECDSAAlgorithmID; + break; +#endif + + default: + /* needs update per cipherSpecs.c */ + assert(0); + sslErrorLog("sslVerifySelectedCipher: unknown key exchange method\n"); + return errSSLInternal; + } + + if(requireAlg == kSecNullAlgorithmID) { + return errSecSuccess; + } + + /* private signing key required */ + if(ctx->signingPrivKeyRef == NULL) { + sslErrorLog("sslVerifySelectedCipher: no signing key\n"); + return errSSLBadConfiguration; + } + + /* Check the alg of our signing key. */ + CFIndex keyAlg = sslPrivKeyGetAlgorithmID(ctx->signingPrivKeyRef); + if (requireAlg != keyAlg) { + sslErrorLog("sslVerifySelectedCipher: signing key alg mismatch\n"); + return errSSLBadConfiguration; + } + + return errSecSuccess; +} + +#endif