diff options
Diffstat (limited to '')
-rw-r--r-- | security/nss/lib/certdb/genname.c | 2011 |
1 files changed, 2011 insertions, 0 deletions
diff --git a/security/nss/lib/certdb/genname.c b/security/nss/lib/certdb/genname.c new file mode 100644 index 0000000000..764a5c1cc5 --- /dev/null +++ b/security/nss/lib/certdb/genname.c @@ -0,0 +1,2011 @@ +/* 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\x09\xa0\x07" \ + "\x30\x05\x82\x03" ".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 |