/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * Certificate Extensions handling code * */ #include "cert.h" #include "secitem.h" #include "secoid.h" #include "secder.h" #include "secasn1.h" #include "certxutl.h" #include "secerr.h" #ifdef OLD #include "ocspti.h" /* XXX a better extensions interface would not * require knowledge of data structures of callers */ #endif static CERTCertExtension * GetExtension(CERTCertExtension **extensions, SECItem *oid) { CERTCertExtension **exts; CERTCertExtension *ext = NULL; SECComparison comp; exts = extensions; if (exts) { while (*exts) { ext = *exts; comp = SECITEM_CompareItem(oid, &ext->id); if (comp == SECEqual) break; exts++; } return (*exts ? ext : NULL); } return (NULL); } SECStatus cert_FindExtensionByOID(CERTCertExtension **extensions, SECItem *oid, SECItem *value) { CERTCertExtension *ext; SECStatus rv = SECSuccess; ext = GetExtension(extensions, oid); if (ext == NULL) { PORT_SetError(SEC_ERROR_EXTENSION_NOT_FOUND); return (SECFailure); } if (value) rv = SECITEM_CopyItem(NULL, value, &ext->value); return (rv); } SECStatus CERT_GetExtenCriticality(CERTCertExtension **extensions, int tag, PRBool *isCritical) { CERTCertExtension *ext; SECOidData *oid; if (!isCritical) return (SECSuccess); /* find the extension in the extensions list */ oid = SECOID_FindOIDByTag((SECOidTag)tag); if (!oid) { return (SECFailure); } ext = GetExtension(extensions, &oid->oid); if (ext == NULL) { PORT_SetError(SEC_ERROR_EXTENSION_NOT_FOUND); return (SECFailure); } /* If the criticality is omitted, then it is false by default. ex->critical.data is NULL */ if (ext->critical.data == NULL) *isCritical = PR_FALSE; else *isCritical = (ext->critical.data[0] == 0xff) ? PR_TRUE : PR_FALSE; return (SECSuccess); } SECStatus cert_FindExtension(CERTCertExtension **extensions, int tag, SECItem *value) { SECOidData *oid; oid = SECOID_FindOIDByTag((SECOidTag)tag); if (!oid) { return (SECFailure); } return (cert_FindExtensionByOID(extensions, &oid->oid, value)); } typedef struct _extNode { struct _extNode *next; CERTCertExtension *ext; } extNode; typedef struct { void (*setExts)(void *object, CERTCertExtension **exts); void *object; PLArenaPool *ownerArena; PLArenaPool *arena; extNode *head; int count; } extRec; /* * cert_StartExtensions * * NOTE: This interface changed significantly to remove knowledge * about callers data structures (owner objects) */ void * cert_StartExtensions(void *owner, PLArenaPool *ownerArena, void (*setExts)(void *object, CERTCertExtension **exts)) { PLArenaPool *arena; extRec *handle; arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if (!arena) { return (0); } handle = (extRec *)PORT_ArenaAlloc(arena, sizeof(extRec)); if (!handle) { PORT_FreeArena(arena, PR_FALSE); return (0); } handle->object = owner; handle->ownerArena = ownerArena; handle->setExts = setExts; handle->arena = arena; handle->head = 0; handle->count = 0; return (handle); } static unsigned char hextrue = 0xff; /* * Note - assumes that data pointed to by oid->data will not move */ SECStatus CERT_AddExtensionByOID(void *exthandle, SECItem *oid, SECItem *value, PRBool critical, PRBool copyData) { CERTCertExtension *ext; SECStatus rv; extNode *node; extRec *handle; handle = (extRec *)exthandle; /* allocate space for extension and list node */ ext = (CERTCertExtension *)PORT_ArenaZAlloc(handle->ownerArena, sizeof(CERTCertExtension)); if (!ext) { return (SECFailure); } node = (extNode *)PORT_ArenaAlloc(handle->arena, sizeof(extNode)); if (!node) { return (SECFailure); } /* add to list */ node->next = handle->head; handle->head = node; /* point to ext struct */ node->ext = ext; /* set critical field */ if (critical) { ext->critical.data = (unsigned char *)&hextrue; ext->critical.len = 1; } /* set object ID of the extension and its value */ if (copyData) { rv = SECITEM_CopyItem(handle->ownerArena, &ext->id, oid); if (rv) { return (SECFailure); } rv = SECITEM_CopyItem(handle->ownerArena, &ext->value, value); if (rv) { return (SECFailure); } } else { ext->id = *oid; ext->value = *value; } handle->count++; return (SECSuccess); } SECStatus CERT_AddExtension(void *exthandle, int idtag, SECItem *value, PRBool critical, PRBool copyData) { SECOidData *oid; oid = SECOID_FindOIDByTag((SECOidTag)idtag); if (!oid) { return (SECFailure); } return (CERT_AddExtensionByOID(exthandle, &oid->oid, value, critical, copyData)); } SECStatus CERT_EncodeAndAddExtension(void *exthandle, int idtag, void *value, PRBool critical, const SEC_ASN1Template *atemplate) { extRec *handle; SECItem *encitem; handle = (extRec *)exthandle; encitem = SEC_ASN1EncodeItem(handle->ownerArena, NULL, value, atemplate); if (encitem == NULL) { return (SECFailure); } return CERT_AddExtension(exthandle, idtag, encitem, critical, PR_FALSE); } void PrepareBitStringForEncoding(SECItem *bitsmap, SECItem *value) { unsigned char onebyte; unsigned int i, len = 0; /* to prevent warning on some platform at compile time */ onebyte = '\0'; /* Get the position of the right-most turn-on bit */ for (i = 0; i < (value->len) * 8; ++i) { if (i % 8 == 0) onebyte = value->data[i / 8]; if (onebyte & 0x80) len = i; onebyte <<= 1; } bitsmap->data = value->data; /* Add one here since we work with base 1 */ bitsmap->len = len + 1; } SECStatus CERT_EncodeAndAddBitStrExtension(void *exthandle, int idtag, SECItem *value, PRBool critical) { SECItem bitsmap; PrepareBitStringForEncoding(&bitsmap, value); return (CERT_EncodeAndAddExtension(exthandle, idtag, &bitsmap, critical, SEC_ASN1_GET(SEC_BitStringTemplate))); } SECStatus CERT_FinishExtensions(void *exthandle) { extRec *handle; extNode *node; CERTCertExtension **exts; SECStatus rv = SECFailure; handle = (extRec *)exthandle; /* allocate space for extensions array */ exts = PORT_ArenaNewArray(handle->ownerArena, CERTCertExtension *, handle->count + 1); if (exts == NULL) { goto loser; } /* put extensions in owner object and update its version number */ #ifdef OLD switch (handle->type) { case CertificateExtensions: handle->owner.cert->extensions = exts; DER_SetUInteger(ownerArena, &(handle->owner.cert->version), SEC_CERTIFICATE_VERSION_3); break; case CrlExtensions: handle->owner.crl->extensions = exts; DER_SetUInteger(ownerArena, &(handle->owner.crl->version), SEC_CRL_VERSION_2); break; case OCSPRequestExtensions: handle->owner.request->tbsRequest->requestExtensions = exts; break; case OCSPSingleRequestExtensions: handle->owner.singleRequest->singleRequestExtensions = exts; break; case OCSPResponseSingleExtensions: handle->owner.singleResponse->singleExtensions = exts; break; } #endif handle->setExts(handle->object, exts); /* update the version number */ /* copy each extension pointer */ node = handle->head; while (node) { *exts = node->ext; node = node->next; exts++; } /* terminate the array of extensions */ *exts = 0; rv = SECSuccess; loser: /* free working arena */ PORT_FreeArena(handle->arena, PR_FALSE); return rv; } SECStatus CERT_MergeExtensions(void *exthandle, CERTCertExtension **extensions) { CERTCertExtension *ext; SECStatus rv = SECSuccess; SECOidTag tag; extNode *node; extRec *handle = exthandle; if (!exthandle || !extensions) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } while ((ext = *extensions++) != NULL) { tag = SECOID_FindOIDTag(&ext->id); for (node = handle->head; node != NULL; node = node->next) { if (tag == 0) { if (SECITEM_ItemsAreEqual(&ext->id, &node->ext->id)) break; } else { if (SECOID_FindOIDTag(&node->ext->id) == tag) { break; } } } if (node == NULL) { PRBool critical = (ext->critical.len != 0 && ext->critical.data[ext->critical.len - 1] != 0); if (critical && tag == SEC_OID_UNKNOWN) { PORT_SetError(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION); rv = SECFailure; break; } /* add to list */ rv = CERT_AddExtensionByOID(exthandle, &ext->id, &ext->value, critical, PR_TRUE); if (rv != SECSuccess) break; } } return rv; } /* * get the value of the Netscape Certificate Type Extension */ SECStatus CERT_FindBitStringExtension(CERTCertExtension **extensions, int tag, SECItem *retItem) { SECItem wrapperItem, tmpItem = { siBuffer, 0 }; SECStatus rv; PORTCheapArenaPool tmpArena; wrapperItem.data = NULL; tmpItem.data = NULL; PORT_InitCheapArena(&tmpArena, DER_DEFAULT_CHUNKSIZE); rv = cert_FindExtension(extensions, tag, &wrapperItem); if (rv != SECSuccess) { goto loser; } rv = SEC_QuickDERDecodeItem(&tmpArena.arena, &tmpItem, SEC_ASN1_GET(SEC_BitStringTemplate), &wrapperItem); if (rv != SECSuccess) { goto loser; } retItem->data = (unsigned char *)PORT_ZAlloc((tmpItem.len + 7) >> 3); if (retItem->data == NULL) { goto loser; } if (tmpItem.len > 0) { PORT_Memcpy(retItem->data, tmpItem.data, (tmpItem.len + 7) >> 3); } retItem->len = tmpItem.len; rv = SECSuccess; goto done; loser: rv = SECFailure; done: PORT_DestroyCheapArena(&tmpArena); if (wrapperItem.data) { PORT_Free(wrapperItem.data); } return (rv); } PRBool cert_HasCriticalExtension(CERTCertExtension **extensions) { CERTCertExtension **exts; CERTCertExtension *ext = NULL; PRBool hasCriticalExten = PR_FALSE; exts = extensions; if (exts) { while (*exts) { ext = *exts; /* If the criticality is omitted, it's non-critical */ if (ext->critical.data && ext->critical.data[0] == 0xff) { hasCriticalExten = PR_TRUE; break; } exts++; } } return (hasCriticalExten); } PRBool cert_HasUnknownCriticalExten(CERTCertExtension **extensions) { CERTCertExtension **exts; CERTCertExtension *ext = NULL; PRBool hasUnknownCriticalExten = PR_FALSE; exts = extensions; if (exts) { while (*exts) { ext = *exts; /* If the criticality is omitted, it's non-critical. If an extension is critical, make sure that we know how to process the extension. */ if (ext->critical.data && ext->critical.data[0] == 0xff) { if (SECOID_KnownCertExtenOID(&ext->id) == PR_FALSE) { hasUnknownCriticalExten = PR_TRUE; break; } } exts++; } } return (hasUnknownCriticalExten); }