+/*
+ * Copyright (c) 2002,2011-2012,2014 Apple Inc. All Rights Reserved.
+ *
+ * The contents of this file constitute Original Code as defined in and are
+ * subject to the Apple Public Source License Version 1.2 (the 'License').
+ * You may not use this file except in compliance with the License. Please obtain
+ * a copy of the License at http://www.apple.com/publicsource and read it before
+ * using this file.
+ *
+ * This 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.
+ */
+
+
+/*
+ * DecodedItem.cpp - class representing the common portions of
+ * NSS-format decoded certs and CRLs, with extensions parsed and
+ * decoded (still in NSS format).
+ */
+
+#include "DecodedItem.h"
+#include "cldebugging.h"
+#include "AppleX509CLSession.h"
+#include "CSPAttacher.h"
+#include "CLFieldsCommon.h"
+#include "clNssUtils.h"
+#include "clNameUtils.h"
+#include <security_asn1/SecAsn1Types.h>
+#include <Security/cssmapple.h>
+#include <Security/oidscert.h>
+
+#define MIN_EXTENSIONS 4 // initial size of *mExtensions
+
+DecodedExten::DecodedExten(
+ const CSSM_OID &extnId, // copied
+ bool critical,
+ void *nssObj, // NSS_KeyUsage, NSS_BasicConstraints,
+ // etc. NOT COPIED, exists in same
+ // memory space as coder
+ bool berEncoded, // indicates unknown extension which we
+ // do not BER-decode when parsing a cert
+ const SecAsn1Template *templ, // to decode/encode if !berEncoded
+ SecNssCoder &coder, // all local allocs from here
+ const CSSM_DATA *rawExtn) // NSS_CertExtension.value, copied to
+ // mRawExtn
+ : mCritical(critical),
+ mNssObj(nssObj),
+ mBerEncoded(berEncoded),
+ mTempl(templ),
+ mCoder(coder),
+ mRawExtn(NULL)
+{
+ coder.allocCopyItem(extnId, mExtnId);
+ if(rawExtn) {
+ mRawExtn = (CSSM_DATA *)coder.malloc(sizeof(CSSM_DATA));
+ coder.allocCopyItem(*rawExtn, *mRawExtn);
+ }
+}
+
+DecodedExten::~DecodedExten()
+{
+ /* the only stuff we allocated was in the coder pool and will be freed
+ * when coder is freed */
+}
+
+/*
+ * Given a fully encoded BER object, create a CSSM_X509EXT_TAGandVALUE
+ * representing it. We malloc the CSSM_X509EXT_TAGandVALUE itself using
+ * specified allocator, but the referent (in CSSM_X509EXT_TAGandVALUE.value)
+ * merely points to data inside the berValue.
+ */
+CSSM_X509EXT_TAGandVALUE *DecodedExten::createTagAndValue(
+ const CSSM_DATA &berValue,
+ Allocator &alloc) const
+{
+ if((berValue.Data == NULL) || (berValue.Length == 0)) {
+ return NULL;
+ }
+ CSSM_X509EXT_TAGandVALUE *tv = (CSSM_X509EXT_TAGandVALUE *)
+ alloc.malloc(sizeof(CSSM_X509EXT_TAGandVALUE));
+
+ // Important: by the time we get called, the extension data
+ // has already been deconstructed, and the raw value we are
+ // handed in berValue does not include ASN.1 type or length.
+ // Since createTagAndValue is only called in the case where
+ // the contents are unknown (and thus opaque), always treat
+ // as an octet string.
+
+ tv->type = SEC_ASN1_OCTET_STRING;
+ tv->value.Length = berValue.Length;
+ tv->value.Data = berValue.Data;
+ return tv;
+
+#if 0
+ tv->type = berValue.Data[0];
+
+ /*
+ * length of length is variable; ASN spec says it can actually be up to
+ * 127 bytes, but we only have room for 32 bits!
+ */
+ const uint8 *lp = berValue.Data + 1;
+ uint8 len1 = *lp;
+ if((len1 & 0x80) == 0) {
+ /* short form */
+ tv->value.Length = len1;
+ tv->value.Data = const_cast<uint8 *>(lp + 1);
+ return tv;
+ }
+
+ unsigned numLenBytes = len1 & 0x7f;
+ if(numLenBytes > 4) {
+ clErrorLog("createTagAndValue: impossible length of length (%u)\n", numLenBytes);
+ alloc.free(tv);
+ CssmError::throwMe(CSSMERR_CL_UNKNOWN_FORMAT);
+ }
+
+ uint32 len = 0;
+ lp++; // points to first length byte
+ for(uint32 dex=0; dex<numLenBytes; dex++) {
+ len <<= 8;
+ len += *lp;
+ lp++;
+ }
+ tv->value.Length = len;
+ tv->value.Data = const_cast<uint8 *>(lp);
+ return tv;
+#endif
+}
+
+/*
+ * Convert this extension to a CSSM_X509_EXTENSION, in the specified
+ * (app-level) alloc space, after its contents have
+ * been converted to a native CDSA object (CE_KeyUsage, etc.).
+ */
+void DecodedExten::convertToCdsa(
+ void *cdsaObj, // e.g. CE_KeyUsage
+ // CSSM_DATA_PTR for berEncoded
+ CSSM_X509_EXTENSION_PTR cssmExt, // contents RETURNED
+ Allocator &alloc) const
+{
+ clAllocCopyData(alloc, mExtnId, cssmExt->extnId);
+ cssmExt->critical = mCritical ? CSSM_TRUE : CSSM_FALSE;
+
+ /*
+ * in either case copy the raw extension data if we have it (we may not
+ * have it if this was created via setField).
+ */
+ if(mRawExtn) {
+ clAllocCopyData(alloc, *mRawExtn, cssmExt->BERvalue);
+ }
+ else {
+ cssmExt->BERvalue.Data = NULL;
+ cssmExt->BERvalue.Length = 0;
+ }
+ if(mBerEncoded) {
+ /* an extension we never parsed or understood */
+ assert(cdsaObj == NULL);
+ cssmExt->format = CSSM_X509_DATAFORMAT_ENCODED;
+ cssmExt->value.tagAndValue = createTagAndValue(cssmExt->BERvalue, alloc);
+ }
+ else {
+ /* caller sees parsed version plus raw BER-encoded bytes */
+ assert(cdsaObj != NULL);
+ cssmExt->format = CSSM_X509_DATAFORMAT_PARSED;
+ /* in app alloc's space, mallocd by getField*() */
+ cssmExt->value.parsedValue = cdsaObj;
+ }
+}
+
+/*
+ * Convert a DecodedExten to a CSSM_X509_EXTENSION. This includes
+ * the mapping of the extnId to a known CDSA type and type and doing the
+ * actual NSS-to-CDSA conversion. At the time this function is
+ * called, the DecodedExten either has a valid mNssObj, or it's an
+ * unknown extension type in which case mNssObj is an AsnOcts containing
+ * the opaquely DER-encoded extension value.
+ *
+ * Currently only used when decoding a CRL and converting it en masse
+ * to CDSA.
+ */
+template<class NssType, class CdsaType>
+void nssToCssm(
+ const DecodedExten &decodedExt,
+ NssType *&nssObj, // RETURNED
+ CdsaType *&cdsaObj, // mallocd and RETURNED
+ Allocator &alloc)
+{
+ nssObj = (NssType *)(decodedExt.nssObj());
+ assert(nssObj != NULL);
+ cdsaObj = (CdsaType *)alloc.malloc(sizeof(CdsaType));
+ memset(cdsaObj, 0, sizeof(CdsaType));
+}
+
+void DecodedExten::parse(
+ CSSM_X509_EXTENSION_PTR cssmExt, // mallocd by caller, RETURNED
+ Allocator &alloc) const
+{
+ void *vCdsaObj = NULL;
+ if(mBerEncoded) {
+ /* non-understood extension */
+ convertToCdsa(NULL, cssmExt, alloc);
+ return;
+ }
+ if(clCompareCssmData(&mExtnId, &CSSMOID_AuthorityKeyIdentifier)) {
+ CE_AuthorityKeyID *cdsaObj;
+ NSS_AuthorityKeyId *nssObj;
+ nssToCssm<NSS_AuthorityKeyId, CE_AuthorityKeyID>(
+ *this,
+ nssObj,
+ cdsaObj,
+ alloc);
+ CL_nssAuthorityKeyIdToCssm(*nssObj, *cdsaObj, mCoder, alloc);
+ vCdsaObj = cdsaObj;
+ }
+ /* same encoding (uint32) for all of these: */
+ else if(clCompareCssmData(&mExtnId, &CSSMOID_CrlNumber) ||
+ clCompareCssmData(&mExtnId, &CSSMOID_DeltaCrlIndicator) ||
+ clCompareCssmData(&mExtnId, &CSSMOID_CrlReason)) {
+ CE_CrlNumber *cdsaObj;
+ CSSM_DATA *nssObj;
+ nssToCssm<CSSM_DATA, CE_CrlNumber>(
+ *this,
+ nssObj,
+ cdsaObj,
+ alloc);
+ CSSM_RETURN toThrow;
+ if(clCompareCssmData(&mExtnId, &CSSMOID_CrlReason)) {
+ toThrow = CSSMERR_CL_INVALID_CRL_POINTER;
+ }
+ else {
+ /* tolerate crlNumber > 4 bytes */
+ toThrow = CSSM_OK;
+ }
+ *cdsaObj = clDataToInt(*nssObj, toThrow);
+ vCdsaObj = cdsaObj;
+ }
+ /* same encoding (GeneralNames) for all of these: */
+ else if(clCompareCssmData(&mExtnId, &CSSMOID_IssuerAltName) ||
+ clCompareCssmData(&mExtnId, &CSSMOID_SubjectAltName) ||
+ clCompareCssmData(&mExtnId, &CSSMOID_CertIssuer)) {
+ CE_GeneralNames *cdsaObj;
+ NSS_GeneralNames *nssObj;
+ nssToCssm<NSS_GeneralNames, CE_GeneralNames>(
+ *this,
+ nssObj,
+ cdsaObj,
+ alloc);
+ CL_nssGeneralNamesToCssm(*nssObj, *cdsaObj, mCoder, alloc);
+ vCdsaObj = cdsaObj;
+ }
+ else if(clCompareCssmData(&mExtnId, &CSSMOID_IssuingDistributionPoint)) {
+ CE_IssuingDistributionPoint *cdsaObj;
+ NSS_IssuingDistributionPoint *nssObj;
+ nssToCssm<NSS_IssuingDistributionPoint, CE_IssuingDistributionPoint>(
+ *this,
+ nssObj,
+ cdsaObj,
+ alloc);
+ CL_nssIssuingDistPointToCssm(nssObj, cdsaObj, mCoder, alloc);
+ vCdsaObj = cdsaObj;
+ }
+ /*
+ <rdar://problem/9580989> CrashTracer: [USER] 48 crashes in WebProcess at ...
+ This code should not normally be executed, since it seems from RFC5280 that
+ CSSMOID_IssuingDistributionPoint is the correct extension to use.
+ */
+ else if(clCompareCssmData(&mExtnId, &CSSMOID_CrlDistributionPoints)) {
+ CE_CRLDistPointsSyntax *cdsaObj;
+ NSS_CRLDistributionPoints *nssObj;
+ nssToCssm<NSS_CRLDistributionPoints, CE_CRLDistPointsSyntax>(
+ *this,
+ nssObj,
+ cdsaObj,
+ alloc);
+ CL_nssDistPointsToCssm((const NSS_CRLDistributionPoints&)*nssObj, *cdsaObj, mCoder, alloc);
+ vCdsaObj = cdsaObj;
+ }
+ /*
+ * cert entry extensions
+ */
+ else if(clCompareCssmData(&mExtnId, &CSSMOID_HoldInstructionCode)) {
+ /* value is just an OID */
+ CSSM_OID *cdsaObj;
+ CSSM_DATA *nssObj;
+ nssToCssm<CSSM_DATA, CSSM_OID>(
+ *this,
+ nssObj,
+ cdsaObj,
+ alloc);
+ clAllocCopyData(alloc, *nssObj, *cdsaObj);
+ vCdsaObj = cdsaObj;
+ }
+ else if(clCompareCssmData(&mExtnId, &CSSMOID_InvalidityDate)) {
+ /* GeneralizedTime */
+ CSSM_DATA *cdsaObj;
+ CSSM_DATA *nssObj;
+ nssToCssm<CSSM_DATA, CSSM_DATA>(
+ *this,
+ nssObj,
+ cdsaObj,
+ alloc);
+ clAllocCopyData(alloc, *nssObj, *cdsaObj);
+ vCdsaObj = cdsaObj;
+ }
+ else {
+ /* if we get here, this routine is not keeping up with
+ * clOidToNssInfo() */
+ // assert(0);
+ CssmError::throwMe(CSSMERR_CL_INTERNAL_ERROR);
+ }
+ convertToCdsa(vCdsaObj, cssmExt, alloc);
+}
+
+
+#pragma mark ------ DecodedExtensions ------
+
+/*
+ * A variable-size array of DecodedExtens.
+ * Used for storing cert and CRL extensions as well as per-CRL-entry
+ * extensions.
+ */
+DecodedExtensions::DecodedExtensions(
+ SecNssCoder &coder,
+ Allocator &alloc)
+ : mCoder(coder),
+ mAlloc(alloc),
+ mExtensions(NULL),
+ mNumExtensions(0),
+ mSizeofExtensions(0)
+{
+
+}
+
+DecodedExtensions::~DecodedExtensions()
+{
+ for(unsigned i=0; i<mNumExtensions; i++) {
+ assert(mExtensions[i] != NULL);
+ delete mExtensions[i];
+ }
+ mAlloc.free(mExtensions);
+ mExtensions = NULL;
+ mNumExtensions = 0;
+ mSizeofExtensions = 0;
+}
+
+
+/*
+ * Initialize by decoding a NSS-style NSS_CertExtension array.
+ * This involves figuring out what kind of object is represented in the
+ * octet string in the extension, decoding it, and appending the resulting
+ * NSS object to mExtensions in the form of a DecodedExten.
+ *
+ * Called when decoding either a cert or a CRL (for caching it or
+ * getting its fields) or a cert template (only via
+ * CertGetAllTemplateFields()).
+ */
+void DecodedExtensions::decodeFromNss(
+ NSS_CertExtension **extensions)
+{
+ if(extensions == NULL) {
+ /* OK, no extensions present */
+ return;
+ }
+ unsigned numExtens = clNssArraySize((const void **)extensions);
+
+ /* traverse extension list */
+ for(unsigned dex=0; dex<numExtens; dex++) {
+ NSS_CertExtension *nssExten = extensions[dex];
+
+ /*
+ * For this extension->extnId, cook up an appropriate
+ * NSS-specific type (NSS_KeyUsage, etc.);
+ */
+ CSSM_DATA &rawExtn = nssExten->value;
+ bool berEncoded = false;
+ bool found; // we understand this OID
+ unsigned nssObjLen; // size of associated NSS object
+ const SecAsn1Template *templ = NULL; // template for decoding
+ void *nssObj = NULL; // decode destination
+ found = clOidToNssInfo(nssExten->extnId, nssObjLen, templ);
+ if(!found) {
+ /*
+ * We don't know how to deal with this.
+ */
+ berEncoded = true;
+ }
+ else {
+ /*
+ * Create NSS-style object specific to this extension, just
+ * by knowing its length and ASN template.
+ * Decode the extensions's extnValue into that object. We don't
+ * have to know what kind of object it is anymore.
+ */
+ assert(templ != NULL);
+ nssObj = mCoder.malloc(nssObjLen);
+ memset(nssObj, 0, nssObjLen);
+ PRErrorCode prtn;
+ prtn = mCoder.decodeItem(rawExtn, templ, nssObj);
+ if(prtn) {
+ /*
+ * FIXME - what do we do here? For now flag it
+ * as an non-understood extension...
+ */
+ clErrorLog("decodeExtensions: extension decode error\n");
+ nssObj = NULL;
+ berEncoded = true;
+ }
+ }
+ if((nssObj != NULL) || berEncoded) {
+ /* append if the decode was successful */
+ addExtension(nssExten->extnId,
+ clNssBoolToCssm(nssExten->critical),
+ nssObj,
+ berEncoded,
+ templ,
+ &rawExtn);
+ }
+ }
+}
+
+/*
+ * Encode into a NSS-style Extensions.
+ *
+ * Each extension object, currently stored as some AsnType subclass,
+ * is BER-encoded and the result is stored as an octet string
+ * (AsnOcts) in a new Extension object in the TBS.
+ *
+ * Called from {Crl,Cert}CreateTemplate via encode{Tbs,Cts}().
+ */
+void DecodedExtensions::encodeToNss(
+ NSS_CertExtension **&extensions)
+{
+ assert(extensions == NULL);
+
+ if(mNumExtensions == 0) {
+ /* no extensions, no error */
+ return;
+ }
+
+ /* malloc a NULL_terminated array of NSS_CertExtension pointers */
+ unsigned len = (mNumExtensions + 1) * sizeof(NSS_CertExtension *);
+ extensions = (NSS_CertExtension **)mCoder.malloc(len);
+ memset(extensions, 0, len);
+
+ /* grind thru our DecodedExtens, creating an NSS_CertExtension for
+ * each one */
+ for(unsigned extenDex=0; extenDex<mNumExtensions; extenDex++) {
+ NSS_CertExtension *thisNssExten =
+ (NSS_CertExtension *)mCoder.malloc(sizeof(NSS_CertExtension));
+ memset(thisNssExten, 0, sizeof(NSS_CertExtension));
+ extensions[extenDex] = thisNssExten;
+
+ const DecodedExten *decodedExt = getExtension(extenDex);
+
+ /* BER-encode the extension object if appropriate */
+ if(decodedExt->berEncoded()) {
+ /* unknown extension type, it's already encoded */
+ const CSSM_DATA *srcBer = (const CSSM_DATA *)decodedExt->rawExtn();
+ assert(srcBer != NULL);
+ mCoder.allocCopyItem(*srcBer, thisNssExten->value);
+ }
+ else {
+ PRErrorCode prtn;
+ prtn = mCoder.encodeItem(decodedExt->nssObj(),
+ decodedExt->templ(), thisNssExten->value);
+ if(prtn) {
+ clErrorLog("encodeToNss: extension encode error");
+ CssmError::throwMe(CSSMERR_CL_INTERNAL_ERROR);
+ }
+ }
+ ArenaAllocator arenaAlloc(mCoder);
+ if(decodedExt->critical()) {
+ /* optional, default false */
+ clCssmBoolToNss(CSSM_TRUE, thisNssExten->critical, arenaAlloc);
+ }
+ mCoder.allocCopyItem(decodedExt->extnId(), thisNssExten->extnId);
+ }
+}
+
+/* add/retrieve entries */
+void DecodedExtensions::addExtension(
+ const CSSM_OID &extnId, // copied
+ bool critical,
+ void *nssObj, // NSS_KeyUsage, NSS_BasicConstraints,
+ // etc. NOT COPIED, exists in same
+ // memory space as coder
+ bool berEncoded, // indicates unknown extension which we
+ // do not BER-decode when parsing a cert
+ const SecAsn1Template *templ, // required if !berEncoded
+ const CSSM_DATA *rawExtn) // NSS_CertExtension.value, copied,
+ // optional (not present during a
+ // SetField op)
+{
+ if(mNumExtensions == mSizeofExtensions) {
+ /* expand by doubling, or initial malloc */
+ mSizeofExtensions = mNumExtensions ?
+ (2 * mNumExtensions) : MIN_EXTENSIONS;
+ mExtensions = (DecodedExten **)mAlloc.realloc(
+ mExtensions, mSizeofExtensions * sizeof(DecodedExten));
+ }
+ mExtensions[mNumExtensions++] = new DecodedExten(extnId,
+ critical, nssObj, berEncoded, templ, mCoder, rawExtn);
+}
+
+const DecodedExten *DecodedExtensions::getExtension(
+ unsigned extenDex) const
+{
+ assert(extenDex < mNumExtensions);
+ return mExtensions[extenDex];
+}
+
+/* Convert to CSSM_X509_EXTENSIONS */
+/* Currently only used when decoding a CRL and converting it en masse
+ * to CDSA */
+void DecodedExtensions::convertToCdsa(
+ CSSM_X509_EXTENSIONS &cssmExtens,
+ Allocator &alloc) const
+{
+ memset(&cssmExtens, 0, sizeof(cssmExtens));
+ if(mNumExtensions == 0) {
+ return;
+ }
+ cssmExtens.extensions = (CSSM_X509_EXTENSION_PTR)alloc.malloc(
+ sizeof(CSSM_X509_EXTENSION) * mNumExtensions);
+ memset(cssmExtens.extensions, 0,
+ sizeof(CSSM_X509_EXTENSION) * mNumExtensions);
+ cssmExtens.numberOfExtensions = mNumExtensions;
+ for(unsigned dex=0; dex<mNumExtensions; dex++) {
+ try {
+ getExtension(dex)->parse(&cssmExtens.extensions[dex], alloc);
+ }
+ catch(...) {
+ /* FIXME - what now? */
+ clFieldLog("DecodedExtensions:convertToCdsa: extension "
+ "decode error");
+ }
+ }
+}
+