text: /*
   * Copyright (c) 2004-2008 Apple Inc.  All Rights Reserved.
   *
   * Export of this software from the United States of America may require
   * a specific license from the United States Government.  It is the
   * responsibility of any person or organization contemplating export to
   * obtain such a license before exporting.
   *
   * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
   * distribute this software and its documentation for any purpose and
   * without fee is hereby granted, provided that the above copyright
   * notice appear in all copies and that both that copyright notice and
   * this permission notice appear in supporting documentation, and that
   * the name of Apple Inc. not be used in advertising or publicity pertaining
   * to distribution of the software without specific, written prior
   * permission.  Apple Inc. makes no representations about the suitability of
   * this software for any purpose.  It is provided "as is" without express
   * or implied warranty.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
   * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
   * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
   *
   */
  
  /*
   * pkinit_apple_cms.c - CMS encode/decode routines, Mac OS X version
   *
   * Created 19 May 2004 by Doug Mitchell at Apple.
   */
  
  #if APPLE_PKINIT
  
  #include "pkinit_cms.h"
  #include "pkinit_asn1.h"
  #include "pkinit_apple_utils.h"
  #include <CoreFoundation/CoreFoundation.h>
  #include <Security/CMSEncoder.h>
  #include <Security/CMSDecoder.h>
  #include <Security/Security.h>
  #include <assert.h>
  #include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
  #include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacTypes.h>
  
  /* 
   * Custom OIDS to specify as eContentType 
   */
  #define OID_PKINIT	0x2B, 6, 1, 5, 2, 3
  #define OID_PKINIT_LEN	6
  
  static const uint8	OID_PKINIT_AUTH_DATA[]	    = {OID_PKINIT, 1};
  static const uint8	OID_PKINIT_RKEY_DATA[]	    = {OID_PKINIT, 3};
  
  /* these may go public so keep these symbols private */
  static const CSSM_OID	_CSSMOID_PKINIT_AUTH_DATA = 
  	{OID_PKINIT_LEN+1, (uint8 *)OID_PKINIT_AUTH_DATA};
  static const CSSM_OID	_CSSMOID_PKINIT_RKEY_DATA = 
  	{OID_PKINIT_LEN+1, (uint8 *)OID_PKINIT_RKEY_DATA};
  
  
  #pragma mark ----- CMS utilities ----
  
  #define CFRELEASE(cf) if(cf) { CFRelease(cf); }
  
  /*
   * Convert platform-specific cert/signature status to krb5int_cert_sig_status.
   */
  static krb5int_cert_sig_status pkiCertSigStatus(
      OSStatus certStatus)
  {
      switch(certStatus) {
  	case CSSM_OK:
  	    return pki_cs_good;
  	case CSSMERR_CSP_VERIFY_FAILED:
  	    return pki_cs_sig_verify_fail;
  	case CSSMERR_TP_NOT_TRUSTED:
  	    return pki_cs_no_root;
  	case CSSMERR_TP_INVALID_ANCHOR_CERT:
  	    return pki_cs_unknown_root;
  	case CSSMERR_TP_CERT_EXPIRED:
  	    return pki_cs_expired;
  	case CSSMERR_TP_CERT_NOT_VALID_YET:
  	    return pki_cs_not_valid_yet;
  	case CSSMERR_TP_CERT_REVOKED:
  	    return pki_cs_revoked;
  	case KRB5_KDB_UNAUTH:
  	    return pki_cs_untrusted;
  	case CSSMERR_TP_INVALID_CERTIFICATE:
  	    return pki_cs_bad_leaf;
  	default:
  	    return pki_cs_other_err;
      }
  }
  
  /*
   * Infer krb5int_cert_sig_status from CMSSignerStatus and a CSSM TO
   * cert veriofy result code (obtained via the certVerifyResultCode argument
   * in CMSDecoderCopySignerStatus()).
   */
  static krb5int_cert_sig_status pkiInferSigStatus(
      CMSSignerStatus cms_status,
      OSStatus	    tp_status)
  {
      switch(cms_status) {
  	case kCMSSignerUnsigned:
  	    return pki_not_signed;
  	case kCMSSignerValid:
  	    return pki_cs_good;
  	case kCMSSignerNeedsDetachedContent:
  	    return pki_bad_cms;
  	case kCMSSignerInvalidSignature:
  	    return pki_cs_sig_verify_fail;
  	case kCMSSignerInvalidCert:
  	    /* proceed with TP status */
  	    break;
  	default:
  	    return pki_cs_other_err;
      }
      
      /* signature good, infer end status from TP verify */
      return pkiCertSigStatus(tp_status);
  }
  
  /*
   * Cook up a SecCertificateRef from a krb5_data.
   */
  static OSStatus pkiKrb5DataToSecCert(
      const krb5_data *rawCert,
      SecCertificateRef *secCert)     /* RETURNED */
  {
      CSSM_DATA certData;
      OSStatus ortn;
      
      assert((rawCert != NULL) && (secCert != NULL));
      
      certData.Data = (uint8 *)rawCert->data;
      certData.Length = rawCert->length;
      ortn = SecCertificateCreateFromData(&certData, CSSM_CERT_X_509v3, 
  	CSSM_CERT_ENCODING_DER, secCert);
      if(ortn) {
  	pkiCssmErr("SecCertificateCreateFromData", ortn);
      }
      return ortn;
  }
  
  /*
   * Convert CFArray of SecCertificateRefs to a mallocd array of krb5_datas.
   */
  static krb5_error_code pkiCertArrayToKrb5Data(
      CFArrayRef  cf_certs,
      unsigned	*num_all_certs,
      krb5_data   **all_certs)	
  {
      CFIndex num_certs;
      krb5_data *allCerts = NULL;
      krb5_error_code krtn = 0;
      CFIndex dex;
      
      if(cf_certs == NULL) {
  	*all_certs = NULL;
  	return 0;
      }
      num_certs = CFArrayGetCount(cf_certs);
      *num_all_certs = (unsigned)num_certs;
      if(num_certs == 0) {
  	*all_certs = NULL;
  	return 0;
      }
      allCerts = (krb5_data *)malloc(sizeof(krb5_data) * num_certs);
      if(allCerts == NULL) {
  	return ENOMEM;
      }
      for(dex=0; dex<num_certs; dex++) {	
  	CSSM_DATA cert_data;
  	OSStatus ortn;
  	SecCertificateRef sec_cert;
  	
  	sec_cert = (SecCertificateRef)CFArrayGetValueAtIndex(cf_certs, dex);
  	ortn = SecCertificateGetData(sec_cert, &cert_data);
  	if(ortn) {
  	    pkiCssmErr("SecCertificateGetData", ortn);
  	    krtn = KRB5_PARSE_MALFORMED;
  	    break;
  	}
  	krtn = pkiCssmDataToKrb5Data(&cert_data, &allCerts[dex]);
  	if(krtn) {
  	    break;
  	}
      }
      if(krtn) {
  	if(allCerts) {
  	    free(allCerts);
  	}
      }
      else {
  	*all_certs = allCerts;
      }
      return krtn;
  }
  
  #pragma mark ----- Create CMS message -----
  
  /*
   * Create a CMS message: either encrypted (EnvelopedData), signed 
   * (SignedData), or both (EnvelopedData(SignedData(content)).
   *
   * The message is signed iff signing_cert is non-NULL.
   * The message is encrypted iff recip_cert is non-NULL.
   *
   * The content_type argument specifies to the eContentType
   * for a SignedData's EncapsulatedContentInfo. 
   */
  krb5_error_code krb5int_pkinit_create_cms_msg(
      const krb5_data		*content,	/* Content */
      krb5_pkinit_signing_cert_t	signing_cert,	/* optional: signed by this cert */
      const krb5_data		*recip_cert,	/* optional: encrypted with this cert */
      krb5int_cms_content_type	content_type,   /* OID for EncapsulatedData */
      krb5_ui_4			num_cms_types,	/* optional, unused here */
      const krb5int_algorithm_id	*cms_types,	/* optional, unused here */
      krb5_data			*content_info)  /* contents mallocd and RETURNED */
  {
      krb5_error_code krtn;
      OSStatus ortn;
      SecCertificateRef sec_recip = NULL;
      CFDataRef cf_content = NULL;
      const CSSM_OID *eContentOid = NULL;
      
      if((signing_cert == NULL) && (recip_cert == NULL)) {
  	/* must have one or the other */
  	pkiDebug("krb5int_pkinit_create_cms_msg: no signer or recipient\n");
  	return KRB5_CRYPTO_INTERNAL;
      }
      
      /* 
       * Optional signer cert. Note signing_cert, if present, is 
       * a SecIdentityRef. 
       */
      if(recip_cert) {
  	if(pkiKrb5DataToSecCert(recip_cert, &sec_recip)) {
  	    krtn = ASN1_BAD_FORMAT;
  	    goto errOut;
  	}
      }
      
      /* optional eContentType */
      if(signing_cert) {
  	switch(content_type) {
  	    case ECT_PkAuthData:
  		eContentOid = &_CSSMOID_PKINIT_AUTH_DATA;
  		break;
  	    case ECT_PkReplyKeyKata:
  		eContentOid = &_CSSMOID_PKINIT_RKEY_DATA;
  		break;
  	    case ECT_Data:
  		/* the only standard/default case we allow */
  		break;
  	    default:
  		/* others: no can do */
  		pkiDebug("krb5int_pkinit_create_cms_msg: bad contentType\n");
  		krtn = KRB5_CRYPTO_INTERNAL;
  		goto errOut;
  	}
      }
      
      /* GO */
      ortn = CMSEncode((SecIdentityRef)signing_cert, sec_recip,
  	eContentOid, 
  	FALSE,		/* detachedContent */
  	kCMSAttrNone,	/* no signed attributes that I know of */
  	content->data, content->length,
  	&cf_content);
      if(ortn) {
  	pkiCssmErr("CMSEncode", ortn);
  	krtn = KRB5_CRYPTO_INTERNAL;
  	goto errOut;
      }
      krtn = pkiCfDataToKrb5Data(cf_content, content_info);
  errOut:
      CFRELEASE(sec_recip);
      CFRELEASE(cf_content);
      return krtn;
  }
  
  #pragma mark ----- Generalized parse ContentInfo ----
  
  /*
   * Parse a ContentInfo as best we can. All return fields are optional.
   * If signer_cert_status is NULL on entry, NO signature or cert evaluation 
   * will be performed. 
   */
  krb5_error_code krb5int_pkinit_parse_cms_msg(
      const krb5_data	    *content_info,
      krb5_pkinit_cert_db_t   cert_db,		/* may be required for SignedData */
      krb5_boolean	    is_client_msg,	/* TRUE : msg is from client */
      krb5_boolean	    *is_signed,		/* RETURNED */
      krb5_boolean	    *is_encrypted,	/* RETURNED */
      krb5_data		    *raw_data,		/* RETURNED */
      krb5int_cms_content_type *inner_content_type,/* Returned, ContentType of */
  						/*    EncapsulatedData */
      krb5_data		    *signer_cert,	/* RETURNED */
      krb5int_cert_sig_status *signer_cert_status,/* RETURNED */
      unsigned		    *num_all_certs,	/* size of *all_certs RETURNED */
      krb5_data		    **all_certs)	/* entire cert chain RETURNED */
  {
      SecPolicySearchRef policy_search = NULL;
      SecPolicyRef policy = NULL;
      OSStatus ortn;
      krb5_error_code krtn = 0;
      CMSDecoderRef decoder = NULL;
      size_t num_signers;
      CMSSignerStatus signer_status;
      OSStatus cert_verify_status;
      CFArrayRef cf_all_certs = NULL;
      int msg_is_signed = 0;
      
      if(content_info == NULL) {
  	pkiDebug("krb5int_pkinit_parse_cms_msg: no ContentInfo\n");
  	return KRB5_CRYPTO_INTERNAL;
      }
      
      ortn = CMSDecoderCreate(&decoder);
      if(ortn) {
  	return ENOMEM;
      }
      ortn = CMSDecoderUpdateMessage(decoder, content_info->data, content_info->length);
      if(ortn) {
  	/* no verify yet, must be bad message */
  	krtn = KRB5_PARSE_MALFORMED;
  	goto errOut;
      }
      ortn = CMSDecoderFinalizeMessage(decoder);
      if(ortn) {
  	pkiCssmErr("CMSDecoderFinalizeMessage", ortn);
  	krtn = KRB5_PARSE_MALFORMED;
  	goto errOut;
      }
  
      /* expect zero or one signers */
      ortn = CMSDecoderGetNumSigners(decoder, &num_signers);
      switch(num_signers) {
  	case 0:
  	    msg_is_signed = 0;
  	    break;
  	case 1:
  	    msg_is_signed = 1;
  	    break;
  	default:
  	    krtn = KRB5_PARSE_MALFORMED;
  	    goto errOut;
      }
  
      /*
       * We need a cert verify policy even if we're not actually evaluating 
       * the cert due to requirements in libsecurity_smime.
       */
      ortn = SecPolicySearchCreate(CSSM_CERT_X_509v3,
  	is_client_msg ? &CSSMOID_APPLE_TP_PKINIT_CLIENT : &CSSMOID_APPLE_TP_PKINIT_SERVER, 
  	NULL, &policy_search);
      if(ortn) {
  	pkiCssmErr("SecPolicySearchCreate", ortn);
  	krtn = KRB5_CRYPTO_INTERNAL;
  	goto errOut;
      }
      ortn = SecPolicySearchCopyNext(policy_search, &policy);
      if(ortn) {
  	pkiCssmErr("SecPolicySearchCopyNext", ortn);
  	krtn = KRB5_CRYPTO_INTERNAL;
  	goto errOut;
      }
      
      /* get some basic status that doesn't need heavyweight evaluation */
      if(msg_is_signed) {
  	if(is_signed) {
  	    *is_signed = TRUE;
  	}
  	if(inner_content_type) {
  	    CSSM_OID ec_oid = {0, NULL};
  	    CFDataRef ec_data = NULL;
  	    
  	    krb5int_cms_content_type ctype;
  	    
  	    ortn = CMSDecoderCopyEncapsulatedContentType(decoder, &ec_data);
  	    if(ortn || (ec_data == NULL)) {
  		pkiCssmErr("CMSDecoderCopyEncapsulatedContentType", ortn);
  		krtn = KRB5_CRYPTO_INTERNAL;
  		goto errOut;
  	    }
  	    ec_oid.Data = (uint8 *)CFDataGetBytePtr(ec_data);
  	    ec_oid.Length = CFDataGetLength(ec_data);
  	    if(pkiCompareCssmData(&ec_oid, &CSSMOID_PKCS7_Data)) {
  		ctype = ECT_Data;
  	    }
  	    else if(pkiCompareCssmData(&ec_oid, &CSSMOID_PKCS7_SignedData)) {
  		ctype = ECT_SignedData;
  	    }
  	    else if(pkiCompareCssmData(&ec_oid, &CSSMOID_PKCS7_EnvelopedData)) {
  		ctype = ECT_EnvelopedData;
  	    }
  	    else if(pkiCompareCssmData(&ec_oid, &CSSMOID_PKCS7_EncryptedData)) {
  		ctype = ECT_EncryptedData;
  	    }
  	    else if(pkiCompareCssmData(&ec_oid, &_CSSMOID_PKINIT_AUTH_DATA)) {
  		ctype = ECT_PkAuthData;
  	    }
  	    else if(pkiCompareCssmData(&ec_oid, &_CSSMOID_PKINIT_RKEY_DATA)) {
  		ctype = ECT_PkReplyKeyKata;
  	    }
  	    else {
  		ctype = ECT_Other;
  	    }
  	    *inner_content_type = ctype;
  	    CFRelease(ec_data);
  	}
  	
  	/* 
  	 * Get SignedData's certs if the caller wants them
  	 */
  	if(all_certs) {	    
  	    ortn = CMSDecoderCopyAllCerts(decoder, &cf_all_certs);
  	    if(ortn) {
  		pkiCssmErr("CMSDecoderCopyAllCerts", ortn);
  		krtn = KRB5_CRYPTO_INTERNAL;
  		goto errOut;
  	    }
  	    krtn = pkiCertArrayToKrb5Data(cf_all_certs, num_all_certs, all_certs);
  	    if(krtn) {
  		goto errOut;
  	    }
  	}
  	
  	/* optional signer cert */
  	if(signer_cert) {
  	    SecCertificateRef sec_signer_cert = NULL;
  	    CSSM_DATA cert_data;
  
  	    ortn = CMSDecoderCopySignerCert(decoder, 0, &sec_signer_cert);
  	    if(ortn) {
  		/* should never happen if it's signed */
  		pkiCssmErr("CMSDecoderCopySignerStatus", ortn);
  		krtn = KRB5_CRYPTO_INTERNAL;
  		goto errOut;
  	    }
  	    ortn = SecCertificateGetData(sec_signer_cert, &cert_data);
  	    if(ortn) {
  		pkiCssmErr("SecCertificateGetData", ortn);
  		CFRelease(sec_signer_cert);
  		krtn = KRB5_CRYPTO_INTERNAL;
  		goto errOut;
  	    }
  	    krtn = pkiDataToKrb5Data(cert_data.Data, cert_data.Length, signer_cert);
  	    CFRelease(sec_signer_cert);
  	    if(krtn) {
  		goto errOut;
  	    }
  	}
      }
      else {
  	/* not signed */
  	if(is_signed) {
  	    *is_signed = FALSE;
  	}
  	if(inner_content_type) {
  	    *inner_content_type = ECT_Other;
  	}
  	if(signer_cert) {
  	    signer_cert->data = NULL;
  	    signer_cert->length = 0;
  	}
  	if(signer_cert_status) {
  	    *signer_cert_status = pki_not_signed;
  	}
  	if(num_all_certs) {
  	    *num_all_certs = 0;
  	}
  	if(all_certs) {
  	    *all_certs = NULL;
  	}
      }
      if(is_encrypted) {
  	Boolean bencr;
  	ortn = CMSDecoderIsContentEncrypted(decoder, &bencr);
  	if(ortn) {
  	    pkiCssmErr("CMSDecoderCopySignerStatus", ortn);
  	    krtn = KRB5_CRYPTO_INTERNAL;
  	    goto errOut;
  	}
  	*is_encrypted = bencr ? TRUE : FALSE;
      }
      
      /* 
       * Verify signature and cert. The actual verify operation is optional,
       * per our signer_cert_status argument, but we do this anyway if we need
       * to get the signer cert.
       */
      if((signer_cert_status != NULL) || (signer_cert != NULL)) {
  	
  	ortn = CMSDecoderCopySignerStatus(decoder, 
  	    0,					    /* signerIndex */
  	    policy,
  	    signer_cert_status ? TRUE : FALSE,	    /* evaluateSecTrust */
  	    &signer_status,
  	    NULL,				    /* secTrust - not needed */
  	    &cert_verify_status);
  	if(ortn) {
  	    /* gross error - subsequent processing impossible */
  	    pkiCssmErr("CMSDecoderCopySignerStatus", ortn);
  	    krtn = KRB5_PARSE_MALFORMED;
  	    goto errOut;
  	}
      }
      /* obtain & return status */
      if(signer_cert_status) {
  	*signer_cert_status = pkiInferSigStatus(signer_status, cert_verify_status);
      }
      
      /* finally, the payload */
      if(raw_data) {
  	CFDataRef cf_content = NULL;
  	
  	ortn = CMSDecoderCopyContent(decoder, &cf_content);
  	if(ortn) {
  	    pkiCssmErr("CMSDecoderCopyContent", ortn);
  	    krtn = KRB5_PARSE_MALFORMED;
  	    goto errOut;
  	}
  	krtn = pkiCfDataToKrb5Data(cf_content, raw_data);
  	CFRELEASE(cf_content);
      }
  errOut:
      CFRELEASE(policy_search);
      CFRELEASE(policy);
      CFRELEASE(cf_all_certs);
      CFRELEASE(decoder);
      return krtn;
  }
  
  krb5_error_code krb5int_pkinit_get_cms_types(
      krb5int_algorithm_id    **supported_cms_types,	/* RETURNED */
      krb5_ui_4		    *num_supported_cms_types)	/* RETURNED */
  {
      /* no preference */
      *supported_cms_types = NULL;
      *num_supported_cms_types = 0;
      return 0;
  }
  
  krb5_error_code krb5int_pkinit_free_cms_types(
      krb5int_algorithm_id    *supported_cms_types,
      krb5_ui_4		    num_supported_cms_types)
  {
      /* 
       * We don't return anything from krb5int_pkinit_get_cms_types(), and
       * if we did, it would be a pointer to a statically declared array,
       * so this is a nop. 
       */
      return 0;
  }
  
  #endif /* APPLE_PKINIT */
  