]> git.saurik.com Git - apple/security.git/blobdiff - Security/libsecurity_ssl/lib/sslCrypto.c
Security-57031.1.35.tar.gz
[apple/security.git] / Security / libsecurity_ssl / lib / sslCrypto.c
diff --git a/Security/libsecurity_ssl/lib/sslCrypto.c b/Security/libsecurity_ssl/lib/sslCrypto.c
new file mode 100644 (file)
index 0000000..0745737
--- /dev/null
@@ -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 <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include <Security/SecTrust.h>
+#include <Security/SecPolicy.h>
+#include <Security/SecCertificate.h>
+
+#include <AssertMacros.h>
+#include "utilities/SecCFRelease.h"
+
+#if TARGET_OS_IPHONE
+#include <Security/SecRSAKey.h>
+#include <Security/SecECKey.h>
+#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; idx<count; idx++) {
+                       SecCertificateRef aCert = (SecCertificateRef)CFArrayGetValueAtIndex(certArray, idx);
+                       CFDataRef aData = SecCertificateCopyData(aCert);
+                       if (aData && CFEqual(aData, certData)) {
+                               matchedCert = aCert;
+                       }
+                       CFReleaseSafe(aData);
+                       if (matchedCert)
+                               break;
+               }
+               CFReleaseSafe(certData);
+       }
+
+    return matchedCert;
+}
+
+/*
+ * Verify a chain of DER-encoded certs.
+ * Last cert in a chain is the leaf; this must also be present
+ * in ctx->trustedCerts.
+ *
+ * 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; j<tmp->derCert.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