| /* 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; |
| |
| /* the object ID of the extension */ |
| ext->id = *oid; |
| |
| /* set critical field */ |
| if (critical) { |
| ext->critical.data = (unsigned char *)&hextrue; |
| ext->critical.len = 1; |
| } |
| |
| /* set the value */ |
| if (copyData) { |
| rv = SECITEM_CopyItem(handle->ownerArena, &ext->value, value); |
| if (rv) { |
| return (SECFailure); |
| } |
| } else { |
| 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; |
| PLArenaPool *arena = NULL; |
| |
| wrapperItem.data = NULL; |
| tmpItem.data = NULL; |
| |
| arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); |
| |
| if (!arena) { |
| return (SECFailure); |
| } |
| |
| rv = cert_FindExtension(extensions, tag, &wrapperItem); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| |
| rv = SEC_QuickDERDecodeItem( |
| arena, &tmpItem, SEC_ASN1_GET(SEC_BitStringTemplate), &wrapperItem); |
| |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| |
| retItem->data = (unsigned char *)PORT_Alloc((tmpItem.len + 7) >> 3); |
| if (retItem->data == NULL) { |
| goto loser; |
| } |
| |
| PORT_Memcpy(retItem->data, tmpItem.data, (tmpItem.len + 7) >> 3); |
| retItem->len = tmpItem.len; |
| |
| rv = SECSuccess; |
| goto done; |
| |
| loser: |
| rv = SECFailure; |
| |
| done: |
| if (arena) { |
| PORT_FreeArena(arena, PR_FALSE); |
| } |
| |
| 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); |
| } |