| /* 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/. */ |
| |
| #include "plarena.h" |
| #include "seccomon.h" |
| #include "secitem.h" |
| #include "secoidt.h" |
| #include "secasn1.h" |
| #include "secder.h" |
| #include "certt.h" |
| #include "cert.h" |
| #include "certi.h" |
| #include "xconst.h" |
| #include "secerr.h" |
| #include "secoid.h" |
| #include "prprf.h" |
| #include "genname.h" |
| |
| SEC_ASN1_MKSUB(SEC_AnyTemplate) |
| SEC_ASN1_MKSUB(SEC_IntegerTemplate) |
| SEC_ASN1_MKSUB(SEC_IA5StringTemplate) |
| SEC_ASN1_MKSUB(SEC_ObjectIDTemplate) |
| SEC_ASN1_MKSUB(SEC_OctetStringTemplate) |
| |
| static const SEC_ASN1Template CERTNameConstraintTemplate[] = { |
| { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTNameConstraint) }, |
| { SEC_ASN1_ANY, offsetof(CERTNameConstraint, DERName) }, |
| { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, |
| offsetof(CERTNameConstraint, min), SEC_ASN1_SUB(SEC_IntegerTemplate) }, |
| { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 1, |
| offsetof(CERTNameConstraint, max), SEC_ASN1_SUB(SEC_IntegerTemplate) }, |
| { 0 } |
| }; |
| |
| const SEC_ASN1Template CERT_NameConstraintSubtreeSubTemplate[] = { |
| { SEC_ASN1_SEQUENCE_OF | SEC_ASN1_XTRN, 0, SEC_ASN1_SUB(SEC_AnyTemplate) } |
| }; |
| |
| static const SEC_ASN1Template CERTNameConstraintsTemplate[] = { |
| { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CERTNameConstraints) }, |
| { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, |
| offsetof(CERTNameConstraints, DERPermited), |
| CERT_NameConstraintSubtreeSubTemplate }, |
| { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, |
| offsetof(CERTNameConstraints, DERExcluded), |
| CERT_NameConstraintSubtreeSubTemplate }, |
| { 0 } |
| }; |
| |
| static const SEC_ASN1Template CERTOthNameTemplate[] = { |
| { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(OtherName) }, |
| { SEC_ASN1_OBJECT_ID, offsetof(OtherName, oid) }, |
| { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | |
| SEC_ASN1_XTRN | 0, |
| offsetof(OtherName, name), SEC_ASN1_SUB(SEC_AnyTemplate) }, |
| { 0 } |
| }; |
| |
| static const SEC_ASN1Template CERTOtherNameTemplate[] = { |
| { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | 0, |
| offsetof(CERTGeneralName, name.OthName), CERTOthNameTemplate, |
| sizeof(CERTGeneralName) } |
| }; |
| |
| static const SEC_ASN1Template CERT_RFC822NameTemplate[] = { |
| { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 1, |
| offsetof(CERTGeneralName, name.other), |
| SEC_ASN1_SUB(SEC_IA5StringTemplate), sizeof(CERTGeneralName) } |
| }; |
| |
| static const SEC_ASN1Template CERT_DNSNameTemplate[] = { |
| { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 2, |
| offsetof(CERTGeneralName, name.other), |
| SEC_ASN1_SUB(SEC_IA5StringTemplate), sizeof(CERTGeneralName) } |
| }; |
| |
| static const SEC_ASN1Template CERT_X400AddressTemplate[] = { |
| { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_XTRN | 3, |
| offsetof(CERTGeneralName, name.other), SEC_ASN1_SUB(SEC_AnyTemplate), |
| sizeof(CERTGeneralName) } |
| }; |
| |
| static const SEC_ASN1Template CERT_DirectoryNameTemplate[] = { |
| { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | |
| SEC_ASN1_XTRN | 4, |
| offsetof(CERTGeneralName, derDirectoryName), |
| SEC_ASN1_SUB(SEC_AnyTemplate), sizeof(CERTGeneralName) } |
| }; |
| |
| static const SEC_ASN1Template CERT_EDIPartyNameTemplate[] = { |
| { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_XTRN | 5, |
| offsetof(CERTGeneralName, name.other), SEC_ASN1_SUB(SEC_AnyTemplate), |
| sizeof(CERTGeneralName) } |
| }; |
| |
| static const SEC_ASN1Template CERT_URITemplate[] = { |
| { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 6, |
| offsetof(CERTGeneralName, name.other), |
| SEC_ASN1_SUB(SEC_IA5StringTemplate), sizeof(CERTGeneralName) } |
| }; |
| |
| static const SEC_ASN1Template CERT_IPAddressTemplate[] = { |
| { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 7, |
| offsetof(CERTGeneralName, name.other), |
| SEC_ASN1_SUB(SEC_OctetStringTemplate), sizeof(CERTGeneralName) } |
| }; |
| |
| static const SEC_ASN1Template CERT_RegisteredIDTemplate[] = { |
| { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 8, |
| offsetof(CERTGeneralName, name.other), SEC_ASN1_SUB(SEC_ObjectIDTemplate), |
| sizeof(CERTGeneralName) } |
| }; |
| |
| const SEC_ASN1Template CERT_GeneralNamesTemplate[] = { |
| { SEC_ASN1_SEQUENCE_OF | SEC_ASN1_XTRN, 0, SEC_ASN1_SUB(SEC_AnyTemplate) } |
| }; |
| |
| static struct { |
| CERTGeneralNameType type; |
| char *name; |
| } typesArray[] = { { certOtherName, "other" }, |
| { certRFC822Name, "email" }, |
| { certRFC822Name, "rfc822" }, |
| { certDNSName, "dns" }, |
| { certX400Address, "x400" }, |
| { certX400Address, "x400addr" }, |
| { certDirectoryName, "directory" }, |
| { certDirectoryName, "dn" }, |
| { certEDIPartyName, "edi" }, |
| { certEDIPartyName, "ediparty" }, |
| { certURI, "uri" }, |
| { certIPAddress, "ip" }, |
| { certIPAddress, "ipaddr" }, |
| { certRegisterID, "registerid" } }; |
| |
| CERTGeneralNameType |
| CERT_GetGeneralNameTypeFromString(const char *string) |
| { |
| int types_count = sizeof(typesArray) / sizeof(typesArray[0]); |
| int i; |
| |
| for (i = 0; i < types_count; i++) { |
| if (PORT_Strcasecmp(string, typesArray[i].name) == 0) { |
| return typesArray[i].type; |
| } |
| } |
| return 0; |
| } |
| |
| CERTGeneralName * |
| CERT_NewGeneralName(PLArenaPool *arena, CERTGeneralNameType type) |
| { |
| CERTGeneralName *name = arena ? PORT_ArenaZNew(arena, CERTGeneralName) |
| : PORT_ZNew(CERTGeneralName); |
| if (name) { |
| name->type = type; |
| name->l.prev = name->l.next = &name->l; |
| } |
| return name; |
| } |
| |
| /* Copy content of one General Name to another. |
| ** Caller has allocated destination general name. |
| ** This function does not change the destinate's GeneralName's list linkage. |
| */ |
| SECStatus |
| cert_CopyOneGeneralName(PLArenaPool *arena, CERTGeneralName *dest, |
| CERTGeneralName *src) |
| { |
| SECStatus rv; |
| void *mark = NULL; |
| |
| PORT_Assert(dest != NULL); |
| dest->type = src->type; |
| |
| mark = PORT_ArenaMark(arena); |
| |
| switch (src->type) { |
| case certDirectoryName: |
| rv = SECITEM_CopyItem(arena, &dest->derDirectoryName, |
| &src->derDirectoryName); |
| if (rv == SECSuccess) |
| rv = CERT_CopyName(arena, &dest->name.directoryName, |
| &src->name.directoryName); |
| break; |
| |
| case certOtherName: |
| rv = SECITEM_CopyItem(arena, &dest->name.OthName.name, |
| &src->name.OthName.name); |
| if (rv == SECSuccess) |
| rv = SECITEM_CopyItem(arena, &dest->name.OthName.oid, |
| &src->name.OthName.oid); |
| break; |
| |
| default: |
| rv = SECITEM_CopyItem(arena, &dest->name.other, &src->name.other); |
| break; |
| } |
| if (rv != SECSuccess) { |
| PORT_ArenaRelease(arena, mark); |
| } else { |
| PORT_ArenaUnmark(arena, mark); |
| } |
| return rv; |
| } |
| |
| void |
| CERT_DestroyGeneralNameList(CERTGeneralNameList *list) |
| { |
| PZLock *lock; |
| |
| if (list != NULL) { |
| lock = list->lock; |
| PZ_Lock(lock); |
| if (--list->refCount <= 0 && list->arena != NULL) { |
| PORT_FreeArena(list->arena, PR_FALSE); |
| PZ_Unlock(lock); |
| PZ_DestroyLock(lock); |
| } else { |
| PZ_Unlock(lock); |
| } |
| } |
| return; |
| } |
| |
| CERTGeneralNameList * |
| CERT_CreateGeneralNameList(CERTGeneralName *name) |
| { |
| PLArenaPool *arena; |
| CERTGeneralNameList *list = NULL; |
| |
| arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); |
| if (arena == NULL) { |
| goto done; |
| } |
| list = PORT_ArenaZNew(arena, CERTGeneralNameList); |
| if (!list) |
| goto loser; |
| if (name != NULL) { |
| SECStatus rv; |
| list->name = CERT_NewGeneralName(arena, (CERTGeneralNameType)0); |
| if (!list->name) |
| goto loser; |
| rv = CERT_CopyGeneralName(arena, list->name, name); |
| if (rv != SECSuccess) |
| goto loser; |
| } |
| list->lock = PZ_NewLock(nssILockList); |
| if (!list->lock) |
| goto loser; |
| list->arena = arena; |
| list->refCount = 1; |
| done: |
| return list; |
| |
| loser: |
| PORT_FreeArena(arena, PR_FALSE); |
| return NULL; |
| } |
| |
| CERTGeneralName * |
| CERT_GetNextGeneralName(CERTGeneralName *current) |
| { |
| PRCList *next; |
| |
| next = current->l.next; |
| return (CERTGeneralName *)(((char *)next) - offsetof(CERTGeneralName, l)); |
| } |
| |
| CERTGeneralName * |
| CERT_GetPrevGeneralName(CERTGeneralName *current) |
| { |
| PRCList *prev; |
| prev = current->l.prev; |
| return (CERTGeneralName *)(((char *)prev) - offsetof(CERTGeneralName, l)); |
| } |
| |
| CERTNameConstraint * |
| CERT_GetNextNameConstraint(CERTNameConstraint *current) |
| { |
| PRCList *next; |
| |
| next = current->l.next; |
| return (CERTNameConstraint *)(((char *)next) - |
| offsetof(CERTNameConstraint, l)); |
| } |
| |
| CERTNameConstraint * |
| CERT_GetPrevNameConstraint(CERTNameConstraint *current) |
| { |
| PRCList *prev; |
| prev = current->l.prev; |
| return (CERTNameConstraint *)(((char *)prev) - |
| offsetof(CERTNameConstraint, l)); |
| } |
| |
| SECItem * |
| CERT_EncodeGeneralName(CERTGeneralName *genName, SECItem *dest, |
| PLArenaPool *arena) |
| { |
| |
| const SEC_ASN1Template *template; |
| |
| PORT_Assert(arena); |
| if (arena == NULL || !genName) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return NULL; |
| } |
| /* TODO: mark arena */ |
| if (dest == NULL) { |
| dest = PORT_ArenaZNew(arena, SECItem); |
| if (!dest) |
| goto loser; |
| } |
| if (genName->type == certDirectoryName) { |
| if (genName->derDirectoryName.data == NULL) { |
| /* The field hasn't been encoded yet. */ |
| SECItem *pre_dest = SEC_ASN1EncodeItem( |
| arena, &(genName->derDirectoryName), |
| &(genName->name.directoryName), CERT_NameTemplate); |
| if (!pre_dest) |
| goto loser; |
| } |
| if (genName->derDirectoryName.data == NULL) { |
| goto loser; |
| } |
| } |
| switch (genName->type) { |
| case certURI: |
| template = CERT_URITemplate; |
| break; |
| case certRFC822Name: |
| template = CERT_RFC822NameTemplate; |
| break; |
| case certDNSName: |
| template = CERT_DNSNameTemplate; |
| break; |
| case certIPAddress: |
| template = CERT_IPAddressTemplate; |
| break; |
| case certOtherName: |
| template = CERTOtherNameTemplate; |
| break; |
| case certRegisterID: |
| template = CERT_RegisteredIDTemplate; |
| break; |
| /* for this type, we expect the value is already encoded */ |
| case certEDIPartyName: |
| template = CERT_EDIPartyNameTemplate; |
| break; |
| /* for this type, we expect the value is already encoded */ |
| case certX400Address: |
| template = CERT_X400AddressTemplate; |
| break; |
| case certDirectoryName: |
| template = CERT_DirectoryNameTemplate; |
| break; |
| default: |
| PORT_Assert(0); |
| goto loser; |
| } |
| dest = SEC_ASN1EncodeItem(arena, dest, genName, template); |
| if (!dest) { |
| goto loser; |
| } |
| /* TODO: unmark arena */ |
| return dest; |
| loser: |
| /* TODO: release arena back to mark */ |
| return NULL; |
| } |
| |
| SECItem ** |
| cert_EncodeGeneralNames(PLArenaPool *arena, CERTGeneralName *names) |
| { |
| CERTGeneralName *current_name; |
| SECItem **items = NULL; |
| int count = 1; |
| int i; |
| PRCList *head; |
| |
| if (!names) { |
| return NULL; |
| } |
| |
| PORT_Assert(arena); |
| /* TODO: mark arena */ |
| current_name = names; |
| head = &(names->l); |
| while (current_name->l.next != head) { |
| current_name = CERT_GetNextGeneralName(current_name); |
| ++count; |
| } |
| current_name = CERT_GetNextGeneralName(current_name); |
| items = PORT_ArenaNewArray(arena, SECItem *, count + 1); |
| if (items == NULL) { |
| goto loser; |
| } |
| for (i = 0; i < count; i++) { |
| items[i] = CERT_EncodeGeneralName(current_name, (SECItem *)NULL, arena); |
| if (items[i] == NULL) { |
| goto loser; |
| } |
| current_name = CERT_GetNextGeneralName(current_name); |
| } |
| items[i] = NULL; |
| /* TODO: unmark arena */ |
| return items; |
| loser: |
| /* TODO: release arena to mark */ |
| return NULL; |
| } |
| |
| CERTGeneralName * |
| CERT_DecodeGeneralName(PLArenaPool *reqArena, SECItem *encodedName, |
| CERTGeneralName *genName) |
| { |
| const SEC_ASN1Template *template; |
| CERTGeneralNameType genNameType; |
| SECStatus rv = SECSuccess; |
| SECItem *newEncodedName; |
| |
| if (!reqArena) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return NULL; |
| } |
| /* make a copy for decoding so the data decoded with QuickDER doesn't |
| point to temporary memory */ |
| newEncodedName = SECITEM_ArenaDupItem(reqArena, encodedName); |
| if (!newEncodedName) { |
| return NULL; |
| } |
| /* TODO: mark arena */ |
| genNameType = (CERTGeneralNameType)((*(newEncodedName->data) & 0x0f) + 1); |
| if (genName == NULL) { |
| genName = CERT_NewGeneralName(reqArena, genNameType); |
| if (!genName) |
| goto loser; |
| } else { |
| genName->type = genNameType; |
| genName->l.prev = genName->l.next = &genName->l; |
| } |
| |
| switch (genNameType) { |
| case certURI: |
| template = CERT_URITemplate; |
| break; |
| case certRFC822Name: |
| template = CERT_RFC822NameTemplate; |
| break; |
| case certDNSName: |
| template = CERT_DNSNameTemplate; |
| break; |
| case certIPAddress: |
| template = CERT_IPAddressTemplate; |
| break; |
| case certOtherName: |
| template = CERTOtherNameTemplate; |
| break; |
| case certRegisterID: |
| template = CERT_RegisteredIDTemplate; |
| break; |
| case certEDIPartyName: |
| template = CERT_EDIPartyNameTemplate; |
| break; |
| case certX400Address: |
| template = CERT_X400AddressTemplate; |
| break; |
| case certDirectoryName: |
| template = CERT_DirectoryNameTemplate; |
| break; |
| default: |
| goto loser; |
| } |
| rv = SEC_QuickDERDecodeItem(reqArena, genName, template, newEncodedName); |
| if (rv != SECSuccess) |
| goto loser; |
| if (genNameType == certDirectoryName) { |
| rv = SEC_QuickDERDecodeItem(reqArena, &(genName->name.directoryName), |
| CERT_NameTemplate, |
| &(genName->derDirectoryName)); |
| if (rv != SECSuccess) |
| goto loser; |
| } |
| |
| /* TODO: unmark arena */ |
| return genName; |
| loser: |
| /* TODO: release arena to mark */ |
| return NULL; |
| } |
| |
| CERTGeneralName * |
| cert_DecodeGeneralNames(PLArenaPool *arena, SECItem **encodedGenName) |
| { |
| PRCList *head = NULL; |
| PRCList *tail = NULL; |
| CERTGeneralName *currentName = NULL; |
| |
| PORT_Assert(arena); |
| if (!encodedGenName || !arena) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return NULL; |
| } |
| /* TODO: mark arena */ |
| while (*encodedGenName != NULL) { |
| currentName = CERT_DecodeGeneralName(arena, *encodedGenName, NULL); |
| if (currentName == NULL) |
| break; |
| if (head == NULL) { |
| head = &(currentName->l); |
| tail = head; |
| } |
| currentName->l.next = head; |
| currentName->l.prev = tail; |
| tail = head->prev = tail->next = &(currentName->l); |
| encodedGenName++; |
| } |
| if (currentName) { |
| /* TODO: unmark arena */ |
| return CERT_GetNextGeneralName(currentName); |
| } |
| /* TODO: release arena to mark */ |
| return NULL; |
| } |
| |
| void |
| CERT_DestroyGeneralName(CERTGeneralName *name) |
| { |
| cert_DestroyGeneralNames(name); |
| } |
| |
| SECStatus |
| cert_DestroyGeneralNames(CERTGeneralName *name) |
| { |
| CERTGeneralName *first; |
| CERTGeneralName *next = NULL; |
| |
| first = name; |
| do { |
| next = CERT_GetNextGeneralName(name); |
| PORT_Free(name); |
| name = next; |
| } while (name != first); |
| return SECSuccess; |
| } |
| |
| static SECItem * |
| cert_EncodeNameConstraint(CERTNameConstraint *constraint, SECItem *dest, |
| PLArenaPool *arena) |
| { |
| PORT_Assert(arena); |
| if (dest == NULL) { |
| dest = PORT_ArenaZNew(arena, SECItem); |
| if (dest == NULL) { |
| return NULL; |
| } |
| } |
| CERT_EncodeGeneralName(&(constraint->name), &(constraint->DERName), arena); |
| |
| dest = |
| SEC_ASN1EncodeItem(arena, dest, constraint, CERTNameConstraintTemplate); |
| return dest; |
| } |
| |
| SECStatus |
| cert_EncodeNameConstraintSubTree(CERTNameConstraint *constraints, |
| PLArenaPool *arena, SECItem ***dest, |
| PRBool permited) |
| { |
| CERTNameConstraint *current_constraint = constraints; |
| SECItem **items = NULL; |
| int count = 0; |
| int i; |
| PRCList *head; |
| |
| PORT_Assert(arena); |
| /* TODO: mark arena */ |
| if (constraints != NULL) { |
| count = 1; |
| } |
| head = &constraints->l; |
| while (current_constraint->l.next != head) { |
| current_constraint = CERT_GetNextNameConstraint(current_constraint); |
| ++count; |
| } |
| current_constraint = CERT_GetNextNameConstraint(current_constraint); |
| items = PORT_ArenaZNewArray(arena, SECItem *, count + 1); |
| if (items == NULL) { |
| goto loser; |
| } |
| for (i = 0; i < count; i++) { |
| items[i] = cert_EncodeNameConstraint(current_constraint, |
| (SECItem *)NULL, arena); |
| if (items[i] == NULL) { |
| goto loser; |
| } |
| current_constraint = CERT_GetNextNameConstraint(current_constraint); |
| } |
| *dest = items; |
| if (*dest == NULL) { |
| goto loser; |
| } |
| /* TODO: unmark arena */ |
| return SECSuccess; |
| loser: |
| /* TODO: release arena to mark */ |
| return SECFailure; |
| } |
| |
| SECStatus |
| cert_EncodeNameConstraints(CERTNameConstraints *constraints, PLArenaPool *arena, |
| SECItem *dest) |
| { |
| SECStatus rv = SECSuccess; |
| |
| PORT_Assert(arena); |
| /* TODO: mark arena */ |
| if (constraints->permited != NULL) { |
| rv = cert_EncodeNameConstraintSubTree( |
| constraints->permited, arena, &constraints->DERPermited, PR_TRUE); |
| if (rv == SECFailure) { |
| goto loser; |
| } |
| } |
| if (constraints->excluded != NULL) { |
| rv = cert_EncodeNameConstraintSubTree( |
| constraints->excluded, arena, &constraints->DERExcluded, PR_FALSE); |
| if (rv == SECFailure) { |
| goto loser; |
| } |
| } |
| dest = SEC_ASN1EncodeItem(arena, dest, constraints, |
| CERTNameConstraintsTemplate); |
| if (dest == NULL) { |
| goto loser; |
| } |
| /* TODO: unmark arena */ |
| return SECSuccess; |
| loser: |
| /* TODO: release arena to mark */ |
| return SECFailure; |
| } |
| |
| CERTNameConstraint * |
| cert_DecodeNameConstraint(PLArenaPool *reqArena, SECItem *encodedConstraint) |
| { |
| CERTNameConstraint *constraint; |
| SECStatus rv = SECSuccess; |
| CERTGeneralName *temp; |
| SECItem *newEncodedConstraint; |
| |
| if (!reqArena) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return NULL; |
| } |
| newEncodedConstraint = SECITEM_ArenaDupItem(reqArena, encodedConstraint); |
| if (!newEncodedConstraint) { |
| return NULL; |
| } |
| /* TODO: mark arena */ |
| constraint = PORT_ArenaZNew(reqArena, CERTNameConstraint); |
| if (!constraint) |
| goto loser; |
| rv = SEC_QuickDERDecodeItem( |
| reqArena, constraint, CERTNameConstraintTemplate, newEncodedConstraint); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| temp = CERT_DecodeGeneralName(reqArena, &(constraint->DERName), |
| &(constraint->name)); |
| if (temp != &(constraint->name)) { |
| goto loser; |
| } |
| |
| /* ### sjlee: since the name constraint contains only one |
| * CERTGeneralName, the list within CERTGeneralName shouldn't |
| * point anywhere else. Otherwise, bad things will happen. |
| */ |
| constraint->name.l.prev = constraint->name.l.next = &(constraint->name.l); |
| /* TODO: unmark arena */ |
| return constraint; |
| loser: |
| /* TODO: release arena back to mark */ |
| return NULL; |
| } |
| |
| static CERTNameConstraint * |
| cert_DecodeNameConstraintSubTree(PLArenaPool *arena, SECItem **subTree, |
| PRBool permited) |
| { |
| CERTNameConstraint *current = NULL; |
| CERTNameConstraint *first = NULL; |
| CERTNameConstraint *last = NULL; |
| int i = 0; |
| |
| PORT_Assert(arena); |
| /* TODO: mark arena */ |
| while (subTree[i] != NULL) { |
| current = cert_DecodeNameConstraint(arena, subTree[i]); |
| if (current == NULL) { |
| goto loser; |
| } |
| if (first == NULL) { |
| first = current; |
| } else { |
| current->l.prev = &(last->l); |
| last->l.next = &(current->l); |
| } |
| last = current; |
| i++; |
| } |
| if (first && last) { |
| first->l.prev = &(last->l); |
| last->l.next = &(first->l); |
| } |
| /* TODO: unmark arena */ |
| return first; |
| loser: |
| /* TODO: release arena back to mark */ |
| return NULL; |
| } |
| |
| CERTNameConstraints * |
| cert_DecodeNameConstraints(PLArenaPool *reqArena, |
| const SECItem *encodedConstraints) |
| { |
| CERTNameConstraints *constraints; |
| SECStatus rv; |
| SECItem *newEncodedConstraints; |
| |
| if (!reqArena) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return NULL; |
| } |
| PORT_Assert(encodedConstraints); |
| newEncodedConstraints = SECITEM_ArenaDupItem(reqArena, encodedConstraints); |
| |
| /* TODO: mark arena */ |
| constraints = PORT_ArenaZNew(reqArena, CERTNameConstraints); |
| if (constraints == NULL) { |
| goto loser; |
| } |
| rv = SEC_QuickDERDecodeItem(reqArena, constraints, |
| CERTNameConstraintsTemplate, |
| newEncodedConstraints); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| if (constraints->DERPermited != NULL && |
| constraints->DERPermited[0] != NULL) { |
| constraints->permited = cert_DecodeNameConstraintSubTree( |
| reqArena, constraints->DERPermited, PR_TRUE); |
| if (constraints->permited == NULL) { |
| goto loser; |
| } |
| } |
| if (constraints->DERExcluded != NULL && |
| constraints->DERExcluded[0] != NULL) { |
| constraints->excluded = cert_DecodeNameConstraintSubTree( |
| reqArena, constraints->DERExcluded, PR_FALSE); |
| if (constraints->excluded == NULL) { |
| goto loser; |
| } |
| } |
| /* TODO: unmark arena */ |
| return constraints; |
| loser: |
| /* TODO: release arena back to mark */ |
| return NULL; |
| } |
| |
| /* Copy a chain of one or more general names to a destination chain. |
| ** Caller has allocated at least the first destination GeneralName struct. |
| ** Both source and destination chains are circular doubly-linked lists. |
| ** The first source struct is copied to the first destination struct. |
| ** If the source chain has more than one member, and the destination chain |
| ** has only one member, then this function allocates new structs for all but |
| ** the first copy from the arena and links them into the destination list. |
| ** If the destination struct is part of a list with more than one member, |
| ** then this function traverses both the source and destination lists, |
| ** copying each source struct to the corresponding dest struct. |
| ** In that case, the destination list MUST contain at least as many |
| ** structs as the source list or some dest entries will be overwritten. |
| */ |
| SECStatus |
| CERT_CopyGeneralName(PLArenaPool *arena, CERTGeneralName *dest, |
| CERTGeneralName *src) |
| { |
| SECStatus rv; |
| CERTGeneralName *destHead = dest; |
| CERTGeneralName *srcHead = src; |
| |
| PORT_Assert(dest != NULL); |
| if (!dest) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return SECFailure; |
| } |
| /* TODO: mark arena */ |
| do { |
| rv = cert_CopyOneGeneralName(arena, dest, src); |
| if (rv != SECSuccess) |
| goto loser; |
| src = CERT_GetNextGeneralName(src); |
| /* if there is only one general name, we shouldn't do this */ |
| if (src != srcHead) { |
| if (dest->l.next == &destHead->l) { |
| CERTGeneralName *temp; |
| temp = CERT_NewGeneralName(arena, (CERTGeneralNameType)0); |
| if (!temp) |
| goto loser; |
| temp->l.next = &destHead->l; |
| temp->l.prev = &dest->l; |
| destHead->l.prev = &temp->l; |
| dest->l.next = &temp->l; |
| dest = temp; |
| } else { |
| dest = CERT_GetNextGeneralName(dest); |
| } |
| } |
| } while (src != srcHead && rv == SECSuccess); |
| /* TODO: unmark arena */ |
| return rv; |
| loser: |
| /* TODO: release back to mark */ |
| return SECFailure; |
| } |
| |
| CERTGeneralNameList * |
| CERT_DupGeneralNameList(CERTGeneralNameList *list) |
| { |
| if (list != NULL) { |
| PZ_Lock(list->lock); |
| list->refCount++; |
| PZ_Unlock(list->lock); |
| } |
| return list; |
| } |
| |
| /* Allocate space and copy CERTNameConstraint from src to dest */ |
| CERTNameConstraint * |
| CERT_CopyNameConstraint(PLArenaPool *arena, CERTNameConstraint *dest, |
| CERTNameConstraint *src) |
| { |
| SECStatus rv; |
| |
| /* TODO: mark arena */ |
| if (dest == NULL) { |
| dest = PORT_ArenaZNew(arena, CERTNameConstraint); |
| if (!dest) |
| goto loser; |
| /* mark that it is not linked */ |
| dest->name.l.prev = dest->name.l.next = &(dest->name.l); |
| } |
| rv = CERT_CopyGeneralName(arena, &dest->name, &src->name); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| rv = SECITEM_CopyItem(arena, &dest->DERName, &src->DERName); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| rv = SECITEM_CopyItem(arena, &dest->min, &src->min); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| rv = SECITEM_CopyItem(arena, &dest->max, &src->max); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| dest->l.prev = dest->l.next = &dest->l; |
| /* TODO: unmark arena */ |
| return dest; |
| loser: |
| /* TODO: release arena to mark */ |
| return NULL; |
| } |
| |
| CERTGeneralName * |
| cert_CombineNamesLists(CERTGeneralName *list1, CERTGeneralName *list2) |
| { |
| PRCList *begin1; |
| PRCList *begin2; |
| PRCList *end1; |
| PRCList *end2; |
| |
| if (list1 == NULL) { |
| return list2; |
| } else if (list2 == NULL) { |
| return list1; |
| } else { |
| begin1 = &list1->l; |
| begin2 = &list2->l; |
| end1 = list1->l.prev; |
| end2 = list2->l.prev; |
| end1->next = begin2; |
| end2->next = begin1; |
| begin1->prev = end2; |
| begin2->prev = end1; |
| return list1; |
| } |
| } |
| |
| CERTNameConstraint * |
| cert_CombineConstraintsLists(CERTNameConstraint *list1, |
| CERTNameConstraint *list2) |
| { |
| PRCList *begin1; |
| PRCList *begin2; |
| PRCList *end1; |
| PRCList *end2; |
| |
| if (list1 == NULL) { |
| return list2; |
| } else if (list2 == NULL) { |
| return list1; |
| } else { |
| begin1 = &list1->l; |
| begin2 = &list2->l; |
| end1 = list1->l.prev; |
| end2 = list2->l.prev; |
| end1->next = begin2; |
| end2->next = begin1; |
| begin1->prev = end2; |
| begin2->prev = end1; |
| return list1; |
| } |
| } |
| |
| /* Add a CERTNameConstraint to the CERTNameConstraint list */ |
| CERTNameConstraint * |
| CERT_AddNameConstraint(CERTNameConstraint *list, CERTNameConstraint *constraint) |
| { |
| PORT_Assert(constraint != NULL); |
| constraint->l.next = constraint->l.prev = &constraint->l; |
| list = cert_CombineConstraintsLists(list, constraint); |
| return list; |
| } |
| |
| SECStatus |
| CERT_GetNameConstraintByType(CERTNameConstraint *constraints, |
| CERTGeneralNameType type, |
| CERTNameConstraint **returnList, |
| PLArenaPool *arena) |
| { |
| CERTNameConstraint *current = NULL; |
| void *mark = NULL; |
| |
| *returnList = NULL; |
| if (!constraints) |
| return SECSuccess; |
| |
| mark = PORT_ArenaMark(arena); |
| |
| current = constraints; |
| do { |
| PORT_Assert(current->name.type); |
| if (current->name.type == type) { |
| CERTNameConstraint *temp; |
| temp = CERT_CopyNameConstraint(arena, NULL, current); |
| if (temp == NULL) |
| goto loser; |
| *returnList = CERT_AddNameConstraint(*returnList, temp); |
| } |
| current = CERT_GetNextNameConstraint(current); |
| } while (current != constraints); |
| PORT_ArenaUnmark(arena, mark); |
| return SECSuccess; |
| |
| loser: |
| PORT_ArenaRelease(arena, mark); |
| return SECFailure; |
| } |
| |
| void * |
| CERT_GetGeneralNameByType(CERTGeneralName *genNames, CERTGeneralNameType type, |
| PRBool derFormat) |
| { |
| CERTGeneralName *current; |
| |
| if (!genNames) |
| return NULL; |
| current = genNames; |
| |
| do { |
| if (current->type == type) { |
| switch (type) { |
| case certDNSName: |
| case certEDIPartyName: |
| case certIPAddress: |
| case certRegisterID: |
| case certRFC822Name: |
| case certX400Address: |
| case certURI: |
| return (void *)¤t->name.other; /* SECItem * */ |
| |
| case certOtherName: |
| return (void *)¤t->name.OthName; /* OthName * */ |
| |
| case certDirectoryName: |
| return derFormat |
| ? (void *)¤t |
| ->derDirectoryName /* SECItem * */ |
| : (void *)¤t->name |
| .directoryName; /* CERTName * */ |
| } |
| PORT_Assert(0); |
| return NULL; |
| } |
| current = CERT_GetNextGeneralName(current); |
| } while (current != genNames); |
| return NULL; |
| } |
| |
| int |
| CERT_GetNamesLength(CERTGeneralName *names) |
| { |
| int length = 0; |
| CERTGeneralName *first; |
| |
| first = names; |
| if (names != NULL) { |
| do { |
| length++; |
| names = CERT_GetNextGeneralName(names); |
| } while (names != first); |
| } |
| return length; |
| } |
| |
| /* Creates new GeneralNames for any email addresses found in the |
| ** input DN, and links them onto the list for the DN. |
| */ |
| SECStatus |
| cert_ExtractDNEmailAddrs(CERTGeneralName *name, PLArenaPool *arena) |
| { |
| CERTGeneralName *nameList = NULL; |
| const CERTRDN **nRDNs = (const CERTRDN **)(name->name.directoryName.rdns); |
| SECStatus rv = SECSuccess; |
| |
| PORT_Assert(name->type == certDirectoryName); |
| if (name->type != certDirectoryName) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return SECFailure; |
| } |
| /* TODO: mark arena */ |
| while (nRDNs && *nRDNs) { /* loop over RDNs */ |
| const CERTRDN *nRDN = *nRDNs++; |
| CERTAVA **nAVAs = nRDN->avas; |
| while (nAVAs && *nAVAs) { /* loop over AVAs */ |
| int tag; |
| CERTAVA *nAVA = *nAVAs++; |
| tag = CERT_GetAVATag(nAVA); |
| if (tag == SEC_OID_PKCS9_EMAIL_ADDRESS || |
| tag == SEC_OID_RFC1274_MAIL) { /* email AVA */ |
| CERTGeneralName *newName = NULL; |
| SECItem *avaValue = CERT_DecodeAVAValue(&nAVA->value); |
| if (!avaValue) |
| goto loser; |
| rv = SECFailure; |
| newName = CERT_NewGeneralName(arena, certRFC822Name); |
| if (newName) { |
| rv = |
| SECITEM_CopyItem(arena, &newName->name.other, avaValue); |
| } |
| SECITEM_FreeItem(avaValue, PR_TRUE); |
| if (rv != SECSuccess) |
| goto loser; |
| nameList = cert_CombineNamesLists(nameList, newName); |
| } /* handle one email AVA */ |
| } /* loop over AVAs */ |
| } /* loop over RDNs */ |
| /* combine new names with old one. */ |
| (void)cert_CombineNamesLists(name, nameList); |
| /* TODO: unmark arena */ |
| return SECSuccess; |
| |
| loser: |
| /* TODO: release arena back to mark */ |
| return SECFailure; |
| } |
| |
| /* Extract all names except Subject Common Name from a cert |
| ** in preparation for a name constraints test. |
| */ |
| CERTGeneralName * |
| CERT_GetCertificateNames(CERTCertificate *cert, PLArenaPool *arena) |
| { |
| return CERT_GetConstrainedCertificateNames(cert, arena, PR_FALSE); |
| } |
| |
| /* This function is called by CERT_VerifyCertChain to extract all |
| ** names from a cert in preparation for a name constraints test. |
| */ |
| CERTGeneralName * |
| CERT_GetConstrainedCertificateNames(const CERTCertificate *cert, |
| PLArenaPool *arena, |
| PRBool includeSubjectCommonName) |
| { |
| CERTGeneralName *DN; |
| CERTGeneralName *SAN; |
| PRUint32 numDNSNames = 0; |
| SECStatus rv; |
| |
| if (!arena) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return NULL; |
| } |
| /* TODO: mark arena */ |
| DN = CERT_NewGeneralName(arena, certDirectoryName); |
| if (DN == NULL) { |
| goto loser; |
| } |
| rv = CERT_CopyName(arena, &DN->name.directoryName, &cert->subject); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| rv = SECITEM_CopyItem(arena, &DN->derDirectoryName, &cert->derSubject); |
| if (rv != SECSuccess) { |
| goto loser; |
| } |
| /* Extract email addresses from DN, construct CERTGeneralName structs |
| ** for them, add them to the name list |
| */ |
| rv = cert_ExtractDNEmailAddrs(DN, arena); |
| if (rv != SECSuccess) |
| goto loser; |
| |
| /* Now extract any GeneralNames from the subject name names extension. */ |
| SAN = cert_GetSubjectAltNameList(cert, arena); |
| if (SAN) { |
| numDNSNames = cert_CountDNSPatterns(SAN); |
| DN = cert_CombineNamesLists(DN, SAN); |
| } |
| if (!numDNSNames && includeSubjectCommonName) { |
| char *cn = CERT_GetCommonName(&cert->subject); |
| if (cn) { |
| CERTGeneralName *CN = CERT_NewGeneralName(arena, certDNSName); |
| if (CN) { |
| SECItem cnItem = { siBuffer, NULL, 0 }; |
| cnItem.data = (unsigned char *)cn; |
| cnItem.len = strlen(cn); |
| rv = SECITEM_CopyItem(arena, &CN->name.other, &cnItem); |
| if (rv == SECSuccess) { |
| DN = cert_CombineNamesLists(DN, CN); |
| } |
| } |
| PORT_Free(cn); |
| } |
| } |
| if (rv == SECSuccess) { |
| /* TODO: unmark arena */ |
| return DN; |
| } |
| loser: |
| /* TODO: release arena to mark */ |
| return NULL; |
| } |
| |
| /* Returns SECSuccess if name matches constraint per RFC 3280 rules for |
| ** URI name constraints. SECFailure otherwise. |
| ** If the constraint begins with a dot, it is a domain name, otherwise |
| ** It is a host name. Examples: |
| ** Constraint Name Result |
| ** ------------ --------------- -------- |
| ** foo.bar.com foo.bar.com matches |
| ** foo.bar.com FoO.bAr.CoM matches |
| ** foo.bar.com www.foo.bar.com no match |
| ** foo.bar.com nofoo.bar.com no match |
| ** .foo.bar.com www.foo.bar.com matches |
| ** .foo.bar.com nofoo.bar.com no match |
| ** .foo.bar.com foo.bar.com no match |
| ** .foo.bar.com www..foo.bar.com no match |
| */ |
| static SECStatus |
| compareURIN2C(const SECItem *name, const SECItem *constraint) |
| { |
| int offset; |
| /* The spec is silent on intepreting zero-length constraints. |
| ** We interpret them as matching no URI names. |
| */ |
| if (!constraint->len) |
| return SECFailure; |
| if (constraint->data[0] != '.') { |
| /* constraint is a host name. */ |
| if (name->len != constraint->len || |
| PL_strncasecmp((char *)name->data, (char *)constraint->data, |
| constraint->len)) |
| return SECFailure; |
| return SECSuccess; |
| } |
| /* constraint is a domain name. */ |
| if (name->len < constraint->len) |
| return SECFailure; |
| offset = name->len - constraint->len; |
| if (PL_strncasecmp((char *)(name->data + offset), (char *)constraint->data, |
| constraint->len)) |
| return SECFailure; |
| if (!offset || |
| (name->data[offset - 1] == '.') + (constraint->data[0] == '.') == 1) |
| return SECSuccess; |
| return SECFailure; |
| } |
| |
| /* for DNSname constraints, RFC 3280 says, (section 4.2.1.11, page 38) |
| ** |
| ** DNS name restrictions are expressed as foo.bar.com. Any DNS name |
| ** that can be constructed by simply adding to the left hand side of the |
| ** name satisfies the name constraint. For example, www.foo.bar.com |
| ** would satisfy the constraint but foo1.bar.com would not. |
| ** |
| ** But NIST's PKITS test suite requires that the constraint be treated |
| ** as a domain name, and requires that any name added to the left hand |
| ** side end in a dot ".". Sensible, but not strictly following the RFC. |
| ** |
| ** Constraint Name RFC 3280 NIST PKITS |
| ** ------------ --------------- -------- ---------- |
| ** foo.bar.com foo.bar.com matches matches |
| ** foo.bar.com FoO.bAr.CoM matches matches |
| ** foo.bar.com www.foo.bar.com matches matches |
| ** foo.bar.com nofoo.bar.com MATCHES NO MATCH |
| ** .foo.bar.com www.foo.bar.com matches matches? disallowed? |
| ** .foo.bar.com foo.bar.com no match no match |
| ** .foo.bar.com www..foo.bar.com matches probably not |
| ** |
| ** We will try to conform to NIST's PKITS tests, and the unstated |
| ** rules they imply. |
| */ |
| static SECStatus |
| compareDNSN2C(const SECItem *name, const SECItem *constraint) |
| { |
| int offset; |
| /* The spec is silent on intepreting zero-length constraints. |
| ** We interpret them as matching all DNSnames. |
| */ |
| if (!constraint->len) |
| return SECSuccess; |
| if (name->len < constraint->len) |
| return SECFailure; |
| offset = name->len - constraint->len; |
| if (PL_strncasecmp((char *)(name->data + offset), (char *)constraint->data, |
| constraint->len)) |
| return SECFailure; |
| if (!offset || |
| (name->data[offset - 1] == '.') + (constraint->data[0] == '.') == 1) |
| return SECSuccess; |
| return SECFailure; |
| } |
| |
| /* Returns SECSuccess if name matches constraint per RFC 3280 rules for |
| ** internet email addresses. SECFailure otherwise. |
| ** If constraint contains a '@' then the two strings much match exactly. |
| ** Else if constraint starts with a '.'. then it must match the right-most |
| ** substring of the name, |
| ** else constraint string must match entire name after the name's '@'. |
| ** Empty constraint string matches all names. All comparisons case insensitive. |
| */ |
| static SECStatus |
| compareRFC822N2C(const SECItem *name, const SECItem *constraint) |
| { |
| int offset; |
| if (!constraint->len) |
| return SECSuccess; |
| if (name->len < constraint->len) |
| return SECFailure; |
| if (constraint->len == 1 && constraint->data[0] == '.') |
| return SECSuccess; |
| for (offset = constraint->len - 1; offset >= 0; --offset) { |
| if (constraint->data[offset] == '@') { |
| return (name->len == constraint->len && |
| !PL_strncasecmp((char *)name->data, |
| (char *)constraint->data, constraint->len)) |
| ? SECSuccess |
| : SECFailure; |
| } |
| } |
| offset = name->len - constraint->len; |
| if (PL_strncasecmp((char *)(name->data + offset), (char *)constraint->data, |
| constraint->len)) |
| return SECFailure; |
| if (constraint->data[0] == '.') |
| return SECSuccess; |
| if (offset > 0 && name->data[offset - 1] == '@') |
| return SECSuccess; |
| return SECFailure; |
| } |
| |
| /* name contains either a 4 byte IPv4 address or a 16 byte IPv6 address. |
| ** constraint contains an address of the same length, and a subnet mask |
| ** of the same length. Compare name's address to the constraint's |
| ** address, subject to the mask. |
| ** Return SECSuccess if they match, SECFailure if they don't. |
| */ |
| static SECStatus |
| compareIPaddrN2C(const SECItem *name, const SECItem *constraint) |
| { |
| int i; |
| if (name->len == 4 && constraint->len == 8) { /* ipv4 addr */ |
| for (i = 0; i < 4; i++) { |
| if ((name->data[i] ^ constraint->data[i]) & constraint->data[i + 4]) |
| goto loser; |
| } |
| return SECSuccess; |
| } |
| if (name->len == 16 && constraint->len == 32) { /* ipv6 addr */ |
| for (i = 0; i < 16; i++) { |
| if ((name->data[i] ^ constraint->data[i]) & |
| constraint->data[i + 16]) |
| goto loser; |
| } |
| return SECSuccess; |
| } |
| loser: |
| return SECFailure; |
| } |
| |
| /* start with a SECItem that points to a URI. Parse it lookingg for |
| ** a hostname. Modify item->data and item->len to define the hostname, |
| ** but do not modify and data at item->data. |
| ** If anything goes wrong, the contents of *item are undefined. |
| */ |
| static SECStatus |
| parseUriHostname(SECItem *item) |
| { |
| int i; |
| PRBool found = PR_FALSE; |
| for (i = 0; (unsigned)(i + 2) < item->len; ++i) { |
| if (item->data[i] == ':' && item->data[i + 1] == '/' && |
| item->data[i + 2] == '/') { |
| i += 3; |
| item->data += i; |
| item->len -= i; |
| found = PR_TRUE; |
| break; |
| } |
| } |
| if (!found) |
| return SECFailure; |
| /* now look for a '/', which is an upper bound in the end of the name */ |
| for (i = 0; (unsigned)i < item->len; ++i) { |
| if (item->data[i] == '/') { |
| item->len = i; |
| break; |
| } |
| } |
| /* now look for a ':', which marks the end of the name */ |
| for (i = item->len; --i >= 0;) { |
| if (item->data[i] == ':') { |
| item->len = i; |
| break; |
| } |
| } |
| /* now look for an '@', which marks the beginning of the hostname */ |
| for (i = 0; (unsigned)i < item->len; ++i) { |
| if (item->data[i] == '@') { |
| ++i; |
| item->data += i; |
| item->len -= i; |
| break; |
| } |
| } |
| return item->len ? SECSuccess : SECFailure; |
| } |
| |
| /* This function takes one name, and a list of constraints. |
| ** It searches the constraints looking for a match. |
| ** It returns SECSuccess if the name satisfies the constraints, i.e., |
| ** if excluded, then the name does not match any constraint, |
| ** if permitted, then the name matches at least one constraint. |
| ** It returns SECFailure if the name fails to satisfy the constraints, |
| ** or if some code fails (e.g. out of memory, or invalid constraint) |
| */ |
| SECStatus |
| cert_CompareNameWithConstraints(const CERTGeneralName *name, |
| const CERTNameConstraint *constraints, |
| PRBool excluded) |
| { |
| SECStatus rv = SECSuccess; |
| SECStatus matched = SECFailure; |
| const CERTNameConstraint *current; |
| |
| PORT_Assert(constraints); /* caller should not call with NULL */ |
| if (!constraints) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return SECFailure; |
| } |
| |
| current = constraints; |
| do { |
| rv = SECSuccess; |
| matched = SECFailure; |
| PORT_Assert(name->type == current->name.type); |
| switch (name->type) { |
| |
| case certDNSName: |
| matched = |
| compareDNSN2C(&name->name.other, ¤t->name.name.other); |
| break; |
| |
| case certRFC822Name: |
| matched = compareRFC822N2C(&name->name.other, |
| ¤t->name.name.other); |
| break; |
| |
| case certURI: { |
| /* make a modifiable copy of the URI SECItem. */ |
| SECItem uri = name->name.other; |
| /* find the hostname in the URI */ |
| rv = parseUriHostname(&uri); |
| if (rv == SECSuccess) { |
| /* does our hostname meet the constraint? */ |
| matched = compareURIN2C(&uri, ¤t->name.name.other); |
| } |
| } break; |
| |
| case certDirectoryName: |
| /* Determine if the constraint directory name is a "prefix" |
| ** for the directory name being tested. |
| */ |
| { |
| /* status defaults to SECEqual, so that a constraint with |
| ** no AVAs will be a wildcard, matching all directory names. |
| */ |
| SECComparison status = SECEqual; |
| const CERTRDN **cRDNs = |
| (const CERTRDN **)current->name.name.directoryName.rdns; |
| const CERTRDN **nRDNs = |
| (const CERTRDN **)name->name.directoryName.rdns; |
| while (cRDNs && *cRDNs && nRDNs && *nRDNs) { |
| /* loop over name RDNs and constraint RDNs in lock step |
| */ |
| const CERTRDN *cRDN = *cRDNs++; |
| const CERTRDN *nRDN = *nRDNs++; |
| CERTAVA **cAVAs = cRDN->avas; |
| while (cAVAs && |
| *cAVAs) { /* loop over constraint AVAs */ |
| CERTAVA *cAVA = *cAVAs++; |
| CERTAVA **nAVAs = nRDN->avas; |
| while (nAVAs && *nAVAs) { /* loop over name AVAs */ |
| CERTAVA *nAVA = *nAVAs++; |
| status = CERT_CompareAVA(cAVA, nAVA); |
| if (status == SECEqual) |
| break; |
| } /* loop over name AVAs */ |
| if (status != SECEqual) |
| break; |
| } /* loop over constraint AVAs */ |
| if (status != SECEqual) |
| break; |
| } /* loop over name RDNs and constraint RDNs */ |
| matched = (status == SECEqual) ? SECSuccess : SECFailure; |
| break; |
| } |
| |
| case certIPAddress: /* type 8 */ |
| matched = compareIPaddrN2C(&name->name.other, |
| ¤t->name.name.other); |
| break; |
| |
| /* NSS does not know how to compare these "Other" type names with |
| ** their respective constraints. But it does know how to tell |
| ** if the constraint applies to the type of name (by comparing |
| ** the constraint OID to the name OID). NSS makes no use of "Other" |
| ** type names at all, so NSS errs on the side of leniency for these |
| ** types, provided that their OIDs match. So, when an "Other" |
| ** name constraint appears in an excluded subtree, it never causes |
| ** a name to fail. When an "Other" name constraint appears in a |
| ** permitted subtree, AND the constraint's OID matches the name's |
| ** OID, then name is treated as if it matches the constraint. |
| */ |
| case certOtherName: /* type 1 */ |
| matched = |
| (!excluded && name->type == current->name.type && |
| SECITEM_ItemsAreEqual(&name->name.OthName.oid, |
| ¤t->name.name.OthName.oid)) |
| ? SECSuccess |
| : SECFailure; |
| break; |
| |
| /* NSS does not know how to compare these types of names with their |
| ** respective constraints. But NSS makes no use of these types of |
| ** names at all, so it errs on the side of leniency for these types. |
| ** Constraints for these types of names never cause the name to |
| ** fail the constraints test. NSS behaves as if the name matched |
| ** for permitted constraints, and did not match for excluded ones. |
| */ |
| case certX400Address: /* type 4 */ |
| case certEDIPartyName: /* type 6 */ |
| case certRegisterID: /* type 9 */ |
| matched = excluded ? SECFailure : SECSuccess; |
| break; |
| |
| default: /* non-standard types are not supported */ |
| rv = SECFailure; |
| break; |
| } |
| if (matched == SECSuccess || rv != SECSuccess) |
| break; |
| current = CERT_GetNextNameConstraint((CERTNameConstraint *)current); |
| } while (current != constraints); |
| if (rv == SECSuccess) { |
| if (matched == SECSuccess) |
| rv = excluded ? SECFailure : SECSuccess; |
| else |
| rv = excluded ? SECSuccess : SECFailure; |
| return rv; |
| } |
| |
| return SECFailure; |
| } |
| |
| /* Add and link a CERTGeneralName to a CERTNameConstraint list. Most |
| ** likely the CERTNameConstraint passed in is either the permitted |
| ** list or the excluded list of a CERTNameConstraints. |
| */ |
| SECStatus |
| CERT_AddNameConstraintByGeneralName(PLArenaPool *arena, |
| CERTNameConstraint **constraints, |
| CERTGeneralName *name) |
| { |
| SECStatus rv; |
| CERTNameConstraint *current = NULL; |
| CERTNameConstraint *first = *constraints; |
| void *mark = NULL; |
| |
| mark = PORT_ArenaMark(arena); |
| |
| current = PORT_ArenaZNew(arena, CERTNameConstraint); |
| if (current == NULL) { |
| rv = SECFailure; |
| goto done; |
| } |
| |
| rv = cert_CopyOneGeneralName(arena, ¤t->name, name); |
| if (rv != SECSuccess) { |
| goto done; |
| } |
| |
| current->name.l.prev = current->name.l.next = &(current->name.l); |
| |
| if (first == NULL) { |
| *constraints = current; |
| PR_INIT_CLIST(¤t->l); |
| } else { |
| PR_INSERT_BEFORE(¤t->l, &first->l); |
| } |
| |
| done: |
| if (rv == SECFailure) { |
| PORT_ArenaRelease(arena, mark); |
| } else { |
| PORT_ArenaUnmark(arena, mark); |
| } |
| return rv; |
| } |
| |
| /* |
| * Here we define a list of name constraints to be imposed on |
| * certain certificates, most importantly root certificates. |
| * |
| * Each entry in the name constraints list is constructed with this |
| * macro. An entry contains two SECItems, which have names in |
| * specific forms to make the macro work: |
| * |
| * * ${CA}_SUBJECT_DN - The subject DN for which the constraints |
| * should be applied |
| * * ${CA}_NAME_CONSTRAINTS - The name constraints extension |
| * |
| * Entities subject to name constraints are identified by subject name |
| * so that we can cover all certificates for that entity, including, e.g., |
| * cross-certificates. We use subject rather than public key because |
| * calling methods often have easy access to that field (vs., say, a key ID), |
| * and in practice, subject names and public keys are usually in one-to-one |
| * correspondence anyway. |
| * |
| */ |
| |
| #define STRING_TO_SECITEM(str) \ |
| { \ |
| siBuffer, (unsigned char *)str, sizeof(str) - 1 \ |
| } |
| |
| #define NAME_CONSTRAINTS_ENTRY(CA) \ |
| { \ |
| STRING_TO_SECITEM(CA##_SUBJECT_DN) \ |
| , \ |
| STRING_TO_SECITEM(CA##_NAME_CONSTRAINTS) \ |
| } |
| |
| /* clang-format off */ |
| |
| /* Agence Nationale de la Securite des Systemes d'Information (ANSSI) */ |
| |
| #define ANSSI_SUBJECT_DN \ |
| "\x30\x81\x85" \ |
| "\x31\x0B\x30\x09\x06\x03\x55\x04\x06\x13\x02" "FR" /* C */ \ |
| "\x31\x0F\x30\x0D\x06\x03\x55\x04\x08\x13\x06" "France" /* ST */ \ |
| "\x31\x0E\x30\x0C\x06\x03\x55\x04\x07\x13\x05" "Paris" /* L */ \ |
| "\x31\x10\x30\x0E\x06\x03\x55\x04\x0A\x13\x07" "PM/SGDN" /* O */ \ |
| "\x31\x0E\x30\x0C\x06\x03\x55\x04\x0B\x13\x05" "DCSSI" /* OU */ \ |
| "\x31\x0E\x30\x0C\x06\x03\x55\x04\x03\x13\x05" "IGC/A" /* CN */ \ |
| "\x31\x23\x30\x21\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x09\x01" \ |
| "\x16\x14" "igca@sgdn.pm.gouv.fr" /* emailAddress */ \ |
| |
| #define ANSSI_NAME_CONSTRAINTS \ |
| "\x30\x5D\xA0\x5B" \ |
| "\x30\x05\x82\x03" ".fr" \ |
| "\x30\x05\x82\x03" ".gp" \ |
| "\x30\x05\x82\x03" ".gf" \ |
| "\x30\x05\x82\x03" ".mq" \ |
| "\x30\x05\x82\x03" ".re" \ |
| "\x30\x05\x82\x03" ".yt" \ |
| "\x30\x05\x82\x03" ".pm" \ |
| "\x30\x05\x82\x03" ".bl" \ |
| "\x30\x05\x82\x03" ".mf" \ |
| "\x30\x05\x82\x03" ".wf" \ |
| "\x30\x05\x82\x03" ".pf" \ |
| "\x30\x05\x82\x03" ".nc" \ |
| "\x30\x05\x82\x03" ".tf" |
| |
| /* TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 */ |
| |
| #define TUBITAK1_SUBJECT_DN \ |
| "\x30\x81\xd2" \ |
| "\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02" \ |
| /* C */ "TR" \ |
| "\x31\x18\x30\x16\x06\x03\x55\x04\x07\x13\x0f" \ |
| /* L */ "Gebze - Kocaeli" \ |
| "\x31\x42\x30\x40\x06\x03\x55\x04\x0a\x13\x39" \ |
| /* O */ "Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK" \ |
| "\x31\x2d\x30\x2b\x06\x03\x55\x04\x0b\x13\x24" \ |
| /* OU */ "Kamu Sertifikasyon Merkezi - Kamu SM" \ |
| "\x31\x36\x30\x34\x06\x03\x55\x04\x03\x13\x2d" \ |
| /* CN */ "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1" |
| |
| #define TUBITAK1_NAME_CONSTRAINTS \ |
| "\x30\x65\xa0\x63" \ |
| "\x30\x09\x82\x07" ".gov.tr" \ |
| "\x30\x09\x82\x07" ".k12.tr" \ |
| "\x30\x09\x82\x07" ".pol.tr" \ |
| "\x30\x09\x82\x07" ".mil.tr" \ |
| "\x30\x09\x82\x07" ".tsk.tr" \ |
| "\x30\x09\x82\x07" ".kep.tr" \ |
| "\x30\x09\x82\x07" ".bel.tr" \ |
| "\x30\x09\x82\x07" ".edu.tr" \ |
| "\x30\x09\x82\x07" ".org.tr" |
| |
| /* clang-format on */ |
| |
| static const SECItem builtInNameConstraints[][2] = { |
| NAME_CONSTRAINTS_ENTRY(ANSSI), |
| NAME_CONSTRAINTS_ENTRY(TUBITAK1) |
| }; |
| |
| SECStatus |
| CERT_GetImposedNameConstraints(const SECItem *derSubject, SECItem *extensions) |
| { |
| size_t i; |
| |
| if (!extensions) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return SECFailure; |
| } |
| |
| for (i = 0; i < PR_ARRAY_SIZE(builtInNameConstraints); ++i) { |
| if (SECITEM_ItemsAreEqual(derSubject, &builtInNameConstraints[i][0])) { |
| return SECITEM_CopyItem(NULL, extensions, |
| &builtInNameConstraints[i][1]); |
| } |
| } |
| |
| PORT_SetError(SEC_ERROR_EXTENSION_NOT_FOUND); |
| return SECFailure; |
| } |
| |
| /* |
| * Extract the name constraints extension from the CA cert. |
| * If the certificate contains no name constraints extension, but |
| * CERT_GetImposedNameConstraints returns a name constraints extension |
| * for the subject of the certificate, then that extension will be returned. |
| */ |
| SECStatus |
| CERT_FindNameConstraintsExten(PLArenaPool *arena, CERTCertificate *cert, |
| CERTNameConstraints **constraints) |
| { |
| SECStatus rv = SECSuccess; |
| SECItem constraintsExtension; |
| void *mark = NULL; |
| |
| *constraints = NULL; |
| |
| rv = CERT_FindCertExtension(cert, SEC_OID_X509_NAME_CONSTRAINTS, |
| &constraintsExtension); |
| if (rv != SECSuccess) { |
| if (PORT_GetError() != SEC_ERROR_EXTENSION_NOT_FOUND) { |
| return rv; |
| } |
| rv = CERT_GetImposedNameConstraints(&cert->derSubject, |
| &constraintsExtension); |
| if (rv != SECSuccess) { |
| if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND) { |
| return SECSuccess; |
| } |
| return rv; |
| } |
| } |
| |
| mark = PORT_ArenaMark(arena); |
| |
| *constraints = cert_DecodeNameConstraints(arena, &constraintsExtension); |
| if (*constraints == NULL) { /* decode failed */ |
| rv = SECFailure; |
| } |
| PORT_Free(constraintsExtension.data); |
| |
| if (rv == SECFailure) { |
| PORT_ArenaRelease(arena, mark); |
| } else { |
| PORT_ArenaUnmark(arena, mark); |
| } |
| |
| return rv; |
| } |
| |
| /* Verify name against all the constraints relevant to that type of |
| ** the name. |
| */ |
| SECStatus |
| CERT_CheckNameSpace(PLArenaPool *arena, const CERTNameConstraints *constraints, |
| const CERTGeneralName *currentName) |
| { |
| CERTNameConstraint *matchingConstraints; |
| SECStatus rv = SECSuccess; |
| |
| if (constraints->excluded != NULL) { |
| rv = CERT_GetNameConstraintByType(constraints->excluded, |
| currentName->type, |
| &matchingConstraints, arena); |
| if (rv == SECSuccess && matchingConstraints != NULL) { |
| rv = cert_CompareNameWithConstraints(currentName, |
| matchingConstraints, PR_TRUE); |
| } |
| if (rv != SECSuccess) { |
| return (rv); |
| } |
| } |
| |
| if (constraints->permited != NULL) { |
| rv = CERT_GetNameConstraintByType(constraints->permited, |
| currentName->type, |
| &matchingConstraints, arena); |
| if (rv == SECSuccess && matchingConstraints != NULL) { |
| rv = cert_CompareNameWithConstraints(currentName, |
| matchingConstraints, PR_FALSE); |
| } |
| if (rv != SECSuccess) { |
| return (rv); |
| } |
| } |
| |
| return (SECSuccess); |
| } |
| |
| /* Extract the name constraints extension from the CA cert. |
| ** Test each and every name in namesList against all the constraints |
| ** relevant to that type of name. |
| ** Returns NULL in pBadCert for success, if all names are acceptable. |
| ** If some name is not acceptable, returns a pointer to the cert that |
| ** contained that name. |
| */ |
| SECStatus |
| CERT_CompareNameSpace(CERTCertificate *cert, CERTGeneralName *namesList, |
| CERTCertificate **certsList, PLArenaPool *reqArena, |
| CERTCertificate **pBadCert) |
| { |
| SECStatus rv = SECSuccess; |
| CERTNameConstraints *constraints; |
| CERTGeneralName *currentName; |
| int count = 0; |
| CERTCertificate *badCert = NULL; |
| |
| /* If no names to check, then no names can be bad. */ |
| if (!namesList) |
| goto done; |
| rv = CERT_FindNameConstraintsExten(reqArena, cert, &constraints); |
| if (rv != SECSuccess) { |
| count = -1; |
| goto done; |
| } |
| |
| currentName = namesList; |
| do { |
| if (constraints) { |
| rv = CERT_CheckNameSpace(reqArena, constraints, currentName); |
| if (rv != SECSuccess) { |
| break; |
| } |
| } |
| currentName = CERT_GetNextGeneralName(currentName); |
| count++; |
| } while (currentName != namesList); |
| |
| done: |
| if (rv != SECSuccess) { |
| badCert = (count >= 0) ? certsList[count] : cert; |
| } |
| if (pBadCert) |
| *pBadCert = badCert; |
| |
| return rv; |
| } |
| |
| #if 0 |
| /* not exported from shared libs, not used. Turn on if we ever need it. */ |
| SECStatus |
| CERT_CompareGeneralName(CERTGeneralName *a, CERTGeneralName *b) |
| { |
| CERTGeneralName *currentA; |
| CERTGeneralName *currentB; |
| PRBool found; |
| |
| currentA = a; |
| currentB = b; |
| if (a != NULL) { |
| do { |
| if (currentB == NULL) { |
| return SECFailure; |
| } |
| currentB = CERT_GetNextGeneralName(currentB); |
| currentA = CERT_GetNextGeneralName(currentA); |
| } while (currentA != a); |
| } |
| if (currentB != b) { |
| return SECFailure; |
| } |
| currentA = a; |
| do { |
| currentB = b; |
| found = PR_FALSE; |
| do { |
| if (currentB->type == currentA->type) { |
| switch (currentB->type) { |
| case certDNSName: |
| case certEDIPartyName: |
| case certIPAddress: |
| case certRegisterID: |
| case certRFC822Name: |
| case certX400Address: |
| case certURI: |
| if (SECITEM_CompareItem(¤tA->name.other, |
| ¤tB->name.other) |
| == SECEqual) { |
| found = PR_TRUE; |
| } |
| break; |
| case certOtherName: |
| if (SECITEM_CompareItem(¤tA->name.OthName.oid, |
| ¤tB->name.OthName.oid) |
| == SECEqual && |
| SECITEM_CompareItem(¤tA->name.OthName.name, |
| ¤tB->name.OthName.name) |
| == SECEqual) { |
| found = PR_TRUE; |
| } |
| break; |
| case certDirectoryName: |
| if (CERT_CompareName(¤tA->name.directoryName, |
| ¤tB->name.directoryName) |
| == SECEqual) { |
| found = PR_TRUE; |
| } |
| } |
| |
| } |
| currentB = CERT_GetNextGeneralName(currentB); |
| } while (currentB != b && found != PR_TRUE); |
| if (found != PR_TRUE) { |
| return SECFailure; |
| } |
| currentA = CERT_GetNextGeneralName(currentA); |
| } while (currentA != a); |
| return SECSuccess; |
| } |
| |
| SECStatus |
| CERT_CompareGeneralNameLists(CERTGeneralNameList *a, CERTGeneralNameList *b) |
| { |
| SECStatus rv; |
| |
| if (a == b) { |
| return SECSuccess; |
| } |
| if (a != NULL && b != NULL) { |
| PZ_Lock(a->lock); |
| PZ_Lock(b->lock); |
| rv = CERT_CompareGeneralName(a->name, b->name); |
| PZ_Unlock(a->lock); |
| PZ_Unlock(b->lock); |
| } else { |
| rv = SECFailure; |
| } |
| return rv; |
| } |
| #endif |
| |
| #if 0 |
| /* This function is not exported from NSS shared libraries, and is not |
| ** used inside of NSS. |
| ** XXX it doesn't check for failed allocations. :-( |
| */ |
| void * |
| CERT_GetGeneralNameFromListByType(CERTGeneralNameList *list, |
| CERTGeneralNameType type, |
| PLArenaPool *arena) |
| { |
| CERTName *name = NULL; |
| SECItem *item = NULL; |
| OtherName *other = NULL; |
| OtherName *tmpOther = NULL; |
| void *data; |
| |
| PZ_Lock(list->lock); |
| data = CERT_GetGeneralNameByType(list->name, type, PR_FALSE); |
| if (data != NULL) { |
| switch (type) { |
| case certDNSName: |
| case certEDIPartyName: |
| case certIPAddress: |
| case certRegisterID: |
| case certRFC822Name: |
| case certX400Address: |
| case certURI: |
| if (arena != NULL) { |
| item = PORT_ArenaNew(arena, SECItem); |
| if (item != NULL) { |
| XXX SECITEM_CopyItem(arena, item, (SECItem *) data); |
| } |
| } else { |
| item = SECITEM_DupItem((SECItem *) data); |
| } |
| PZ_Unlock(list->lock); |
| return item; |
| case certOtherName: |
| other = (OtherName *) data; |
| if (arena != NULL) { |
| tmpOther = PORT_ArenaNew(arena, OtherName); |
| } else { |
| tmpOther = PORT_New(OtherName); |
| } |
| if (tmpOther != NULL) { |
| XXX SECITEM_CopyItem(arena, &tmpOther->oid, &other->oid); |
| XXX SECITEM_CopyItem(arena, &tmpOther->name, &other->name); |
| } |
| PZ_Unlock(list->lock); |
| return tmpOther; |
| case certDirectoryName: |
| if (arena) { |
| name = PORT_ArenaZNew(list->arena, CERTName); |
| if (name) { |
| XXX CERT_CopyName(arena, name, (CERTName *) data); |
| } |
| } |
| PZ_Unlock(list->lock); |
| return name; |
| } |
| } |
| PZ_Unlock(list->lock); |
| return NULL; |
| } |
| #endif |
| |
| #if 0 |
| /* This function is not exported from NSS shared libraries, and is not |
| ** used inside of NSS. |
| ** XXX it should NOT be a void function, since it does allocations |
| ** that can fail. |
| */ |
| void |
| CERT_AddGeneralNameToList(CERTGeneralNameList *list, |
| CERTGeneralNameType type, |
| void *data, SECItem *oid) |
| { |
| CERTGeneralName *name; |
| |
| if (list != NULL && data != NULL) { |
| PZ_Lock(list->lock); |
| name = CERT_NewGeneralName(list->arena, type); |
| if (!name) |
| goto done; |
| switch (type) { |
| case certDNSName: |
| case certEDIPartyName: |
| case certIPAddress: |
| case certRegisterID: |
| case certRFC822Name: |
| case certX400Address: |
| case certURI: |
| XXX SECITEM_CopyItem(list->arena, &name->name.other, (SECItem *)data); |
| break; |
| case certOtherName: |
| XXX SECITEM_CopyItem(list->arena, &name->name.OthName.name, |
| (SECItem *) data); |
| XXX SECITEM_CopyItem(list->arena, &name->name.OthName.oid, |
| oid); |
| break; |
| case certDirectoryName: |
| XXX CERT_CopyName(list->arena, &name->name.directoryName, |
| (CERTName *) data); |
| break; |
| } |
| list->name = cert_CombineNamesLists(list->name, name); |
| list->len++; |
| done: |
| PZ_Unlock(list->lock); |
| } |
| return; |
| } |
| #endif |