#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/SecTrustPriv.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
CFIndex sslPubKeyGetAlgorithmID(SecKeyRef pubKey)
{
#if TARGET_OS_IPHONE
return SecKeyGetAlgorithmID(pubKey);
#else
return SecKeyGetAlgorithmId(pubKey);
#endif
}
CFIndex sslPrivKeyGetAlgorithmID(SecKeyRef privKey)
{
#if TARGET_OS_IPHONE
return SecKeyGetAlgorithmID(privKey);
#else
return SecKeyGetAlgorithmId(privKey);
#endif
}
static
OSStatus sslCreateCFArrayFromList(const tls_buffer_list_t *list, CFArrayRef *cfArray)
{
int err;
CFMutableArrayRef array = NULL;
CFDataRef data = NULL;
err = errSSLInternal;
array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
require(array, out);
while(list) {
require((data = CFDataCreate(kCFAllocatorDefault, list->buffer.data, list->buffer.length)), out);
CFArrayAppendValue(array, data);
CFReleaseNull(data);
list=list->next;
}
*cfArray = array;
return errSecSuccess;
out:
CFReleaseSafe(data);
CFReleaseSafe(array);
return err;
}
OSStatus
sslCreateSecTrust(
SSLContext *ctx,
CFArrayRef certChain,
SecTrustRef *pTrust)
{
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 (peerDomainNameLen && peerDomainNameData) {
CFIndex len = peerDomainNameLen;
if (peerDomainNameData[len - 1] == 0) {
len--;
}
require(peerDomainName = CFStringCreateWithBytes(kCFAllocatorDefault,
(const UInt8 *)peerDomainNameData, len,
kCFStringEncodingUTF8, false), errOut);
}
bool use_server_policy = (ctx->protocolSide == kSSLClientSide);
require(policies = SecPolicyCreateSSL(use_server_policy, peerDomainName), errOut);
require_noerr(status = SecTrustCreateWithCertificates(certChain, policies,
&trust), errOut);
if(ctx->protocolSide == kSSLClientSide) {
const tls_buffer_list_t *sct_list = tls_handshake_get_peer_sct_list(ctx->hdsk);
const tls_buffer *ocsp_response = tls_handshake_get_peer_ocsp_response(ctx->hdsk);
if(ocsp_response) {
CFDataRef responseData = CFDataCreate(kCFAllocatorDefault, ocsp_response->data, ocsp_response->length);
status = SecTrustSetOCSPResponse(trust, responseData);
CFReleaseSafe(responseData);
require_noerr(status, errOut);
}
if(sct_list) {
CFArrayRef sctArray = NULL;
require_noerr(status = sslCreateCFArrayFromList(sct_list, &sctArray), errOut);
#if TARGET_OS_IPHONE
status = SecTrustSetSignedCertificateTimestamps(trust, sctArray);
#else
status = noErr;
#endif
CFReleaseSafe(sctArray);
require_noerr(status, errOut);
}
}
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;
}
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;
}
static OSStatus sslVerifyCertChain(
SSLContext *ctx,
CFArrayRef certChain)
{
OSStatus status;
SecTrustRef trust = NULL;
CFReleaseNull(ctx->peerSecTrust);
if(certChain==NULL) {
if(ctx->protocolSide == kSSLClientSide) {
status = errSSLXCertChainInvalid;
sslErrorLog("***Error: NULL server cert chain\n");
} else {
if(ctx->clientAuth == kAlwaysAuthenticate) {
sslErrorLog("***Error: NULL client cert chain\n");
status = errSSLXCertChainInvalid;
} else {
status = noErr;
}
}
goto errOut;
}
status = sslCreateSecTrust(ctx, certChain, &trust);
if (!ctx->enableCertVerify) {
status = errSecSuccess;
goto errOut;
}
SecTrustResultType secTrustResult;
require_noerr(status = SecTrustEvaluate(trust, &secTrustResult), errOut);
switch (secTrustResult) {
case kSecTrustResultUnspecified:
case kSecTrustResultProceed:
status = errSecSuccess;
break;
case kSecTrustResultDeny:
case kSecTrustResultConfirm:
case kSecTrustResultRecoverableTrustFailure:
default:
if(ctx->allowAnyRoot) {
sslErrorLog("***Warning: accepting unverified cert chain\n");
status = errSecSuccess;
}
else {
if(ctx->trustedLeafCerts) {
if (sslGetMatchingCertInArray((SecCertificateRef)CFArrayGetValueAtIndex(certChain, 0),
ctx->trustedLeafCerts)) {
status = errSecSuccess;
goto errOut;
}
}
status = errSSLXCertChainInvalid;
}
break;
}
errOut:
ctx->peerSecTrust = trust;
return status;
}
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_action((cfCert = SecCertificateCreateWithData(kCFAllocatorDefault, certData)), out, err=errSSLBadCert);
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=errSSLBadCert);
*pubKey = key;
err = errSecSuccess;
out:
CFReleaseSafe(certData);
CFReleaseSafe(cfCert);
CFReleaseSafe(trust);
CFReleaseSafe(certArray);
return err;
}
int tls_set_peer_pubkey(SSLContext *ctx)
{
int err;
CFIndex algId;
SecKeyRef pubkey = NULL;
CFDataRef modulus = NULL;
CFDataRef exponent = NULL;
CFDataRef ecpubdata = NULL;
const SSLCertificate *certchain = NULL;
certchain = tls_handshake_get_peer_certificates(ctx->hdsk);
CFReleaseNull(ctx->peerCert);
if(!certchain)
return 0;
ctx->peerCert = tls_get_peer_certs(certchain);
#if 0
{
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(ctx->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(ctx->hdsk, curve, &pubdata);
break;
}
default:
break;
}
errOut:
CFReleaseSafe(pubkey);
CFReleaseSafe(modulus);
CFReleaseSafe(exponent);
CFReleaseSafe(ecpubdata);
return err;
}
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 = 0;
OSStatus st;
st = sslVerifyCertChain(ctx, ctx->peerCert);
tls_handshake_trust_t trust;
switch (st) {
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(ctx->protocolSide == kSSLServerSide) {
if (ctx->breakOnClientAuth) {
err = errSSLClientAuthCompleted;
}
} else if(ctx->peerCert) {
if (ctx->breakOnServerAuth) {
err = errSSLServerAuthCompleted;
}
}
return err;
}
#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)) {
return errSecSuccess;
}
#endif
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;
break;
#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:
assert(0);
sslErrorLog("sslVerifySelectedCipher: unknown key exchange method\n");
return errSSLInternal;
}
if(requireAlg == kSecNullAlgorithmID) {
return errSecSuccess;
}
if(ctx->signingPrivKeyRef == NULL) {
sslErrorLog("sslVerifySelectedCipher: no signing key\n");
return errSSLBadConfiguration;
}
CFIndex keyAlg = sslPrivKeyGetAlgorithmID(ctx->signingPrivKeyRef);
if (requireAlg != keyAlg) {
sslErrorLog("sslVerifySelectedCipher: signing key alg mismatch\n");
return errSSLBadConfiguration;
}
return errSecSuccess;
}
#endif