diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /security/nss/lib/certhigh | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/nss/lib/certhigh')
-rw-r--r-- | security/nss/lib/certhigh/Makefile | 45 | ||||
-rw-r--r-- | security/nss/lib/certhigh/certhigh.c | 1210 | ||||
-rw-r--r-- | security/nss/lib/certhigh/certhigh.gyp | 31 | ||||
-rw-r--r-- | security/nss/lib/certhigh/certhtml.c | 321 | ||||
-rw-r--r-- | security/nss/lib/certhigh/certreq.c | 331 | ||||
-rw-r--r-- | security/nss/lib/certhigh/certvfy.c | 2158 | ||||
-rw-r--r-- | security/nss/lib/certhigh/certvfypkix.c | 2302 | ||||
-rw-r--r-- | security/nss/lib/certhigh/crlv2.c | 160 | ||||
-rw-r--r-- | security/nss/lib/certhigh/exports.gyp | 33 | ||||
-rw-r--r-- | security/nss/lib/certhigh/manifest.mn | 35 | ||||
-rw-r--r-- | security/nss/lib/certhigh/ocsp.c | 6119 | ||||
-rw-r--r-- | security/nss/lib/certhigh/ocsp.h | 723 | ||||
-rw-r--r-- | security/nss/lib/certhigh/ocspi.h | 166 | ||||
-rw-r--r-- | security/nss/lib/certhigh/ocspsig.c | 597 | ||||
-rw-r--r-- | security/nss/lib/certhigh/ocspt.h | 301 | ||||
-rw-r--r-- | security/nss/lib/certhigh/ocspti.h | 356 | ||||
-rw-r--r-- | security/nss/lib/certhigh/xcrldist.c | 212 |
17 files changed, 15100 insertions, 0 deletions
diff --git a/security/nss/lib/certhigh/Makefile b/security/nss/lib/certhigh/Makefile new file mode 100644 index 0000000000..bb88772853 --- /dev/null +++ b/security/nss/lib/certhigh/Makefile @@ -0,0 +1,45 @@ +#! gmake +# +# 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/. + +####################################################################### +# (1) Include initial platform-independent assignments (MANDATORY). # +####################################################################### + +include manifest.mn + +####################################################################### +# (2) Include "global" configuration information. (OPTIONAL) # +####################################################################### + +include $(CORE_DEPTH)/coreconf/config.mk + +####################################################################### +# (3) Include "component" configuration information. (OPTIONAL) # +####################################################################### + + + +####################################################################### +# (4) Include "local" platform-dependent assignments (OPTIONAL). # +####################################################################### + +####################################################################### +# (5) Execute "global" rules. (OPTIONAL) # +####################################################################### + +include $(CORE_DEPTH)/coreconf/rules.mk + +####################################################################### +# (6) Execute "component" rules. (OPTIONAL) # +####################################################################### + + + +####################################################################### +# (7) Execute "local" rules. (OPTIONAL). # +####################################################################### + + diff --git a/security/nss/lib/certhigh/certhigh.c b/security/nss/lib/certhigh/certhigh.c new file mode 100644 index 0000000000..7ae80b193e --- /dev/null +++ b/security/nss/lib/certhigh/certhigh.c @@ -0,0 +1,1210 @@ +/* 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 "nspr.h" +#include "secerr.h" +#include "secasn1.h" +#include "seccomon.h" +#include "pk11func.h" +#include "certdb.h" +#include "certt.h" +#include "cert.h" +#include "certxutl.h" + +#include "certi.h" +#include "nsspki.h" +#include "pki.h" +#include "pkit.h" +#include "pkitm.h" +#include "pki3hack.h" + +PRBool +CERT_MatchNickname(char *name1, char *name2) +{ + char *nickname1 = NULL; + char *nickname2 = NULL; + char *token1; + char *token2; + + /* first deal with the straight comparison */ + if (PORT_Strcmp(name1, name2) == 0) { + return PR_TRUE; + } + /* we need to handle the case where one name has an explicit token and the other + * doesn't */ + token1 = PORT_Strchr(name1, ':'); + token2 = PORT_Strchr(name2, ':'); + if ((token1 && token2) || (!token1 && !token2)) { + /* either both token names are specified or neither are, not match */ + return PR_FALSE; + } + if (token1) { + nickname1 = token1; + nickname2 = name2; + } else { + nickname1 = token2; + nickname2 = name1; + } + nickname1++; + if (PORT_Strcmp(nickname1, nickname2) != 0) { + return PR_FALSE; + } + /* Bug 1192443 - compare the other token with the internal slot here */ + return PR_TRUE; +} + +/* + * Find all user certificates that match the given criteria. + * + * "handle" - database to search + * "usage" - certificate usage to match + * "oneCertPerName" - if set then only return the "best" cert per + * name + * "validOnly" - only return certs that are curently valid + * "proto_win" - window handle passed to pkcs11 + */ +CERTCertList * +CERT_FindUserCertsByUsage(CERTCertDBHandle *handle, + SECCertUsage usage, + PRBool oneCertPerName, + PRBool validOnly, + void *proto_win) +{ + CERTCertNicknames *nicknames = NULL; + char **nnptr; + int nn; + CERTCertificate *cert = NULL; + CERTCertList *certList = NULL; + SECStatus rv; + PRTime time; + CERTCertListNode *node = NULL; + CERTCertListNode *freenode = NULL; + int n; + + time = PR_Now(); + + nicknames = CERT_GetCertNicknames(handle, SEC_CERT_NICKNAMES_USER, + proto_win); + + if ((nicknames == NULL) || (nicknames->numnicknames == 0)) { + goto loser; + } + + nnptr = nicknames->nicknames; + nn = nicknames->numnicknames; + + while (nn > 0) { + cert = NULL; + /* use the pk11 call so that we pick up any certs on tokens, + * which may require login + */ + if (proto_win != NULL) { + cert = PK11_FindCertFromNickname(*nnptr, proto_win); + } + + /* Sigh, It turns out if the cert is already in the temp db, because + * it's in the perm db, then the nickname lookup doesn't work. + * since we already have the cert here, though, than we can just call + * CERT_CreateSubjectCertList directly. For those cases where we didn't + * find the cert in pkcs #11 (because we didn't have a password arg, + * or because the nickname is for a peer, server, or CA cert, then we + * go look the cert up. + */ + if (cert == NULL) { + cert = CERT_FindCertByNickname(handle, *nnptr); + } + + if (cert != NULL) { + /* collect certs for this nickname, sorting them into the list */ + certList = CERT_CreateSubjectCertList(certList, handle, + &cert->derSubject, time, validOnly); + + CERT_FilterCertListForUserCerts(certList); + + /* drop the extra reference */ + CERT_DestroyCertificate(cert); + } + + nnptr++; + nn--; + } + + /* remove certs with incorrect usage */ + rv = CERT_FilterCertListByUsage(certList, usage, PR_FALSE); + + if (rv != SECSuccess) { + goto loser; + } + + /* remove any extra certs for each name */ + if (oneCertPerName) { + PRBool *flags; + + nn = nicknames->numnicknames; + nnptr = nicknames->nicknames; + + if (!certList) { + goto loser; + } + + flags = (PRBool *)PORT_ZAlloc(sizeof(PRBool) * nn); + if (flags == NULL) { + goto loser; + } + + node = CERT_LIST_HEAD(certList); + + /* treverse all certs in the list */ + while (!CERT_LIST_END(node, certList)) { + + /* find matching nickname index */ + for (n = 0; n < nn; n++) { + if (CERT_MatchNickname(nnptr[n], node->cert->nickname)) { + /* We found a match. If this is the first one, then + * set the flag and move on to the next cert. If this + * is not the first one then delete it from the list. + */ + if (flags[n]) { + /* We have already seen a cert with this nickname, + * so delete this one. + */ + freenode = node; + node = CERT_LIST_NEXT(node); + CERT_RemoveCertListNode(freenode); + } else { + /* keep the first cert for each nickname, but set the + * flag so we know to delete any others with the same + * nickname. + */ + flags[n] = PR_TRUE; + node = CERT_LIST_NEXT(node); + } + break; + } + } + if (n == nn) { + /* if we get here it means that we didn't find a matching + * nickname, which should not happen. + */ + PORT_Assert(0); + node = CERT_LIST_NEXT(node); + } + } + PORT_Free(flags); + } + + goto done; + +loser: + if (certList != NULL) { + CERT_DestroyCertList(certList); + certList = NULL; + } + +done: + if (nicknames != NULL) { + CERT_FreeNicknames(nicknames); + } + + return (certList); +} + +/* + * Find a user certificate that matchs the given criteria. + * + * "handle" - database to search + * "nickname" - nickname to match + * "usage" - certificate usage to match + * "validOnly" - only return certs that are curently valid + * "proto_win" - window handle passed to pkcs11 + */ +CERTCertificate * +CERT_FindUserCertByUsage(CERTCertDBHandle *handle, + const char *nickname, + SECCertUsage usage, + PRBool validOnly, + void *proto_win) +{ + CERTCertificate *cert = NULL; + CERTCertList *certList = NULL; + SECStatus rv; + PRTime time; + + time = PR_Now(); + + /* use the pk11 call so that we pick up any certs on tokens, + * which may require login + */ + /* XXX - why is this restricted? */ + if (proto_win != NULL) { + cert = PK11_FindCertFromNickname(nickname, proto_win); + } + + /* sigh, There are still problems find smart cards from the temp + * db. This will get smart cards working again. The real fix + * is to make sure we can search the temp db by their token nickname. + */ + if (cert == NULL) { + cert = CERT_FindCertByNickname(handle, nickname); + } + + if (cert != NULL) { + unsigned int requiredKeyUsage; + unsigned int requiredCertType; + + rv = CERT_KeyUsageAndTypeForCertUsage(usage, PR_FALSE, + &requiredKeyUsage, &requiredCertType); + if (rv != SECSuccess) { + /* drop the extra reference */ + CERT_DestroyCertificate(cert); + cert = NULL; + goto loser; + } + /* If we already found the right cert, just return it */ + if ((!validOnly || CERT_CheckCertValidTimes(cert, time, PR_FALSE) == secCertTimeValid) && + (CERT_CheckKeyUsage(cert, requiredKeyUsage) == SECSuccess) && + (cert->nsCertType & requiredCertType) && + CERT_IsUserCert(cert)) { + return (cert); + } + + /* collect certs for this nickname, sorting them into the list */ + certList = CERT_CreateSubjectCertList(certList, handle, + &cert->derSubject, time, validOnly); + + CERT_FilterCertListForUserCerts(certList); + + /* drop the extra reference */ + CERT_DestroyCertificate(cert); + cert = NULL; + } + + if (certList == NULL) { + goto loser; + } + + /* remove certs with incorrect usage */ + rv = CERT_FilterCertListByUsage(certList, usage, PR_FALSE); + + if (rv != SECSuccess) { + goto loser; + } + + if (!CERT_LIST_EMPTY(certList)) { + cert = CERT_DupCertificate(CERT_LIST_HEAD(certList)->cert); + } + +loser: + if (certList != NULL) { + CERT_DestroyCertList(certList); + } + + return (cert); +} + +CERTCertList * +CERT_MatchUserCert(CERTCertDBHandle *handle, + SECCertUsage usage, + int nCANames, char **caNames, + void *proto_win) +{ + CERTCertList *certList = NULL; + SECStatus rv; + + certList = CERT_FindUserCertsByUsage(handle, usage, PR_TRUE, PR_TRUE, + proto_win); + if (certList == NULL) { + goto loser; + } + + rv = CERT_FilterCertListByCANames(certList, nCANames, caNames, usage); + if (rv != SECSuccess) { + goto loser; + } + + goto done; + +loser: + if (certList != NULL) { + CERT_DestroyCertList(certList); + certList = NULL; + } + +done: + + return (certList); +} + +typedef struct stringNode { + struct stringNode *next; + char *string; +} stringNode; + +static PRStatus +CollectNicknames(NSSCertificate *c, void *data) +{ + CERTCertNicknames *names; + PRBool saveit = PR_FALSE; + stringNode *node; + int len; +#ifdef notdef + NSSTrustDomain *td; + NSSTrust *trust; +#endif + char *stanNickname; + char *nickname = NULL; + + names = (CERTCertNicknames *)data; + + stanNickname = nssCertificate_GetNickname(c, NULL); + + if (stanNickname) { + nss_ZFreeIf(stanNickname); + stanNickname = NULL; + if (names->what == SEC_CERT_NICKNAMES_USER) { + saveit = NSSCertificate_IsPrivateKeyAvailable(c, NULL, NULL); + } +#ifdef notdef + else { + td = NSSCertificate_GetTrustDomain(c); + if (!td) { + return PR_SUCCESS; + } + trust = nssTrustDomain_FindTrustForCertificate(td, c); + + switch (names->what) { + case SEC_CERT_NICKNAMES_ALL: + if ((trust->sslFlags & (CERTDB_VALID_CA | CERTDB_VALID_PEER)) || + (trust->emailFlags & (CERTDB_VALID_CA | CERTDB_VALID_PEER)) || + (trust->objectSigningFlags & + (CERTDB_VALID_CA | CERTDB_VALID_PEER))) { + saveit = PR_TRUE; + } + + break; + case SEC_CERT_NICKNAMES_SERVER: + if (trust->sslFlags & CERTDB_VALID_PEER) { + saveit = PR_TRUE; + } + + break; + case SEC_CERT_NICKNAMES_CA: + if (((trust->sslFlags & CERTDB_VALID_CA) == CERTDB_VALID_CA) || + ((trust->emailFlags & CERTDB_VALID_CA) == CERTDB_VALID_CA) || + ((trust->objectSigningFlags & CERTDB_VALID_CA) == + CERTDB_VALID_CA)) { + saveit = PR_TRUE; + } + break; + } + } +#endif + } + + /* traverse the list of collected nicknames and make sure we don't make + * a duplicate + */ + if (saveit) { + nickname = STAN_GetCERTCertificateName(NULL, c); + /* nickname can only be NULL here if we are having memory + * alloc problems */ + if (nickname == NULL) { + return PR_FAILURE; + } + node = (stringNode *)names->head; + while (node != NULL) { + if (PORT_Strcmp(nickname, node->string) == 0) { + /* if the string matches, then don't save this one */ + saveit = PR_FALSE; + break; + } + node = node->next; + } + } + + if (saveit) { + + /* allocate the node */ + node = (stringNode *)PORT_ArenaAlloc(names->arena, sizeof(stringNode)); + if (node == NULL) { + PORT_Free(nickname); + return PR_FAILURE; + } + + /* copy the string */ + len = PORT_Strlen(nickname) + 1; + node->string = (char *)PORT_ArenaAlloc(names->arena, len); + if (node->string == NULL) { + PORT_Free(nickname); + return PR_FAILURE; + } + PORT_Memcpy(node->string, nickname, len); + + /* link it into the list */ + node->next = (stringNode *)names->head; + names->head = (void *)node; + + /* bump the count */ + names->numnicknames++; + } + + if (nickname) + PORT_Free(nickname); + return (PR_SUCCESS); +} + +CERTCertNicknames * +CERT_GetCertNicknames(CERTCertDBHandle *handle, int what, void *wincx) +{ + PLArenaPool *arena; + CERTCertNicknames *names; + int i; + stringNode *node; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return (NULL); + } + + names = (CERTCertNicknames *)PORT_ArenaAlloc(arena, sizeof(CERTCertNicknames)); + if (names == NULL) { + goto loser; + } + + names->arena = arena; + names->head = NULL; + names->numnicknames = 0; + names->nicknames = NULL; + names->what = what; + names->totallen = 0; + + /* make sure we are logged in */ + (void)pk11_TraverseAllSlots(NULL, NULL, PR_TRUE, wincx); + + NSSTrustDomain_TraverseCertificates(handle, + CollectNicknames, (void *)names); + if (names->numnicknames) { + names->nicknames = (char **)PORT_ArenaAlloc(arena, + names->numnicknames * + sizeof(char *)); + + if (names->nicknames == NULL) { + goto loser; + } + + node = (stringNode *)names->head; + + for (i = 0; i < names->numnicknames; i++) { + PORT_Assert(node != NULL); + + names->nicknames[i] = node->string; + names->totallen += PORT_Strlen(node->string); + node = node->next; + } + + PORT_Assert(node == NULL); + } + + return (names); + +loser: + PORT_FreeArena(arena, PR_FALSE); + return (NULL); +} + +void +CERT_FreeNicknames(CERTCertNicknames *nicknames) +{ + PORT_FreeArena(nicknames->arena, PR_FALSE); + + return; +} + +/* [ FROM pcertdb.c ] */ + +typedef struct dnameNode { + struct dnameNode *next; + SECItem name; +} dnameNode; + +void +CERT_FreeDistNames(CERTDistNames *names) +{ + PORT_FreeArena(names->arena, PR_FALSE); + + return; +} + +static SECStatus +CollectDistNames(CERTCertificate *cert, SECItem *k, void *data) +{ + CERTDistNames *names; + PRBool saveit = PR_FALSE; + CERTCertTrust trust; + dnameNode *node; + int len; + + names = (CERTDistNames *)data; + + if (CERT_GetCertTrust(cert, &trust) == SECSuccess) { + /* only collect names of CAs trusted for issuing SSL clients */ + if (trust.sslFlags & CERTDB_TRUSTED_CLIENT_CA) { + saveit = PR_TRUE; + } + } + + if (saveit) { + /* allocate the node */ + node = (dnameNode *)PORT_ArenaAlloc(names->arena, sizeof(dnameNode)); + if (node == NULL) { + return (SECFailure); + } + + /* copy the name */ + node->name.len = len = cert->derSubject.len; + node->name.type = siBuffer; + node->name.data = (unsigned char *)PORT_ArenaAlloc(names->arena, len); + if (node->name.data == NULL) { + return (SECFailure); + } + PORT_Memcpy(node->name.data, cert->derSubject.data, len); + + /* link it into the list */ + node->next = (dnameNode *)names->head; + names->head = (void *)node; + + /* bump the count */ + names->nnames++; + } + + return (SECSuccess); +} + +/* + * Return all of the CAs that are "trusted" for SSL. + */ +CERTDistNames * +CERT_DupDistNames(CERTDistNames *orig) +{ + PLArenaPool *arena; + CERTDistNames *names; + int i; + SECStatus rv; + + /* allocate an arena to use */ + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return (NULL); + } + + /* allocate the header structure */ + names = (CERTDistNames *)PORT_ArenaAlloc(arena, sizeof(CERTDistNames)); + if (names == NULL) { + goto loser; + } + + /* initialize the header struct */ + names->arena = arena; + names->head = NULL; + names->nnames = orig->nnames; + names->names = NULL; + + /* construct the array from the list */ + if (orig->nnames) { + names->names = (SECItem *)PORT_ArenaNewArray(arena, SECItem, + orig->nnames); + if (names->names == NULL) { + goto loser; + } + for (i = 0; i < orig->nnames; i++) { + rv = SECITEM_CopyItem(arena, &names->names[i], &orig->names[i]); + if (rv != SECSuccess) { + goto loser; + } + } + } + return (names); + +loser: + PORT_FreeArena(arena, PR_FALSE); + return (NULL); +} + +CERTDistNames * +CERT_GetSSLCACerts(CERTCertDBHandle *handle) +{ + PLArenaPool *arena; + CERTDistNames *names; + int i; + SECStatus rv; + dnameNode *node; + + /* allocate an arena to use */ + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return (NULL); + } + + /* allocate the header structure */ + names = (CERTDistNames *)PORT_ArenaAlloc(arena, sizeof(CERTDistNames)); + if (names == NULL) { + goto loser; + } + + /* initialize the header struct */ + names->arena = arena; + names->head = NULL; + names->nnames = 0; + names->names = NULL; + + /* collect the names from the database */ + rv = PK11_TraverseSlotCerts(CollectDistNames, (void *)names, NULL); + if (rv) { + goto loser; + } + + /* construct the array from the list */ + if (names->nnames) { + names->names = (SECItem *)PORT_ArenaAlloc(arena, names->nnames * sizeof(SECItem)); + + if (names->names == NULL) { + goto loser; + } + + node = (dnameNode *)names->head; + + for (i = 0; i < names->nnames; i++) { + PORT_Assert(node != NULL); + + names->names[i] = node->name; + node = node->next; + } + + PORT_Assert(node == NULL); + } + + return (names); + +loser: + PORT_FreeArena(arena, PR_FALSE); + return (NULL); +} + +CERTDistNames * +CERT_DistNamesFromCertList(CERTCertList *certList) +{ + CERTDistNames *dnames = NULL; + PLArenaPool *arena; + CERTCertListNode *node = NULL; + SECItem *names = NULL; + int listLen = 0, i = 0; + + if (certList == NULL) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + node = CERT_LIST_HEAD(certList); + while (!CERT_LIST_END(node, certList)) { + listLen += 1; + node = CERT_LIST_NEXT(node); + } + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) + goto loser; + dnames = PORT_ArenaZNew(arena, CERTDistNames); + if (dnames == NULL) + goto loser; + + dnames->arena = arena; + dnames->nnames = listLen; + dnames->names = names = PORT_ArenaZNewArray(arena, SECItem, listLen); + if (names == NULL) + goto loser; + + node = CERT_LIST_HEAD(certList); + while (!CERT_LIST_END(node, certList)) { + CERTCertificate *cert = node->cert; + SECStatus rv = SECITEM_CopyItem(arena, &names[i++], &cert->derSubject); + if (rv == SECFailure) { + goto loser; + } + node = CERT_LIST_NEXT(node); + } + return dnames; +loser: + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } + return NULL; +} + +CERTDistNames * +CERT_DistNamesFromNicknames(CERTCertDBHandle *handle, char **nicknames, + int nnames) +{ + CERTDistNames *dnames = NULL; + PLArenaPool *arena; + int i, rv; + SECItem *names = NULL; + CERTCertificate *cert = NULL; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) + goto loser; + dnames = PORT_ArenaZNew(arena, CERTDistNames); + if (dnames == NULL) + goto loser; + + dnames->arena = arena; + dnames->nnames = nnames; + dnames->names = names = PORT_ArenaZNewArray(arena, SECItem, nnames); + if (names == NULL) + goto loser; + + for (i = 0; i < nnames; i++) { + cert = CERT_FindCertByNicknameOrEmailAddr(handle, nicknames[i]); + if (cert == NULL) + goto loser; + rv = SECITEM_CopyItem(arena, &names[i], &cert->derSubject); + if (rv == SECFailure) + goto loser; + CERT_DestroyCertificate(cert); + } + return dnames; + +loser: + if (cert != NULL) + CERT_DestroyCertificate(cert); + if (arena != NULL) + PORT_FreeArena(arena, PR_FALSE); + return NULL; +} + +/* [ from pcertdb.c - calls Ascii to Name ] */ +/* + * Lookup a certificate in the database by name + */ +CERTCertificate * +CERT_FindCertByNameString(CERTCertDBHandle *handle, char *nameStr) +{ + CERTName *name; + SECItem *nameItem; + CERTCertificate *cert = NULL; + PLArenaPool *arena = NULL; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + + if (arena == NULL) { + goto loser; + } + + name = CERT_AsciiToName(nameStr); + + if (name) { + nameItem = SEC_ASN1EncodeItem(arena, NULL, (void *)name, + CERT_NameTemplate); + if (nameItem != NULL) { + cert = CERT_FindCertByName(handle, nameItem); + } + CERT_DestroyName(name); + } + +loser: + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } + + return (cert); +} + +/* From certv3.c */ + +CERTCrlDistributionPoints * +CERT_FindCRLDistributionPoints(CERTCertificate *cert) +{ + SECItem encodedExtenValue; + SECStatus rv; + CERTCrlDistributionPoints *dps; + + encodedExtenValue.data = NULL; + encodedExtenValue.len = 0; + + rv = cert_FindExtension(cert->extensions, SEC_OID_X509_CRL_DIST_POINTS, + &encodedExtenValue); + if (rv != SECSuccess) { + return (NULL); + } + + dps = CERT_DecodeCRLDistributionPoints(cert->arena, &encodedExtenValue); + + PORT_Free(encodedExtenValue.data); + + return dps; +} + +/* From crl.c */ +CERTSignedCrl * +CERT_ImportCRL(CERTCertDBHandle *handle, SECItem *derCRL, char *url, int type, void *wincx) +{ + CERTSignedCrl *retCrl = NULL; + PK11SlotInfo *slot = PK11_GetInternalKeySlot(); + retCrl = PK11_ImportCRL(slot, derCRL, url, type, wincx, + CRL_IMPORT_DEFAULT_OPTIONS, NULL, CRL_DECODE_DEFAULT_OPTIONS); + PK11_FreeSlot(slot); + + return retCrl; +} + +/* From certdb.c */ +static SECStatus +cert_ImportCAChain(SECItem *certs, int numcerts, SECCertUsage certUsage, PRBool trusted) +{ + SECStatus rv; + SECItem *derCert; + CERTCertificate *cert = NULL; + CERTCertificate *newcert = NULL; + CERTCertDBHandle *handle; + CERTCertTrust trust; + PRBool isca; + char *nickname; + unsigned int certtype; + PRBool istemp = PR_FALSE; + + handle = CERT_GetDefaultCertDB(); + + while (numcerts--) { + derCert = certs; + certs++; + + /* decode my certificate */ + /* This use is ok -- only looks at decoded parts, calls NewTemp later */ + newcert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL); + if (newcert == NULL) { + goto loser; + } + + if (!trusted) { + /* make sure that cert is valid */ + rv = CERT_CertTimesValid(newcert); + if (rv == SECFailure) { + goto endloop; + } + } + + /* does it have the CA extension */ + + /* + * Make sure that if this is an intermediate CA in the chain that + * it was given permission by its signer to be a CA. + */ + isca = CERT_IsCACert(newcert, &certtype); + + if (!isca) { + if (!trusted) { + goto endloop; + } + trust.sslFlags = CERTDB_VALID_CA; + trust.emailFlags = CERTDB_VALID_CA; + trust.objectSigningFlags = CERTDB_VALID_CA; + } else { + /* SSL ca's must have the ssl bit set */ + if ((certUsage == certUsageSSLCA) && + ((certtype & NS_CERT_TYPE_SSL_CA) != NS_CERT_TYPE_SSL_CA)) { + goto endloop; + } + + /* it passed all of the tests, so lets add it to the database */ + /* mark it as a CA */ + PORT_Memset((void *)&trust, 0, sizeof(trust)); + switch (certUsage) { + case certUsageSSLCA: + trust.sslFlags = CERTDB_VALID_CA; + break; + case certUsageUserCertImport: + if ((certtype & NS_CERT_TYPE_SSL_CA) == NS_CERT_TYPE_SSL_CA) { + trust.sslFlags = CERTDB_VALID_CA; + } + if ((certtype & NS_CERT_TYPE_EMAIL_CA) == + NS_CERT_TYPE_EMAIL_CA) { + trust.emailFlags = CERTDB_VALID_CA; + } + if ((certtype & NS_CERT_TYPE_OBJECT_SIGNING_CA) == + NS_CERT_TYPE_OBJECT_SIGNING_CA) { + trust.objectSigningFlags = CERTDB_VALID_CA; + } + break; + default: + PORT_Assert(0); + break; + } + } + + cert = CERT_NewTempCertificate(handle, derCert, NULL, + PR_FALSE, PR_FALSE); + if (cert == NULL) { + goto loser; + } + + /* if the cert is temp, make it perm; otherwise we're done */ + rv = CERT_GetCertIsTemp(cert, &istemp); + if (rv != SECSuccess) { + goto loser; + } + if (istemp) { + /* get a default nickname for it */ + nickname = CERT_MakeCANickname(cert); + + rv = CERT_AddTempCertToPerm(cert, nickname, &trust); + + /* free the nickname */ + if (nickname) { + PORT_Free(nickname); + } + } else { + rv = SECSuccess; + } + + if (rv != SECSuccess) { + goto loser; + } + + endloop: + if (newcert) { + CERT_DestroyCertificate(newcert); + newcert = NULL; + } + } + + rv = SECSuccess; + goto done; +loser: + rv = SECFailure; +done: + + if (newcert) { + CERT_DestroyCertificate(newcert); + newcert = NULL; + } + + if (cert) { + CERT_DestroyCertificate(cert); + cert = NULL; + } + + return (rv); +} + +SECStatus +CERT_ImportCAChain(SECItem *certs, int numcerts, SECCertUsage certUsage) +{ + return cert_ImportCAChain(certs, numcerts, certUsage, PR_FALSE); +} + +SECStatus +CERT_ImportCAChainTrusted(SECItem *certs, int numcerts, SECCertUsage certUsage) +{ + return cert_ImportCAChain(certs, numcerts, certUsage, PR_TRUE); +} + +/* Moved from certdb.c */ +/* +** CERT_CertChainFromCert +** +** Construct a CERTCertificateList consisting of the given certificate and all +** of the issuer certs until we either get to a self-signed cert or can't find +** an issuer. Since we don't know how many certs are in the chain we have to +** build a linked list first as we count them. +*/ + +typedef struct certNode { + struct certNode *next; + CERTCertificate *cert; +} certNode; + +CERTCertificateList * +CERT_CertChainFromCert(CERTCertificate *cert, SECCertUsage usage, + PRBool includeRoot) +{ + CERTCertificateList *chain = NULL; + NSSCertificate **stanChain; + NSSCertificate *stanCert; + PLArenaPool *arena; + NSSUsage nssUsage; + int i, len; + NSSTrustDomain *td = STAN_GetDefaultTrustDomain(); + NSSCryptoContext *cc = STAN_GetDefaultCryptoContext(); + + stanCert = STAN_GetNSSCertificate(cert); + if (!stanCert) { + /* error code is set */ + return NULL; + } + nssUsage.anyUsage = PR_FALSE; + nssUsage.nss3usage = usage; + nssUsage.nss3lookingForCA = PR_FALSE; + stanChain = NSSCertificate_BuildChain(stanCert, NULL, &nssUsage, NULL, NULL, + CERT_MAX_CERT_CHAIN, NULL, NULL, td, cc); + if (!stanChain) { + PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER); + return NULL; + } + + len = 0; + stanCert = stanChain[0]; + while (stanCert) { + stanCert = stanChain[++len]; + } + + arena = PORT_NewArena(4096); + if (arena == NULL) { + goto loser; + } + + chain = (CERTCertificateList *)PORT_ArenaAlloc(arena, + sizeof(CERTCertificateList)); + if (!chain) + goto loser; + chain->certs = (SECItem *)PORT_ArenaAlloc(arena, len * sizeof(SECItem)); + if (!chain->certs) + goto loser; + i = 0; + stanCert = stanChain[i]; + while (stanCert) { + SECItem derCert; + CERTCertificate *cCert = STAN_GetCERTCertificate(stanCert); + if (!cCert) { + goto loser; + } + derCert.len = (unsigned int)stanCert->encoding.size; + derCert.data = (unsigned char *)stanCert->encoding.data; + derCert.type = siBuffer; + if (SECITEM_CopyItem(arena, &chain->certs[i], &derCert) != SECSuccess) { + CERT_DestroyCertificate(cCert); + goto loser; + } + stanCert = stanChain[++i]; + if (!stanCert && !cCert->isRoot) { + /* reached the end of the chain, but the final cert is + * not a root. Don't discard it. + */ + includeRoot = PR_TRUE; + } + CERT_DestroyCertificate(cCert); + } + if (!includeRoot && len > 1) { + chain->len = len - 1; + } else { + chain->len = len; + } + + chain->arena = arena; + nss_ZFreeIf(stanChain); + return chain; +loser: + i = 0; + stanCert = stanChain[i]; + while (stanCert) { + CERTCertificate *cCert = STAN_GetCERTCertificate(stanCert); + if (cCert) { + CERT_DestroyCertificate(cCert); + } + stanCert = stanChain[++i]; + } + nss_ZFreeIf(stanChain); + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } + return NULL; +} + +/* Builds a CERTCertificateList holding just one DER-encoded cert, namely +** the one for the cert passed as an argument. +*/ +CERTCertificateList * +CERT_CertListFromCert(CERTCertificate *cert) +{ + CERTCertificateList *chain = NULL; + int rv; + PLArenaPool *arena; + + /* arena for SecCertificateList */ + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) + goto no_memory; + + /* build the CERTCertificateList */ + chain = (CERTCertificateList *)PORT_ArenaAlloc(arena, sizeof(CERTCertificateList)); + if (chain == NULL) + goto no_memory; + chain->certs = (SECItem *)PORT_ArenaAlloc(arena, 1 * sizeof(SECItem)); + if (chain->certs == NULL) + goto no_memory; + rv = SECITEM_CopyItem(arena, chain->certs, &(cert->derCert)); + if (rv < 0) + goto loser; + chain->len = 1; + chain->arena = arena; + + return chain; + +no_memory: + PORT_SetError(SEC_ERROR_NO_MEMORY); +loser: + if (arena != NULL) { + PORT_FreeArena(arena, PR_FALSE); + } + return NULL; +} + +CERTCertificateList * +CERT_DupCertList(const CERTCertificateList *oldList) +{ + CERTCertificateList *newList = NULL; + PLArenaPool *arena = NULL; + SECItem *newItem; + SECItem *oldItem; + int len = oldList->len; + int rv; + + /* arena for SecCertificateList */ + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) + goto no_memory; + + /* now build the CERTCertificateList */ + newList = PORT_ArenaNew(arena, CERTCertificateList); + if (newList == NULL) + goto no_memory; + newList->arena = arena; + newItem = (SECItem *)PORT_ArenaAlloc(arena, len * sizeof(SECItem)); + if (newItem == NULL) + goto no_memory; + newList->certs = newItem; + newList->len = len; + + for (oldItem = oldList->certs; len > 0; --len, ++newItem, ++oldItem) { + rv = SECITEM_CopyItem(arena, newItem, oldItem); + if (rv < 0) + goto loser; + } + return newList; + +no_memory: + PORT_SetError(SEC_ERROR_NO_MEMORY); +loser: + if (arena != NULL) { + PORT_FreeArena(arena, PR_FALSE); + } + return NULL; +} + +void +CERT_DestroyCertificateList(CERTCertificateList *list) +{ + PORT_FreeArena(list->arena, PR_FALSE); +} diff --git a/security/nss/lib/certhigh/certhigh.gyp b/security/nss/lib/certhigh/certhigh.gyp new file mode 100644 index 0000000000..5817c3eb5f --- /dev/null +++ b/security/nss/lib/certhigh/certhigh.gyp @@ -0,0 +1,31 @@ +# 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/. +{ + 'includes': [ + '../../coreconf/config.gypi' + ], + 'targets': [ + { + 'target_name': 'certhi', + 'type': 'static_library', + 'sources': [ + 'certhigh.c', + 'certhtml.c', + 'certreq.c', + 'certvfy.c', + 'certvfypkix.c', + 'crlv2.c', + 'ocsp.c', + 'ocspsig.c', + 'xcrldist.c' + ], + 'dependencies': [ + '<(DEPTH)/exports.gyp:nss_exports' + ] + } + ], + 'variables': { + 'module': 'nss' + } +}
\ No newline at end of file diff --git a/security/nss/lib/certhigh/certhtml.c b/security/nss/lib/certhigh/certhtml.c new file mode 100644 index 0000000000..2d708cc950 --- /dev/null +++ b/security/nss/lib/certhigh/certhtml.c @@ -0,0 +1,321 @@ +/* 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/. */ + +/* + * certhtml.c --- convert a cert to html + */ + +#include "seccomon.h" +#include "secitem.h" +#include "sechash.h" +#include "cert.h" +#include "keyhi.h" +#include "secder.h" +#include "prprf.h" +#include "secport.h" +#include "secasn1.h" +#include "pk11func.h" + +static char *hex = "0123456789ABCDEF"; + +/* +** Convert a der-encoded integer to a hex printable string form +*/ +char * +CERT_Hexify(SECItem *i, int do_colon) +{ + unsigned char *cp, *end; + char *rv, *o; + + if (!i->len) { + return PORT_Strdup("00"); + } + + rv = o = (char *)PORT_Alloc(i->len * 3); + if (!rv) + return rv; + + cp = i->data; + end = cp + i->len; + while (cp < end) { + unsigned char ch = *cp++; + *o++ = hex[(ch >> 4) & 0xf]; + *o++ = hex[ch & 0xf]; + if (cp != end) { + if (do_colon) { + *o++ = ':'; + } + } + } + *o = 0; /* Null terminate the string */ + return rv; +} + +#define BREAK "<br>" +#define BREAKLEN 4 +#define COMMA ", " +#define COMMALEN 2 + +#define MAX_OUS 20 +#define MAX_DC MAX_OUS + +char * +CERT_FormatName(CERTName *name) +{ + CERTRDN **rdns; + CERTRDN *rdn; + CERTAVA **avas; + CERTAVA *ava; + char *buf = 0; + char *tmpbuf = 0; + SECItem *cn = 0; + SECItem *email = 0; + SECItem *org = 0; + SECItem *loc = 0; + SECItem *state = 0; + SECItem *country = 0; + SECItem *dq = 0; + + unsigned len = 0; + int tag; + int i; + int ou_count = 0; + int dc_count = 0; + PRBool first; + SECItem *orgunit[MAX_OUS]; + SECItem *dc[MAX_DC]; + + /* Loop over name components and gather the interesting ones */ + rdns = name->rdns; + while ((rdn = *rdns++) != 0) { + avas = rdn->avas; + while ((ava = *avas++) != 0) { + tag = CERT_GetAVATag(ava); + switch (tag) { + case SEC_OID_AVA_COMMON_NAME: + if (cn) { + break; + } + cn = CERT_DecodeAVAValue(&ava->value); + if (!cn) { + goto loser; + } + len += cn->len; + // cn will always have BREAK after it + len += BREAKLEN; + break; + case SEC_OID_AVA_COUNTRY_NAME: + if (country) { + break; + } + country = CERT_DecodeAVAValue(&ava->value); + if (!country) { + goto loser; + } + len += country->len; + // country may have COMMA after it (if we over-count len, + // that's fine - we'll just allocate a buffer larger than we + // need) + len += COMMALEN; + break; + case SEC_OID_AVA_LOCALITY: + if (loc) { + break; + } + loc = CERT_DecodeAVAValue(&ava->value); + if (!loc) { + goto loser; + } + len += loc->len; + // loc may have COMMA after it + len += COMMALEN; + break; + case SEC_OID_AVA_STATE_OR_PROVINCE: + if (state) { + break; + } + state = CERT_DecodeAVAValue(&ava->value); + if (!state) { + goto loser; + } + len += state->len; + // state currently won't have COMMA after it, but this is a + // (probably vain) attempt to future-proof this code + len += COMMALEN; + break; + case SEC_OID_AVA_ORGANIZATION_NAME: + if (org) { + break; + } + org = CERT_DecodeAVAValue(&ava->value); + if (!org) { + goto loser; + } + len += org->len; + // org will have BREAK after it + len += BREAKLEN; + break; + case SEC_OID_AVA_DN_QUALIFIER: + if (dq) { + break; + } + dq = CERT_DecodeAVAValue(&ava->value); + if (!dq) { + goto loser; + } + len += dq->len; + // dq will have BREAK after it + len += BREAKLEN; + break; + case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME: + if (ou_count < MAX_OUS) { + orgunit[ou_count] = CERT_DecodeAVAValue(&ava->value); + if (!orgunit[ou_count]) { + goto loser; + } + len += orgunit[ou_count++]->len; + // each ou will have BREAK after it + len += BREAKLEN; + } + break; + case SEC_OID_AVA_DC: + if (dc_count < MAX_DC) { + dc[dc_count] = CERT_DecodeAVAValue(&ava->value); + if (!dc[dc_count]) { + goto loser; + } + len += dc[dc_count++]->len; + // each dc will have BREAK after it + len += BREAKLEN; + } + break; + case SEC_OID_PKCS9_EMAIL_ADDRESS: + case SEC_OID_RFC1274_MAIL: + if (email) { + break; + } + email = CERT_DecodeAVAValue(&ava->value); + if (!email) { + goto loser; + } + len += email->len; + // email will have BREAK after it + len += BREAKLEN; + break; + default: + break; + } + } + } + + // there may be a final BREAK + len += BREAKLEN; + + /* allocate buffer */ + buf = (char *)PORT_Alloc(len); + if (!buf) { + goto loser; + } + + tmpbuf = buf; + + if (cn) { + PORT_Memcpy(tmpbuf, cn->data, cn->len); + tmpbuf += cn->len; + PORT_Memcpy(tmpbuf, BREAK, BREAKLEN); + tmpbuf += BREAKLEN; + } + if (email) { + PORT_Memcpy(tmpbuf, email->data, email->len); + tmpbuf += (email->len); + PORT_Memcpy(tmpbuf, BREAK, BREAKLEN); + tmpbuf += BREAKLEN; + } + for (i = ou_count - 1; i >= 0; i--) { + PORT_Memcpy(tmpbuf, orgunit[i]->data, orgunit[i]->len); + tmpbuf += (orgunit[i]->len); + PORT_Memcpy(tmpbuf, BREAK, BREAKLEN); + tmpbuf += BREAKLEN; + } + if (dq) { + PORT_Memcpy(tmpbuf, dq->data, dq->len); + tmpbuf += (dq->len); + PORT_Memcpy(tmpbuf, BREAK, BREAKLEN); + tmpbuf += BREAKLEN; + } + if (org) { + PORT_Memcpy(tmpbuf, org->data, org->len); + tmpbuf += (org->len); + PORT_Memcpy(tmpbuf, BREAK, BREAKLEN); + tmpbuf += BREAKLEN; + } + for (i = dc_count - 1; i >= 0; i--) { + PORT_Memcpy(tmpbuf, dc[i]->data, dc[i]->len); + tmpbuf += (dc[i]->len); + PORT_Memcpy(tmpbuf, BREAK, BREAKLEN); + tmpbuf += BREAKLEN; + } + first = PR_TRUE; + if (loc) { + PORT_Memcpy(tmpbuf, loc->data, loc->len); + tmpbuf += (loc->len); + first = PR_FALSE; + } + if (state) { + if (!first) { + PORT_Memcpy(tmpbuf, COMMA, COMMALEN); + tmpbuf += COMMALEN; + } + PORT_Memcpy(tmpbuf, state->data, state->len); + tmpbuf += (state->len); + first = PR_FALSE; + } + if (country) { + if (!first) { + PORT_Memcpy(tmpbuf, COMMA, COMMALEN); + tmpbuf += COMMALEN; + } + PORT_Memcpy(tmpbuf, country->data, country->len); + tmpbuf += (country->len); + first = PR_FALSE; + } + if (!first) { + PORT_Memcpy(tmpbuf, BREAK, BREAKLEN); + tmpbuf += BREAKLEN; + } + + *tmpbuf = 0; + +/* fall through and clean */ +loser: + if (cn) { + SECITEM_FreeItem(cn, PR_TRUE); + } + if (email) { + SECITEM_FreeItem(email, PR_TRUE); + } + for (i = ou_count - 1; i >= 0; i--) { + SECITEM_FreeItem(orgunit[i], PR_TRUE); + } + if (dq) { + SECITEM_FreeItem(dq, PR_TRUE); + } + if (org) { + SECITEM_FreeItem(org, PR_TRUE); + } + for (i = dc_count - 1; i >= 0; i--) { + SECITEM_FreeItem(dc[i], PR_TRUE); + } + if (loc) { + SECITEM_FreeItem(loc, PR_TRUE); + } + if (state) { + SECITEM_FreeItem(state, PR_TRUE); + } + if (country) { + SECITEM_FreeItem(country, PR_TRUE); + } + + return (buf); +} diff --git a/security/nss/lib/certhigh/certreq.c b/security/nss/lib/certhigh/certreq.c new file mode 100644 index 0000000000..2ab4f1ab7b --- /dev/null +++ b/security/nss/lib/certhigh/certreq.c @@ -0,0 +1,331 @@ +/* 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 "cert.h" +#include "certt.h" +#include "secder.h" +#include "keyhi.h" +#include "secitem.h" +#include "secasn1.h" +#include "secerr.h" + +SEC_ASN1_MKSUB(SEC_AnyTemplate) + +const SEC_ASN1Template CERT_AttributeTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(CERTAttribute) }, + { SEC_ASN1_OBJECT_ID, offsetof(CERTAttribute, attrType) }, + { SEC_ASN1_SET_OF | SEC_ASN1_XTRN, offsetof(CERTAttribute, attrValue), + SEC_ASN1_SUB(SEC_AnyTemplate) }, + { 0 } +}; + +const SEC_ASN1Template CERT_SetOfAttributeTemplate[] = { + { SEC_ASN1_SET_OF, 0, CERT_AttributeTemplate }, +}; + +const SEC_ASN1Template CERT_CertificateRequestTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(CERTCertificateRequest) }, + { SEC_ASN1_INTEGER, + offsetof(CERTCertificateRequest, version) }, + { SEC_ASN1_INLINE, + offsetof(CERTCertificateRequest, subject), + CERT_NameTemplate }, + { SEC_ASN1_INLINE, + offsetof(CERTCertificateRequest, subjectPublicKeyInfo), + CERT_SubjectPublicKeyInfoTemplate }, + { SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(CERTCertificateRequest, attributes), + CERT_SetOfAttributeTemplate }, + { 0 } +}; + +SEC_ASN1_CHOOSER_IMPLEMENT(CERT_CertificateRequestTemplate) + +CERTCertificate * +CERT_CreateCertificate(unsigned long serialNumber, + CERTName *issuer, + CERTValidity *validity, + CERTCertificateRequest *req) +{ + CERTCertificate *c; + int rv; + PLArenaPool *arena; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + + if (!arena) { + return (0); + } + + c = (CERTCertificate *)PORT_ArenaZAlloc(arena, sizeof(CERTCertificate)); + + if (!c) { + PORT_FreeArena(arena, PR_FALSE); + return 0; + } + + c->referenceCount = 1; + c->arena = arena; + + /* + * Default is a plain version 1. + * If extensions are added, it will get changed as appropriate. + */ + rv = DER_SetUInteger(arena, &c->version, SEC_CERTIFICATE_VERSION_1); + if (rv) + goto loser; + + rv = DER_SetUInteger(arena, &c->serialNumber, serialNumber); + if (rv) + goto loser; + + rv = CERT_CopyName(arena, &c->issuer, issuer); + if (rv) + goto loser; + + rv = CERT_CopyValidity(arena, &c->validity, validity); + if (rv) + goto loser; + + rv = CERT_CopyName(arena, &c->subject, &req->subject); + if (rv) + goto loser; + rv = SECKEY_CopySubjectPublicKeyInfo(arena, &c->subjectPublicKeyInfo, + &req->subjectPublicKeyInfo); + if (rv) + goto loser; + + return c; + +loser: + CERT_DestroyCertificate(c); + return 0; +} + +/************************************************************************/ +/* It's clear from the comments that the original author of this + * function expected the template for certificate requests to treat + * the attributes as a SET OF ANY. This function expected to be + * passed an array of SECItems each of which contained an already encoded + * Attribute. But the cert request template does not treat the + * Attributes as a SET OF ANY, and AFAIK never has. Instead the template + * encodes attributes as a SET OF xxxxxxx. That is, it expects to encode + * each of the Attributes, not have them pre-encoded. Consequently an + * array of SECItems containing encoded Attributes is of no value to this + * function. But we cannot change the signature of this public function. + * It must continue to take SECItems. + * + * I have recoded this function so that each SECItem contains an + * encoded cert extension. The encoded cert extensions form the list for the + * single attribute of the cert request. In this implementation there is at most + * one attribute and it is always of type SEC_OID_PKCS9_EXTENSION_REQUEST. + */ + +CERTCertificateRequest * +CERT_CreateCertificateRequest(CERTName *subject, + CERTSubjectPublicKeyInfo *spki, + SECItem **attributes) +{ + CERTCertificateRequest *certreq; + PLArenaPool *arena; + CERTAttribute *attribute; + SECOidData *oidData; + SECStatus rv; + int i = 0; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + return NULL; + } + + certreq = PORT_ArenaZNew(arena, CERTCertificateRequest); + if (!certreq) { + PORT_FreeArena(arena, PR_FALSE); + return NULL; + } + /* below here it is safe to goto loser */ + + certreq->arena = arena; + + rv = DER_SetUInteger(arena, &certreq->version, + SEC_CERTIFICATE_REQUEST_VERSION); + if (rv != SECSuccess) + goto loser; + + rv = CERT_CopyName(arena, &certreq->subject, subject); + if (rv != SECSuccess) + goto loser; + + rv = SECKEY_CopySubjectPublicKeyInfo(arena, + &certreq->subjectPublicKeyInfo, + spki); + if (rv != SECSuccess) + goto loser; + + certreq->attributes = PORT_ArenaZNewArray(arena, CERTAttribute *, 2); + if (!certreq->attributes) + goto loser; + + /* Copy over attribute information */ + if (!attributes || !attributes[0]) { + /* + ** Invent empty attribute information. According to the + ** pkcs#10 spec, attributes has this ASN.1 type: + ** + ** attributes [0] IMPLICIT Attributes + ** + ** Which means, we should create a NULL terminated list + ** with the first entry being NULL; + */ + certreq->attributes[0] = NULL; + return certreq; + } + + /* allocate space for attributes */ + attribute = PORT_ArenaZNew(arena, CERTAttribute); + if (!attribute) + goto loser; + + oidData = SECOID_FindOIDByTag(SEC_OID_PKCS9_EXTENSION_REQUEST); + PORT_Assert(oidData); + if (!oidData) + goto loser; + rv = SECITEM_CopyItem(arena, &attribute->attrType, &oidData->oid); + if (rv != SECSuccess) + goto loser; + + for (i = 0; attributes[i] != NULL; i++) + ; + attribute->attrValue = PORT_ArenaZNewArray(arena, SECItem *, i + 1); + if (!attribute->attrValue) + goto loser; + + /* copy attributes */ + for (i = 0; attributes[i]; i++) { + /* + ** Attributes are a SetOf Attribute which implies + ** lexigraphical ordering. It is assumes that the + ** attributes are passed in sorted. If we need to + ** add functionality to sort them, there is an + ** example in the PKCS 7 code. + */ + attribute->attrValue[i] = SECITEM_ArenaDupItem(arena, attributes[i]); + if (!attribute->attrValue[i]) + goto loser; + } + + certreq->attributes[0] = attribute; + + return certreq; + +loser: + CERT_DestroyCertificateRequest(certreq); + return NULL; +} + +void +CERT_DestroyCertificateRequest(CERTCertificateRequest *req) +{ + if (req && req->arena) { + PORT_FreeArena(req->arena, PR_FALSE); + } + return; +} + +static void +setCRExt(void *o, CERTCertExtension **exts) +{ + ((CERTCertificateRequest *)o)->attributes = (struct CERTAttributeStr **)exts; +} + +/* +** Set up to start gathering cert extensions for a cert request. +** The list is created as CertExtensions and converted to an +** attribute list by CERT_FinishCRAttributes(). + */ +extern void *cert_StartExtensions(void *owner, PLArenaPool *ownerArena, + void (*setExts)(void *object, CERTCertExtension **exts)); +void * +CERT_StartCertificateRequestAttributes(CERTCertificateRequest *req) +{ + return (cert_StartExtensions((void *)req, req->arena, setCRExt)); +} + +/* +** At entry req->attributes actually contains an list of cert extensions-- +** req-attributes is overloaded until the list is DER encoded (the first +** ...EncodeItem() below). +** We turn this into an attribute list by encapsulating it +** in a PKCS 10 Attribute structure + */ +SECStatus +CERT_FinishCertificateRequestAttributes(CERTCertificateRequest *req) +{ + SECItem *extlist; + SECOidData *oidrec; + CERTAttribute *attribute; + + if (!req || !req->arena) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + if (req->attributes == NULL || req->attributes[0] == NULL) + return SECSuccess; + + extlist = SEC_ASN1EncodeItem(req->arena, NULL, &req->attributes, + SEC_ASN1_GET(CERT_SequenceOfCertExtensionTemplate)); + if (extlist == NULL) + return (SECFailure); + + oidrec = SECOID_FindOIDByTag(SEC_OID_PKCS9_EXTENSION_REQUEST); + if (oidrec == NULL) + return SECFailure; + + /* now change the list of cert extensions into a list of attributes + */ + req->attributes = PORT_ArenaZNewArray(req->arena, CERTAttribute *, 2); + + attribute = PORT_ArenaZNew(req->arena, CERTAttribute); + + if (req->attributes == NULL || attribute == NULL || + SECITEM_CopyItem(req->arena, &attribute->attrType, &oidrec->oid) != 0) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return SECFailure; + } + attribute->attrValue = PORT_ArenaZNewArray(req->arena, SECItem *, 2); + + if (attribute->attrValue == NULL) + return SECFailure; + + attribute->attrValue[0] = extlist; + attribute->attrValue[1] = NULL; + req->attributes[0] = attribute; + req->attributes[1] = NULL; + + return SECSuccess; +} + +SECStatus +CERT_GetCertificateRequestExtensions(CERTCertificateRequest *req, + CERTCertExtension ***exts) +{ + if (req == NULL || exts == NULL) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (req->attributes == NULL || *req->attributes == NULL) + return SECSuccess; + + if ((*req->attributes)->attrValue == NULL) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + return (SEC_ASN1DecodeItem(req->arena, exts, + SEC_ASN1_GET(CERT_SequenceOfCertExtensionTemplate), + (*req->attributes)->attrValue[0])); +} diff --git a/security/nss/lib/certhigh/certvfy.c b/security/nss/lib/certhigh/certvfy.c new file mode 100644 index 0000000000..8e742279f3 --- /dev/null +++ b/security/nss/lib/certhigh/certvfy.c @@ -0,0 +1,2158 @@ +/* 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 "nspr.h" +#include "secerr.h" +#include "secport.h" +#include "seccomon.h" +#include "secoid.h" +#include "genname.h" +#include "keyhi.h" +#include "cert.h" +#include "certdb.h" +#include "certi.h" +#include "cryptohi.h" + +#ifndef NSS_DISABLE_LIBPKIX +#include "pkix.h" +#include "pkix_pl_cert.h" +#else +#include "nss.h" +#endif /* NSS_DISABLE_LIBPKIX */ + +#include "nsspki.h" +#include "pkitm.h" +#include "pkim.h" +#include "pki3hack.h" +#include "base.h" +#include "keyi.h" + +/* + * Check the validity times of a certificate + */ +SECStatus +CERT_CertTimesValid(CERTCertificate *c) +{ + SECCertTimeValidity valid = CERT_CheckCertValidTimes(c, PR_Now(), PR_TRUE); + return (valid == secCertTimeValid) ? SECSuccess : SECFailure; +} + +static SECStatus +checkKeyParams(const SECAlgorithmID *sigAlgorithm, const SECKEYPublicKey *key) +{ + SECStatus rv; + SECOidTag sigAlg; + SECOidTag curve; + PRUint32 policyFlags = 0; + PRInt32 minLen, len; + + sigAlg = SECOID_GetAlgorithmTag(sigAlgorithm); + + switch (sigAlg) { + case SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE: + case SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE: + case SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE: + case SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE: + case SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE: + if (key->keyType != ecKey) { + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + return SECFailure; + } + + curve = SECKEY_GetECCOid(&key->u.ec.DEREncodedParams); + if (curve != 0) { + if (NSS_GetAlgorithmPolicy(curve, &policyFlags) == SECFailure || + !(policyFlags & NSS_USE_ALG_IN_CERT_SIGNATURE)) { + PORT_SetError(SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED); + return SECFailure; + } + return SECSuccess; + } + PORT_SetError(SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE); + return SECFailure; + + case SEC_OID_PKCS1_RSA_PSS_SIGNATURE: { + PORTCheapArenaPool tmpArena; + SECOidTag hashAlg; + SECOidTag maskHashAlg; + + PORT_InitCheapArena(&tmpArena, DER_DEFAULT_CHUNKSIZE); + rv = sec_DecodeRSAPSSParams(&tmpArena.arena, + &sigAlgorithm->parameters, + &hashAlg, &maskHashAlg, NULL); + PORT_DestroyCheapArena(&tmpArena); + if (rv != SECSuccess) { + return SECFailure; + } + + if (NSS_GetAlgorithmPolicy(hashAlg, &policyFlags) == SECSuccess && + !(policyFlags & NSS_USE_ALG_IN_CERT_SIGNATURE)) { + PORT_SetError(SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED); + return SECFailure; + } + if (NSS_GetAlgorithmPolicy(maskHashAlg, &policyFlags) == SECSuccess && + !(policyFlags & NSS_USE_ALG_IN_CERT_SIGNATURE)) { + PORT_SetError(SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED); + return SECFailure; + } + } + /* fall through to RSA key checking */ + case SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION: + case SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION: + case SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION: + case SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION: + case SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION: + case SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE: + case SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE: + if (key->keyType != rsaKey && key->keyType != rsaPssKey) { + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + return SECFailure; + } + + len = 8 * key->u.rsa.modulus.len; + + rv = NSS_OptionGet(NSS_RSA_MIN_KEY_SIZE, &minLen); + if (rv != SECSuccess) { + return SECFailure; + } + + if (len < minLen) { + return SECFailure; + } + + return SECSuccess; + case SEC_OID_ANSIX9_DSA_SIGNATURE: + case SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST: + case SEC_OID_BOGUS_DSA_SIGNATURE_WITH_SHA1_DIGEST: + case SEC_OID_SDN702_DSA_SIGNATURE: + case SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST: + case SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST: + if (key->keyType != dsaKey) { + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + return SECFailure; + } + + len = 8 * key->u.dsa.params.prime.len; + + rv = NSS_OptionGet(NSS_DSA_MIN_KEY_SIZE, &minLen); + if (rv != SECSuccess) { + return SECFailure; + } + + if (len < minLen) { + return SECFailure; + } + + return SECSuccess; + default: + return SECSuccess; + } +} + +/* + * verify the signature of a signed data object with the given DER publickey + */ +SECStatus +CERT_VerifySignedDataWithPublicKey(const CERTSignedData *sd, + SECKEYPublicKey *pubKey, + void *wincx) +{ + SECStatus rv; + SECItem sig; + SECOidTag sigAlg; + SECOidTag encAlg; + SECOidTag hashAlg; + PRUint32 policyFlags; + + if (!pubKey || !sd) { + PORT_SetError(PR_INVALID_ARGUMENT_ERROR); + return SECFailure; + } + + /* Can we use this algorithm for signature verification? */ + sigAlg = SECOID_GetAlgorithmTag(&sd->signatureAlgorithm); + rv = sec_DecodeSigAlg(pubKey, sigAlg, + &sd->signatureAlgorithm.parameters, + &encAlg, &hashAlg); + if (rv != SECSuccess) { + return SECFailure; /* error is set */ + } + rv = NSS_GetAlgorithmPolicy(encAlg, &policyFlags); + if (rv == SECSuccess && + !(policyFlags & NSS_USE_ALG_IN_CERT_SIGNATURE)) { + PORT_SetError(SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED); + return SECFailure; + } + rv = NSS_GetAlgorithmPolicy(hashAlg, &policyFlags); + if (rv == SECSuccess && + !(policyFlags & NSS_USE_ALG_IN_CERT_SIGNATURE)) { + PORT_SetError(SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED); + return SECFailure; + } + rv = checkKeyParams(&sd->signatureAlgorithm, pubKey); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED); + return SECFailure; + } + + /* check the signature */ + sig = sd->signature; + /* convert sig->len from bit counts to byte count. */ + DER_ConvertBitString(&sig); + + rv = VFY_VerifyDataWithAlgorithmID(sd->data.data, sd->data.len, pubKey, + &sig, &sd->signatureAlgorithm, + &hashAlg, wincx); + if (rv != SECSuccess) { + return SECFailure; /* error is set */ + } + + /* for some algorithms, hash algorithm is only known after verification */ + rv = NSS_GetAlgorithmPolicy(hashAlg, &policyFlags); + if (rv == SECSuccess && + !(policyFlags & NSS_USE_ALG_IN_CERT_SIGNATURE)) { + PORT_SetError(SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED); + return SECFailure; + } + return SECSuccess; +} + +/* + * verify the signature of a signed data object with the given DER publickey + */ +SECStatus +CERT_VerifySignedDataWithPublicKeyInfo(CERTSignedData *sd, + CERTSubjectPublicKeyInfo *pubKeyInfo, + void *wincx) +{ + SECKEYPublicKey *pubKey; + SECStatus rv = SECFailure; + + /* get cert's public key */ + pubKey = SECKEY_ExtractPublicKey(pubKeyInfo); + if (pubKey) { + rv = CERT_VerifySignedDataWithPublicKey(sd, pubKey, wincx); + SECKEY_DestroyPublicKey(pubKey); + } + return rv; +} + +/* + * verify the signature of a signed data object with the given certificate + */ +SECStatus +CERT_VerifySignedData(CERTSignedData *sd, CERTCertificate *cert, + PRTime t, void *wincx) +{ + SECKEYPublicKey *pubKey = 0; + SECStatus rv = SECFailure; + SECCertTimeValidity validity; + + /* check the certificate's validity */ + validity = CERT_CheckCertValidTimes(cert, t, PR_FALSE); + if (validity != secCertTimeValid) { + return rv; + } + + /* get cert's public key */ + pubKey = CERT_ExtractPublicKey(cert); + if (pubKey) { + rv = CERT_VerifySignedDataWithPublicKey(sd, pubKey, wincx); + SECKEY_DestroyPublicKey(pubKey); + } + return rv; +} + +SECStatus +SEC_CheckCRL(CERTCertDBHandle *handle, CERTCertificate *cert, + CERTCertificate *caCert, PRTime t, void *wincx) +{ + return CERT_CheckCRL(cert, caCert, NULL, t, wincx); +} + +/* + * Find the issuer of a cert. Use the authorityKeyID if it exists. + */ +CERTCertificate * +CERT_FindCertIssuer(CERTCertificate *cert, PRTime validTime, SECCertUsage usage) +{ + NSSCertificate *me; + NSSTime *nssTime; + NSSTrustDomain *td; + NSSCryptoContext *cc; + NSSCertificate *chain[3]; + NSSUsage nssUsage; + PRStatus status; + + me = STAN_GetNSSCertificate(cert); + if (!me) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + nssTime = NSSTime_SetPRTime(NULL, validTime); + nssUsage.anyUsage = PR_FALSE; + nssUsage.nss3usage = usage; + nssUsage.nss3lookingForCA = PR_TRUE; + memset(chain, 0, 3 * sizeof(NSSCertificate *)); + td = STAN_GetDefaultTrustDomain(); + cc = STAN_GetDefaultCryptoContext(); + (void)NSSCertificate_BuildChain(me, nssTime, &nssUsage, NULL, + chain, 2, NULL, &status, td, cc); + nss_ZFreeIf(nssTime); + if (status == PR_SUCCESS) { + PORT_Assert(me == chain[0]); + /* if it's a root, the chain will only have one cert */ + if (!chain[1]) { + /* already has a reference from the call to BuildChain */ + return cert; + } + NSSCertificate_Destroy(chain[0]); /* the first cert in the chain */ + return STAN_GetCERTCertificate(chain[1]); /* return the 2nd */ + } + if (chain[0]) { + PORT_Assert(me == chain[0]); + NSSCertificate_Destroy(chain[0]); /* the first cert in the chain */ + } + PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER); + return NULL; +} + +/* + * return required trust flags for various cert usages for CAs + */ +SECStatus +CERT_TrustFlagsForCACertUsage(SECCertUsage usage, + unsigned int *retFlags, + SECTrustType *retTrustType) +{ + unsigned int requiredFlags; + SECTrustType trustType; + + switch (usage) { + case certUsageSSLClient: + requiredFlags = CERTDB_TRUSTED_CLIENT_CA; + trustType = trustSSL; + break; + case certUsageSSLServer: + case certUsageSSLCA: + requiredFlags = CERTDB_TRUSTED_CA; + trustType = trustSSL; + break; + case certUsageIPsec: + requiredFlags = CERTDB_TRUSTED_CA; + trustType = trustSSL; + break; + case certUsageSSLServerWithStepUp: + requiredFlags = CERTDB_TRUSTED_CA | CERTDB_GOVT_APPROVED_CA; + trustType = trustSSL; + break; + case certUsageEmailSigner: + case certUsageEmailRecipient: + requiredFlags = CERTDB_TRUSTED_CA; + trustType = trustEmail; + break; + case certUsageObjectSigner: + requiredFlags = CERTDB_TRUSTED_CA; + trustType = trustObjectSigning; + break; + case certUsageVerifyCA: + case certUsageAnyCA: + case certUsageStatusResponder: + requiredFlags = CERTDB_TRUSTED_CA; + trustType = trustTypeNone; + break; + default: + PORT_Assert(0); + goto loser; + } + if (retFlags != NULL) { + *retFlags = requiredFlags; + } + if (retTrustType != NULL) { + *retTrustType = trustType; + } + + return (SECSuccess); +loser: + return (SECFailure); +} + +void +cert_AddToVerifyLog(CERTVerifyLog *log, CERTCertificate *cert, long error, + unsigned int depth, void *arg) +{ + CERTVerifyLogNode *node, *tnode; + + PORT_Assert(log != NULL); + + node = (CERTVerifyLogNode *)PORT_ArenaAlloc(log->arena, + sizeof(CERTVerifyLogNode)); + if (node != NULL) { + node->cert = CERT_DupCertificate(cert); + node->error = error; + node->depth = depth; + node->arg = arg; + + if (log->tail == NULL) { + /* empty list */ + log->head = log->tail = node; + node->prev = NULL; + node->next = NULL; + } else if (depth >= log->tail->depth) { + /* add to tail */ + node->prev = log->tail; + log->tail->next = node; + log->tail = node; + node->next = NULL; + } else if (depth < log->head->depth) { + /* add at head */ + node->prev = NULL; + node->next = log->head; + log->head->prev = node; + log->head = node; + } else { + /* add in middle */ + tnode = log->tail; + while (tnode != NULL) { + if (depth >= tnode->depth) { + /* insert after tnode */ + node->prev = tnode; + node->next = tnode->next; + tnode->next->prev = node; + tnode->next = node; + break; + } + + tnode = tnode->prev; + } + } + + log->count++; + } + return; +} + +#define EXIT_IF_NOT_LOGGING(log) \ + if (log == NULL) { \ + goto loser; \ + } + +#define LOG_ERROR_OR_EXIT(log, cert, depth, arg) \ + if (log != NULL) { \ + cert_AddToVerifyLog(log, cert, PORT_GetError(), depth, \ + (void *)(PRWord)arg); \ + } else { \ + goto loser; \ + } + +#define LOG_ERROR(log, cert, depth, arg) \ + if (log != NULL) { \ + cert_AddToVerifyLog(log, cert, PORT_GetError(), depth, \ + (void *)(PRWord)arg); \ + } + +/* /C=CN/O=WoSign CA Limited/CN=CA \xE6\xB2\x83\xE9\x80\x9A\xE6\xA0\xB9\xE8\xAF\x81\xE4\xB9\xA6 + * Using a consistent naming convention, this would actually be called + * 'CA沃通根证书DN', but since GCC 6.2.1 apparently can't handle UTF-8 + * identifiers, this will have to do. + */ +static const unsigned char CAWoSignRootDN[72] = { + 0x30, 0x46, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, + 0x43, 0x4E, 0x31, 0x1A, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x11, + 0x57, 0x6F, 0x53, 0x69, 0x67, 0x6E, 0x20, 0x43, 0x41, 0x20, 0x4C, 0x69, 0x6D, + 0x69, 0x74, 0x65, 0x64, 0x31, 0x1B, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, + 0x0C, 0x12, 0x43, 0x41, 0x20, 0xE6, 0xB2, 0x83, 0xE9, 0x80, 0x9A, 0xE6, 0xA0, + 0xB9, 0xE8, 0xAF, 0x81, 0xE4, 0xB9, 0xA6 +}; + +/* /C=CN/O=WoSign CA Limited/CN=CA WoSign ECC Root */ +static const unsigned char CAWoSignECCRootDN[72] = { + 0x30, 0x46, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, + 0x43, 0x4E, 0x31, 0x1A, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x11, + 0x57, 0x6F, 0x53, 0x69, 0x67, 0x6E, 0x20, 0x43, 0x41, 0x20, 0x4C, 0x69, 0x6D, + 0x69, 0x74, 0x65, 0x64, 0x31, 0x1B, 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x03, + 0x13, 0x12, 0x43, 0x41, 0x20, 0x57, 0x6F, 0x53, 0x69, 0x67, 0x6E, 0x20, 0x45, + 0x43, 0x43, 0x20, 0x52, 0x6F, 0x6F, 0x74 +}; + +/* /C=CN/O=WoSign CA Limited/CN=Certification Authority of WoSign */ +static const unsigned char CertificationAuthorityofWoSignDN[87] = { + 0x30, 0x55, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, + 0x43, 0x4E, 0x31, 0x1A, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x11, + 0x57, 0x6F, 0x53, 0x69, 0x67, 0x6E, 0x20, 0x43, 0x41, 0x20, 0x4C, 0x69, 0x6D, + 0x69, 0x74, 0x65, 0x64, 0x31, 0x2A, 0x30, 0x28, 0x06, 0x03, 0x55, 0x04, 0x03, + 0x13, 0x21, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6F, 0x6E, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6F, 0x72, 0x69, 0x74, 0x79, 0x20, + 0x6F, 0x66, 0x20, 0x57, 0x6F, 0x53, 0x69, 0x67, 0x6E +}; + +/* /C=CN/O=WoSign CA Limited/CN=Certification Authority of WoSign G2 */ +static const unsigned char CertificationAuthorityofWoSignG2DN[90] = { + 0x30, 0x58, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, + 0x43, 0x4E, 0x31, 0x1A, 0x30, 0x18, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x11, + 0x57, 0x6F, 0x53, 0x69, 0x67, 0x6E, 0x20, 0x43, 0x41, 0x20, 0x4C, 0x69, 0x6D, + 0x69, 0x74, 0x65, 0x64, 0x31, 0x2D, 0x30, 0x2B, 0x06, 0x03, 0x55, 0x04, 0x03, + 0x13, 0x24, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6F, 0x6E, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6F, 0x72, 0x69, 0x74, 0x79, 0x20, + 0x6F, 0x66, 0x20, 0x57, 0x6F, 0x53, 0x69, 0x67, 0x6E, 0x20, 0x47, 0x32 +}; + +/* /C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority */ +static const unsigned char StartComCertificationAuthorityDN[127] = { + 0x30, 0x7D, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, + 0x49, 0x4C, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x0D, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6F, 0x6D, 0x20, 0x4C, 0x74, 0x64, 0x2E, + 0x31, 0x2B, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x0B, 0x13, 0x22, 0x53, 0x65, + 0x63, 0x75, 0x72, 0x65, 0x20, 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6C, 0x20, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x53, + 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67, 0x31, 0x29, 0x30, 0x27, 0x06, 0x03, 0x55, + 0x04, 0x03, 0x13, 0x20, 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6F, 0x6D, 0x20, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, + 0x20, 0x41, 0x75, 0x74, 0x68, 0x6F, 0x72, 0x69, 0x74, 0x79 +}; + +/* /C=IL/O=StartCom Ltd./CN=StartCom Certification Authority G2 */ +static const unsigned char StartComCertificationAuthorityG2DN[85] = { + 0x30, 0x53, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, + 0x49, 0x4C, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x0D, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6F, 0x6D, 0x20, 0x4C, 0x74, 0x64, 0x2E, + 0x31, 0x2C, 0x30, 0x2A, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x23, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x43, 0x6F, 0x6D, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6F, + 0x72, 0x69, 0x74, 0x79, 0x20, 0x47, 0x32 +}; + +struct DataAndLength { + const unsigned char *data; + PRUint32 len; +}; + +static const struct DataAndLength StartComAndWoSignDNs[] = { + { CAWoSignRootDN, + sizeof(CAWoSignRootDN) }, + { CAWoSignECCRootDN, + sizeof(CAWoSignECCRootDN) }, + { CertificationAuthorityofWoSignDN, + sizeof(CertificationAuthorityofWoSignDN) }, + { CertificationAuthorityofWoSignG2DN, + sizeof(CertificationAuthorityofWoSignG2DN) }, + { StartComCertificationAuthorityDN, + sizeof(StartComCertificationAuthorityDN) }, + { StartComCertificationAuthorityG2DN, + sizeof(StartComCertificationAuthorityG2DN) }, +}; + +static PRBool +CertIsStartComOrWoSign(const CERTCertificate *cert) +{ + int i; + const struct DataAndLength *dn = StartComAndWoSignDNs; + + for (i = 0; i < sizeof(StartComAndWoSignDNs) / sizeof(struct DataAndLength); ++i, dn++) { + if (cert->derSubject.len == dn->len && + memcmp(cert->derSubject.data, dn->data, dn->len) == 0) { + return PR_TRUE; + } + } + return PR_FALSE; +} + +SECStatus +isIssuerCertAllowedAtCertIssuanceTime(CERTCertificate *issuerCert, + CERTCertificate *referenceCert) +{ + if (!issuerCert || !referenceCert) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (CertIsStartComOrWoSign(issuerCert)) { + /* PRTime is microseconds since the epoch, whereas JS time is milliseconds. + * (new Date("2016-10-21T00:00:00Z")).getTime() * 1000 + */ + static const PRTime OCTOBER_21_2016 = 1477008000000000; + + PRTime notBefore, notAfter; + SECStatus rv; + + rv = CERT_GetCertTimes(referenceCert, ¬Before, ¬After); + if (rv != SECSuccess) + return rv; + + if (notBefore > OCTOBER_21_2016) { + return SECFailure; + } + } + + return SECSuccess; +} + +static SECStatus +cert_VerifyCertChainOld(CERTCertDBHandle *handle, CERTCertificate *cert, + PRBool checkSig, PRBool *sigerror, + SECCertUsage certUsage, PRTime t, void *wincx, + CERTVerifyLog *log, PRBool *revoked) +{ + SECTrustType trustType; + CERTBasicConstraints basicConstraint; + CERTCertificate *issuerCert = NULL; + CERTCertificate *subjectCert = NULL; + CERTCertificate *badCert = NULL; + PRBool isca; + SECStatus rv; + SECStatus rvFinal = SECSuccess; + int count; + int currentPathLen = 0; + int pathLengthLimit = CERT_UNLIMITED_PATH_CONSTRAINT; + unsigned int caCertType; + unsigned int requiredCAKeyUsage; + unsigned int requiredFlags; + PLArenaPool *arena = NULL; + CERTGeneralName *namesList = NULL; + CERTCertificate **certsList = NULL; + int certsListLen = 16; + int namesCount = 0; + PRBool subjectCertIsSelfIssued; + CERTCertTrust issuerTrust; + + if (revoked) { + *revoked = PR_FALSE; + } + + if (CERT_KeyUsageAndTypeForCertUsage(certUsage, PR_TRUE, + &requiredCAKeyUsage, + &caCertType) != + SECSuccess) { + PORT_Assert(0); + EXIT_IF_NOT_LOGGING(log); + requiredCAKeyUsage = 0; + caCertType = 0; + } + + switch (certUsage) { + case certUsageSSLClient: + case certUsageSSLServer: + case certUsageIPsec: + case certUsageSSLCA: + case certUsageSSLServerWithStepUp: + case certUsageEmailSigner: + case certUsageEmailRecipient: + case certUsageObjectSigner: + case certUsageVerifyCA: + case certUsageAnyCA: + case certUsageStatusResponder: + if (CERT_TrustFlagsForCACertUsage(certUsage, &requiredFlags, + &trustType) != SECSuccess) { + PORT_Assert(0); + EXIT_IF_NOT_LOGGING(log); + /* XXX continuing with requiredFlags = 0 seems wrong. It'll + * cause the following test to be true incorrectly: + * flags = SEC_GET_TRUST_FLAGS(issuerCert->trust, trustType); + * if (( flags & requiredFlags ) == requiredFlags) { + * rv = rvFinal; + * goto done; + * } + * There are three other instances of this problem. + */ + requiredFlags = 0; + trustType = trustSSL; + } + break; + default: + PORT_Assert(0); + EXIT_IF_NOT_LOGGING(log); + requiredFlags = 0; + trustType = trustSSL; /* This used to be 0, but we need something + * that matches the enumeration type. + */ + caCertType = 0; + } + + subjectCert = CERT_DupCertificate(cert); + if (subjectCert == NULL) { + goto loser; + } + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + goto loser; + } + + certsList = PORT_ZNewArray(CERTCertificate *, certsListLen); + if (certsList == NULL) + goto loser; + + /* RFC 3280 says that the name constraints will apply to the names + ** in the leaf (EE) cert, whether it is self issued or not, so + ** we pretend that it is not. + */ + subjectCertIsSelfIssued = PR_FALSE; + for (count = 0; count < CERT_MAX_CERT_CHAIN; count++) { + PRBool validCAOverride = PR_FALSE; + + /* Construct a list of names for the current and all previous + * certifcates (except leaf (EE) certs, root CAs, and self-issued + * intermediate CAs) to be verified against the name constraints + * extension of the issuer certificate. + */ + if (subjectCertIsSelfIssued == PR_FALSE) { + CERTGeneralName *subjectNameList; + int subjectNameListLen; + int i; + PRBool getSubjectCN = (!count && + (certUsage == certUsageSSLServer || certUsage == certUsageIPsec)); + subjectNameList = + CERT_GetConstrainedCertificateNames(subjectCert, arena, + getSubjectCN); + if (!subjectNameList) + goto loser; + subjectNameListLen = CERT_GetNamesLength(subjectNameList); + if (!subjectNameListLen) + goto loser; + if (certsListLen <= namesCount + subjectNameListLen) { + CERTCertificate **tmpCertsList; + certsListLen = (namesCount + subjectNameListLen) * 2; + tmpCertsList = + (CERTCertificate **)PORT_Realloc(certsList, + certsListLen * + sizeof(CERTCertificate *)); + if (tmpCertsList == NULL) { + goto loser; + } + certsList = tmpCertsList; + } + for (i = 0; i < subjectNameListLen; i++) { + certsList[namesCount + i] = subjectCert; + } + namesCount += subjectNameListLen; + namesList = cert_CombineNamesLists(namesList, subjectNameList); + } + + /* check if the cert has an unsupported critical extension */ + if (subjectCert->options.bits.hasUnsupportedCriticalExt) { + PORT_SetError(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION); + LOG_ERROR_OR_EXIT(log, subjectCert, count, 0); + } + + /* check that the signatureAlgorithm field of the certificate + * matches the signature field of the tbsCertificate */ + if (SECOID_CompareAlgorithmID( + &subjectCert->signatureWrap.signatureAlgorithm, + &subjectCert->signature)) { + PORT_SetError(SEC_ERROR_ALGORITHM_MISMATCH); + LOG_ERROR(log, subjectCert, count, 0); + goto loser; + } + + /* find the certificate of the issuer */ + issuerCert = CERT_FindCertIssuer(subjectCert, t, certUsage); + if (!issuerCert) { + PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER); + LOG_ERROR(log, subjectCert, count, 0); + goto loser; + } + + /* verify the signature on the cert */ + if (checkSig) { + rv = CERT_VerifySignedData(&subjectCert->signatureWrap, + issuerCert, t, wincx); + + if (rv != SECSuccess) { + if (sigerror) { + *sigerror = PR_TRUE; + } + if (PORT_GetError() == SEC_ERROR_EXPIRED_CERTIFICATE) { + PORT_SetError(SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE); + LOG_ERROR_OR_EXIT(log, issuerCert, count + 1, 0); + } else { + if (PORT_GetError() != + SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED) { + PORT_SetError(SEC_ERROR_BAD_SIGNATURE); + } + LOG_ERROR_OR_EXIT(log, subjectCert, count, 0); + } + } + } + + /* If the basicConstraint extension is included in an immediate CA + * certificate, make sure that the isCA flag is on. If the + * pathLenConstraint component exists, it must be greater than the + * number of CA certificates we have seen so far. If the extension + * is omitted, we will assume that this is a CA certificate with + * an unlimited pathLenConstraint (since it already passes the + * netscape-cert-type extension checking). + */ + + rv = CERT_FindBasicConstraintExten(issuerCert, &basicConstraint); + if (rv != SECSuccess) { + if (PORT_GetError() != SEC_ERROR_EXTENSION_NOT_FOUND) { + LOG_ERROR_OR_EXIT(log, issuerCert, count + 1, 0); + } + pathLengthLimit = CERT_UNLIMITED_PATH_CONSTRAINT; + /* no basic constraints found, we aren't (yet) a CA. */ + isca = PR_FALSE; + } else { + if (basicConstraint.isCA == PR_FALSE) { + PORT_SetError(SEC_ERROR_CA_CERT_INVALID); + LOG_ERROR_OR_EXIT(log, issuerCert, count + 1, 0); + } + pathLengthLimit = basicConstraint.pathLenConstraint; + isca = PR_TRUE; + } + /* make sure that the path len constraint is properly set.*/ + if (pathLengthLimit >= 0 && currentPathLen > pathLengthLimit) { + PORT_SetError(SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID); + LOG_ERROR_OR_EXIT(log, issuerCert, count + 1, pathLengthLimit); + } + + /* make sure that the entire chain is within the name space of the + * current issuer certificate. + */ + rv = CERT_CompareNameSpace(issuerCert, namesList, certsList, + arena, &badCert); + if (rv != SECSuccess || badCert != NULL) { + PORT_SetError(SEC_ERROR_CERT_NOT_IN_NAME_SPACE); + LOG_ERROR_OR_EXIT(log, badCert, count + 1, 0); + goto loser; + } + + rv = isIssuerCertAllowedAtCertIssuanceTime(issuerCert, cert); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_UNTRUSTED_ISSUER); + LOG_ERROR(log, issuerCert, count + 1, 0); + goto loser; + } + + /* XXX - the error logging may need to go down into CRL stuff at some + * point + */ + /* check revoked list (issuer) */ + rv = SEC_CheckCRL(handle, subjectCert, issuerCert, t, wincx); + if (rv == SECFailure) { + if (revoked) { + *revoked = PR_TRUE; + } + LOG_ERROR_OR_EXIT(log, subjectCert, count, 0); + } else if (rv == SECWouldBlock) { + /* We found something fishy, so we intend to issue an + * error to the user, but the user may wish to continue + * processing, in which case we better make sure nothing + * worse has happened... so keep cranking the loop */ + rvFinal = SECFailure; + if (revoked) { + *revoked = PR_TRUE; + } + LOG_ERROR(log, subjectCert, count, 0); + } + + if (CERT_GetCertTrust(issuerCert, &issuerTrust) == SECSuccess) { + /* we have some trust info, but this does NOT imply that this + * cert is actually trusted for any purpose. The cert may be + * explicitly UNtrusted. We won't know until we examine the + * trust bits. + */ + unsigned int flags; + + if (certUsage != certUsageAnyCA && + certUsage != certUsageStatusResponder) { + + /* + * XXX This choice of trustType seems arbitrary. + */ + if (certUsage == certUsageVerifyCA) { + if (subjectCert->nsCertType & NS_CERT_TYPE_EMAIL_CA) { + trustType = trustEmail; + } else if (subjectCert->nsCertType & NS_CERT_TYPE_SSL_CA) { + trustType = trustSSL; + } else { + trustType = trustObjectSigning; + } + } + + flags = SEC_GET_TRUST_FLAGS(&issuerTrust, trustType); + if ((flags & requiredFlags) == requiredFlags) { + /* we found a trusted one, so return */ + rv = rvFinal; + goto done; + } + if (flags & CERTDB_VALID_CA) { + validCAOverride = PR_TRUE; + } + /* is it explicitly distrusted? */ + if ((flags & CERTDB_TERMINAL_RECORD) && + ((flags & (CERTDB_TRUSTED | CERTDB_TRUSTED_CA)) == 0)) { + /* untrusted -- the cert is explicitly untrusted, not + * just that it doesn't chain to a trusted cert */ + PORT_SetError(SEC_ERROR_UNTRUSTED_ISSUER); + LOG_ERROR_OR_EXIT(log, issuerCert, count + 1, flags); + } + } else { + /* Check if we have any valid trust when cheching for + * certUsageAnyCA or certUsageStatusResponder. */ + for (trustType = trustSSL; trustType < trustTypeNone; + trustType++) { + flags = SEC_GET_TRUST_FLAGS(&issuerTrust, trustType); + if ((flags & requiredFlags) == requiredFlags) { + rv = rvFinal; + goto done; + } + if (flags & CERTDB_VALID_CA) + validCAOverride = PR_TRUE; + } + /* We have 2 separate loops because we want any single trust + * bit to allow this usage to return trusted. Only if none of + * the trust bits are on do we check to see if the cert is + * untrusted */ + for (trustType = trustSSL; trustType < trustTypeNone; + trustType++) { + flags = SEC_GET_TRUST_FLAGS(&issuerTrust, trustType); + /* is it explicitly distrusted? */ + if ((flags & CERTDB_TERMINAL_RECORD) && + ((flags & (CERTDB_TRUSTED | CERTDB_TRUSTED_CA)) == 0)) { + /* untrusted -- the cert is explicitly untrusted, not + * just that it doesn't chain to a trusted cert */ + PORT_SetError(SEC_ERROR_UNTRUSTED_ISSUER); + LOG_ERROR_OR_EXIT(log, issuerCert, count + 1, flags); + } + } + } + } + + if (!validCAOverride) { + /* + * Make sure that if this is an intermediate CA in the chain that + * it was given permission by its signer to be a CA. + */ + /* + * if basicConstraints says it is a ca, then we check the + * nsCertType. If the nsCertType has any CA bits set, then + * it must have the right one. + */ + if (!isca || (issuerCert->nsCertType & NS_CERT_TYPE_CA)) { + isca = (issuerCert->nsCertType & caCertType) ? PR_TRUE : PR_FALSE; + } + + if (!isca) { + PORT_SetError(SEC_ERROR_CA_CERT_INVALID); + LOG_ERROR_OR_EXIT(log, issuerCert, count + 1, 0); + } + + /* make sure key usage allows cert signing */ + if (CERT_CheckKeyUsage(issuerCert, requiredCAKeyUsage) != SECSuccess) { + PORT_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE); + LOG_ERROR_OR_EXIT(log, issuerCert, count + 1, requiredCAKeyUsage); + } + } + + /* make sure that the issuer is not self signed. If it is, then + * stop here to prevent looping. + */ + if (issuerCert->isRoot) { + PORT_SetError(SEC_ERROR_UNTRUSTED_ISSUER); + LOG_ERROR(log, issuerCert, count + 1, 0); + goto loser; + } + /* The issuer cert will be the subject cert in the next loop. + * A cert is self-issued if its subject and issuer are equal and + * both are of non-zero length. + */ + subjectCertIsSelfIssued = (PRBool) + SECITEM_ItemsAreEqual(&issuerCert->derIssuer, + &issuerCert->derSubject) && + issuerCert->derSubject.len > + 0; + if (subjectCertIsSelfIssued == PR_FALSE) { + /* RFC 3280 says only non-self-issued intermediate CA certs + * count in path length. + */ + ++currentPathLen; + } + + CERT_DestroyCertificate(subjectCert); + subjectCert = issuerCert; + issuerCert = NULL; + } + + PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER); + LOG_ERROR(log, subjectCert, count, 0); +loser: + rv = SECFailure; +done: + if (certsList != NULL) { + PORT_Free(certsList); + } + if (issuerCert) { + CERT_DestroyCertificate(issuerCert); + } + + if (subjectCert) { + CERT_DestroyCertificate(subjectCert); + } + + if (arena != NULL) { + PORT_FreeArena(arena, PR_FALSE); + } + return rv; +} + +SECStatus +cert_VerifyCertChain(CERTCertDBHandle *handle, CERTCertificate *cert, + PRBool checkSig, PRBool *sigerror, + SECCertUsage certUsage, PRTime t, void *wincx, + CERTVerifyLog *log, PRBool *revoked) +{ + if (CERT_GetUsePKIXForValidation()) { + return cert_VerifyCertChainPkix(cert, checkSig, certUsage, t, + wincx, log, sigerror, revoked); + } + return cert_VerifyCertChainOld(handle, cert, checkSig, sigerror, + certUsage, t, wincx, log, revoked); +} + +SECStatus +CERT_VerifyCertChain(CERTCertDBHandle *handle, CERTCertificate *cert, + PRBool checkSig, SECCertUsage certUsage, PRTime t, + void *wincx, CERTVerifyLog *log) +{ + return cert_VerifyCertChain(handle, cert, checkSig, NULL, certUsage, t, + wincx, log, NULL); +} + +/* + * verify that a CA can sign a certificate with the requested usage. + */ +SECStatus +CERT_VerifyCACertForUsage(CERTCertDBHandle *handle, CERTCertificate *cert, + PRBool checkSig, SECCertUsage certUsage, PRTime t, + void *wincx, CERTVerifyLog *log) +{ + SECTrustType trustType; + CERTBasicConstraints basicConstraint; + PRBool isca; + PRBool validCAOverride = PR_FALSE; + SECStatus rv; + SECStatus rvFinal = SECSuccess; + unsigned int flags; + unsigned int caCertType; + unsigned int requiredCAKeyUsage; + unsigned int requiredFlags; + CERTCertificate *issuerCert; + CERTCertTrust certTrust; + + if (CERT_KeyUsageAndTypeForCertUsage(certUsage, PR_TRUE, + &requiredCAKeyUsage, + &caCertType) != SECSuccess) { + PORT_Assert(0); + EXIT_IF_NOT_LOGGING(log); + requiredCAKeyUsage = 0; + caCertType = 0; + } + + switch (certUsage) { + case certUsageSSLClient: + case certUsageSSLServer: + case certUsageIPsec: + case certUsageSSLCA: + case certUsageSSLServerWithStepUp: + case certUsageEmailSigner: + case certUsageEmailRecipient: + case certUsageObjectSigner: + case certUsageVerifyCA: + case certUsageStatusResponder: + if (CERT_TrustFlagsForCACertUsage(certUsage, &requiredFlags, + &trustType) != SECSuccess) { + PORT_Assert(0); + EXIT_IF_NOT_LOGGING(log); + requiredFlags = 0; + trustType = trustSSL; + } + break; + default: + PORT_Assert(0); + EXIT_IF_NOT_LOGGING(log); + requiredFlags = 0; + trustType = trustSSL; /* This used to be 0, but we need something + * that matches the enumeration type. + */ + caCertType = 0; + } + + /* If the basicConstraint extension is included in an intermmediate CA + * certificate, make sure that the isCA flag is on. If the + * pathLenConstraint component exists, it must be greater than the + * number of CA certificates we have seen so far. If the extension + * is omitted, we will assume that this is a CA certificate with + * an unlimited pathLenConstraint (since it already passes the + * netscape-cert-type extension checking). + */ + + rv = CERT_FindBasicConstraintExten(cert, &basicConstraint); + if (rv != SECSuccess) { + if (PORT_GetError() != SEC_ERROR_EXTENSION_NOT_FOUND) { + LOG_ERROR_OR_EXIT(log, cert, 0, 0); + } + /* no basic constraints found, we aren't (yet) a CA. */ + isca = PR_FALSE; + } else { + if (basicConstraint.isCA == PR_FALSE) { + PORT_SetError(SEC_ERROR_CA_CERT_INVALID); + LOG_ERROR_OR_EXIT(log, cert, 0, 0); + } + + /* can't check path length if we don't know the previous path */ + isca = PR_TRUE; + } + + if (CERT_GetCertTrust(cert, &certTrust) == SECSuccess) { + /* we have some trust info, but this does NOT imply that this + * cert is actually trusted for any purpose. The cert may be + * explicitly UNtrusted. We won't know until we examine the + * trust bits. + */ + if (certUsage == certUsageStatusResponder) { + /* Check the special case of certUsageStatusResponder */ + issuerCert = CERT_FindCertIssuer(cert, t, certUsage); + if (issuerCert) { + if (SEC_CheckCRL(handle, cert, issuerCert, t, wincx) != + SECSuccess) { + PORT_SetError(SEC_ERROR_REVOKED_CERTIFICATE); + CERT_DestroyCertificate(issuerCert); + goto loser; + } + CERT_DestroyCertificate(issuerCert); + } + /* XXX We have NOT determined that this cert is trusted. + * For years, NSS has treated this as trusted, + * but it seems incorrect. + */ + rv = rvFinal; + goto done; + } + + /* + * check the trust params of the issuer + */ + flags = SEC_GET_TRUST_FLAGS(&certTrust, trustType); + if ((flags & requiredFlags) == requiredFlags) { + /* we found a trusted one, so return */ + rv = rvFinal; + goto done; + } + if (flags & CERTDB_VALID_CA) { + validCAOverride = PR_TRUE; + } + /* is it explicitly distrusted? */ + if ((flags & CERTDB_TERMINAL_RECORD) && + ((flags & (CERTDB_TRUSTED | CERTDB_TRUSTED_CA)) == 0)) { + /* untrusted -- the cert is explicitly untrusted, not + * just that it doesn't chain to a trusted cert */ + PORT_SetError(SEC_ERROR_UNTRUSTED_CERT); + LOG_ERROR_OR_EXIT(log, cert, 0, flags); + } + } + if (!validCAOverride) { + /* + * Make sure that if this is an intermediate CA in the chain that + * it was given permission by its signer to be a CA. + */ + /* + * if basicConstraints says it is a ca, then we check the + * nsCertType. If the nsCertType has any CA bits set, then + * it must have the right one. + */ + if (!isca || (cert->nsCertType & NS_CERT_TYPE_CA)) { + isca = (cert->nsCertType & caCertType) ? PR_TRUE : PR_FALSE; + } + + if (!isca) { + PORT_SetError(SEC_ERROR_CA_CERT_INVALID); + LOG_ERROR_OR_EXIT(log, cert, 0, 0); + } + + /* make sure key usage allows cert signing */ + if (CERT_CheckKeyUsage(cert, requiredCAKeyUsage) != SECSuccess) { + PORT_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE); + LOG_ERROR_OR_EXIT(log, cert, 0, requiredCAKeyUsage); + } + } + /* make sure that the issuer is not self signed. If it is, then + * stop here to prevent looping. + */ + if (cert->isRoot) { + PORT_SetError(SEC_ERROR_UNTRUSTED_ISSUER); + LOG_ERROR(log, cert, 0, 0); + goto loser; + } + + return CERT_VerifyCertChain(handle, cert, checkSig, certUsage, t, + wincx, log); +loser: + rv = SECFailure; +done: + return rv; +} + +#define NEXT_USAGE() \ + { \ + i *= 2; \ + certUsage++; \ + continue; \ + } + +#define VALID_USAGE() \ + { \ + NEXT_USAGE(); \ + } + +#define INVALID_USAGE() \ + { \ + if (returnedUsages) { \ + *returnedUsages &= (~i); \ + } \ + if (PR_TRUE == requiredUsage) { \ + valid = SECFailure; \ + } \ + NEXT_USAGE(); \ + } + +/* + * check the leaf cert against trust and usage. + * returns success if the cert is not distrusted. If the cert is + * trusted, then the trusted bool will be true. + * returns failure if the cert is distrusted. If failure, flags + * will return the flag bits that indicated distrust. + */ +SECStatus +cert_CheckLeafTrust(CERTCertificate *cert, SECCertUsage certUsage, + unsigned int *failedFlags, PRBool *trusted) +{ + unsigned int flags; + CERTCertTrust trust; + + *failedFlags = 0; + *trusted = PR_FALSE; + + /* check trust flags to see if this cert is directly trusted */ + if (CERT_GetCertTrust(cert, &trust) == SECSuccess) { + switch (certUsage) { + case certUsageSSLClient: + case certUsageSSLServer: + case certUsageIPsec: + flags = trust.sslFlags; + + /* is the cert directly trusted or not trusted ? */ + if (flags & CERTDB_TERMINAL_RECORD) { /* the trust record is + * authoritative */ + if (flags & CERTDB_TRUSTED) { /* trust this cert */ + *trusted = PR_TRUE; + return SECSuccess; + } else { /* don't trust this cert */ + *failedFlags = flags; + return SECFailure; + } + } + break; + case certUsageSSLServerWithStepUp: + /* XXX - step up certs can't be directly trusted, only distrust */ + flags = trust.sslFlags; + if (flags & CERTDB_TERMINAL_RECORD) { /* the trust record is + * authoritative */ + if ((flags & CERTDB_TRUSTED) == 0) { + /* don't trust this cert */ + *failedFlags = flags; + return SECFailure; + } + } + break; + case certUsageSSLCA: + flags = trust.sslFlags; + if (flags & CERTDB_TERMINAL_RECORD) { /* the trust record is + * authoritative */ + if ((flags & (CERTDB_TRUSTED | CERTDB_TRUSTED_CA)) == 0) { + /* don't trust this cert */ + *failedFlags = flags; + return SECFailure; + } + } + break; + case certUsageEmailSigner: + case certUsageEmailRecipient: + flags = trust.emailFlags; + if (flags & CERTDB_TERMINAL_RECORD) { /* the trust record is + * authoritative */ + if (flags & CERTDB_TRUSTED) { /* trust this cert */ + *trusted = PR_TRUE; + return SECSuccess; + } else { /* don't trust this cert */ + *failedFlags = flags; + return SECFailure; + } + } + + break; + case certUsageObjectSigner: + flags = trust.objectSigningFlags; + + if (flags & CERTDB_TERMINAL_RECORD) { /* the trust record is + * authoritative */ + if (flags & CERTDB_TRUSTED) { /* trust this cert */ + *trusted = PR_TRUE; + return SECSuccess; + } else { /* don't trust this cert */ + *failedFlags = flags; + return SECFailure; + } + } + break; + case certUsageVerifyCA: + case certUsageStatusResponder: + flags = trust.sslFlags; + /* is the cert directly trusted or not trusted ? */ + if ((flags & (CERTDB_VALID_CA | CERTDB_TRUSTED_CA)) == + (CERTDB_VALID_CA | CERTDB_TRUSTED_CA)) { + *trusted = PR_TRUE; + return SECSuccess; + } + flags = trust.emailFlags; + /* is the cert directly trusted or not trusted ? */ + if ((flags & (CERTDB_VALID_CA | CERTDB_TRUSTED_CA)) == + (CERTDB_VALID_CA | CERTDB_TRUSTED_CA)) { + *trusted = PR_TRUE; + return SECSuccess; + } + flags = trust.objectSigningFlags; + /* is the cert directly trusted or not trusted ? */ + if ((flags & (CERTDB_VALID_CA | CERTDB_TRUSTED_CA)) == + (CERTDB_VALID_CA | CERTDB_TRUSTED_CA)) { + *trusted = PR_TRUE; + return SECSuccess; + } + /* fall through to test distrust */ + case certUsageAnyCA: + case certUsageUserCertImport: + /* do we distrust these certs explicitly */ + flags = trust.sslFlags; + if (flags & CERTDB_TERMINAL_RECORD) { /* the trust record is + * authoritative */ + if ((flags & (CERTDB_TRUSTED | CERTDB_TRUSTED_CA)) == 0) { + *failedFlags = flags; + return SECFailure; + } + } + flags = trust.emailFlags; + if (flags & CERTDB_TERMINAL_RECORD) { /* the trust record is + * authoritative */ + if ((flags & (CERTDB_TRUSTED | CERTDB_TRUSTED_CA)) == 0) { + *failedFlags = flags; + return SECFailure; + } + } + /* fall through */ + case certUsageProtectedObjectSigner: + flags = trust.objectSigningFlags; + if (flags & CERTDB_TERMINAL_RECORD) { /* the trust record is + * authoritative */ + if ((flags & (CERTDB_TRUSTED | CERTDB_TRUSTED_CA)) == 0) { + *failedFlags = flags; + return SECFailure; + } + } + break; + } + } + return SECSuccess; +} + +/* + * verify a certificate by checking if it's valid and that we + * trust the issuer. + * + * certificateUsage contains a bitfield of all cert usages that are + * required for verification to succeed + * + * a bitfield of cert usages is returned in *returnedUsages + * if requiredUsages is non-zero, the returned bitmap is only + * for those required usages, otherwise it is for all usages + * + */ +SECStatus +CERT_VerifyCertificate(CERTCertDBHandle *handle, CERTCertificate *cert, + PRBool checkSig, SECCertificateUsage requiredUsages, PRTime t, + void *wincx, CERTVerifyLog *log, SECCertificateUsage *returnedUsages) +{ + SECStatus rv; + SECStatus valid; + unsigned int requiredKeyUsage; + unsigned int requiredCertType; + unsigned int flags; + unsigned int certType; + PRBool allowOverride; + SECCertTimeValidity validity; + CERTStatusConfig *statusConfig; + PRInt32 i; + SECCertUsage certUsage = 0; + PRBool checkedOCSP = PR_FALSE; + PRBool checkAllUsages = PR_FALSE; + PRBool revoked = PR_FALSE; + PRBool sigerror = PR_FALSE; + PRBool trusted = PR_FALSE; + + if (!requiredUsages) { + /* there are no required usages, so the user probably wants to + get status for all usages */ + checkAllUsages = PR_TRUE; + } + + if (returnedUsages) { + *returnedUsages = 0; + } else { + /* we don't have a place to return status for all usages, + so we can skip checks for usages that aren't required */ + checkAllUsages = PR_FALSE; + } + valid = SECSuccess; /* start off assuming cert is valid */ + + /* make sure that the cert is valid at time t */ + allowOverride = (PRBool)((requiredUsages & certificateUsageSSLServer) || + (requiredUsages & certificateUsageSSLServerWithStepUp) || + (requiredUsages & certificateUsageIPsec)); + validity = CERT_CheckCertValidTimes(cert, t, allowOverride); + if (validity != secCertTimeValid) { + valid = SECFailure; + LOG_ERROR_OR_EXIT(log, cert, 0, validity); + } + + /* check key usage and netscape cert type */ + cert_GetCertType(cert); + certType = cert->nsCertType; + + for (i = 1; i <= certificateUsageHighest && + (SECSuccess == valid || returnedUsages || log);) { + PRBool requiredUsage = (i & requiredUsages) ? PR_TRUE : PR_FALSE; + if (PR_FALSE == requiredUsage && PR_FALSE == checkAllUsages) { + NEXT_USAGE(); + } + if (returnedUsages) { + *returnedUsages |= i; /* start off assuming this usage is valid */ + } + switch (certUsage) { + case certUsageSSLClient: + case certUsageSSLServer: + case certUsageSSLServerWithStepUp: + case certUsageSSLCA: + case certUsageEmailSigner: + case certUsageEmailRecipient: + case certUsageObjectSigner: + case certUsageStatusResponder: + case certUsageIPsec: + rv = CERT_KeyUsageAndTypeForCertUsage(certUsage, PR_FALSE, + &requiredKeyUsage, + &requiredCertType); + if (rv != SECSuccess) { + PORT_Assert(0); + /* EXIT_IF_NOT_LOGGING(log); XXX ??? */ + requiredKeyUsage = 0; + requiredCertType = 0; + INVALID_USAGE(); + } + break; + + case certUsageAnyCA: + case certUsageProtectedObjectSigner: + case certUsageUserCertImport: + case certUsageVerifyCA: + /* these usages cannot be verified */ + NEXT_USAGE(); + + default: + PORT_Assert(0); + requiredKeyUsage = 0; + requiredCertType = 0; + INVALID_USAGE(); + } + if (CERT_CheckKeyUsage(cert, requiredKeyUsage) != SECSuccess) { + if (PR_TRUE == requiredUsage) { + PORT_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE); + } + LOG_ERROR(log, cert, 0, requiredKeyUsage); + INVALID_USAGE(); + } + if (!(certType & requiredCertType)) { + if (PR_TRUE == requiredUsage) { + PORT_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE); + } + LOG_ERROR(log, cert, 0, requiredCertType); + INVALID_USAGE(); + } + + rv = cert_CheckLeafTrust(cert, certUsage, &flags, &trusted); + if (rv == SECFailure) { + if (PR_TRUE == requiredUsage) { + PORT_SetError(SEC_ERROR_UNTRUSTED_CERT); + } + LOG_ERROR(log, cert, 0, flags); + INVALID_USAGE(); + } else if (trusted) { + VALID_USAGE(); + } + + if (PR_TRUE == revoked || PR_TRUE == sigerror) { + INVALID_USAGE(); + } + + rv = cert_VerifyCertChain(handle, cert, + checkSig, &sigerror, + certUsage, t, wincx, log, + &revoked); + + if (rv != SECSuccess) { + /* EXIT_IF_NOT_LOGGING(log); XXX ???? */ + INVALID_USAGE(); + } + + /* + * Check OCSP revocation status, but only if the cert we are checking + * is not a status responder itself. We only do this in the case + * where we checked the cert chain (above); explicit trust "wins" + * (avoids status checking, just as it avoids CRL checking) by + * bypassing this code. + */ + + if (PR_FALSE == checkedOCSP) { + checkedOCSP = PR_TRUE; /* only check OCSP once */ + statusConfig = CERT_GetStatusConfig(handle); + if (requiredUsages != certificateUsageStatusResponder && + statusConfig != NULL) { + if (statusConfig->statusChecker != NULL) { + rv = (*statusConfig->statusChecker)(handle, cert, + t, wincx); + if (rv != SECSuccess) { + LOG_ERROR(log, cert, 0, 0); + revoked = PR_TRUE; + INVALID_USAGE(); + } + } + } + } + + NEXT_USAGE(); + } + +loser: + return (valid); +} + +SECStatus +CERT_VerifyCert(CERTCertDBHandle *handle, CERTCertificate *cert, + PRBool checkSig, SECCertUsage certUsage, PRTime t, + void *wincx, CERTVerifyLog *log) +{ + return cert_VerifyCertWithFlags(handle, cert, checkSig, certUsage, t, + CERT_VERIFYCERT_USE_DEFAULTS, wincx, log); +} + +SECStatus +cert_VerifyCertWithFlags(CERTCertDBHandle *handle, CERTCertificate *cert, + PRBool checkSig, SECCertUsage certUsage, PRTime t, + PRUint32 flags, void *wincx, CERTVerifyLog *log) +{ + SECStatus rv; + unsigned int requiredKeyUsage; + unsigned int requiredCertType; + unsigned int failedFlags; + unsigned int certType; + PRBool trusted; + PRBool allowOverride; + SECCertTimeValidity validity; + CERTStatusConfig *statusConfig; + +#ifdef notdef + /* check if this cert is in the Evil list */ + rv = CERT_CheckForEvilCert(cert); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_REVOKED_CERTIFICATE); + LOG_ERROR_OR_EXIT(log, cert, 0, 0); + } +#endif + + /* make sure that the cert is valid at time t */ + allowOverride = (PRBool)((certUsage == certUsageSSLServer) || + (certUsage == certUsageSSLServerWithStepUp) || + (certUsage == certUsageIPsec)); + validity = CERT_CheckCertValidTimes(cert, t, allowOverride); + if (validity != secCertTimeValid) { + LOG_ERROR_OR_EXIT(log, cert, 0, validity); + } + + /* check key usage and netscape cert type */ + cert_GetCertType(cert); + certType = cert->nsCertType; + switch (certUsage) { + case certUsageSSLClient: + case certUsageSSLServer: + case certUsageSSLServerWithStepUp: + case certUsageIPsec: + case certUsageSSLCA: + case certUsageEmailSigner: + case certUsageEmailRecipient: + case certUsageObjectSigner: + case certUsageStatusResponder: + rv = CERT_KeyUsageAndTypeForCertUsage(certUsage, PR_FALSE, + &requiredKeyUsage, + &requiredCertType); + if (rv != SECSuccess) { + PORT_Assert(0); + EXIT_IF_NOT_LOGGING(log); + requiredKeyUsage = 0; + requiredCertType = 0; + } + break; + case certUsageVerifyCA: + case certUsageAnyCA: + requiredKeyUsage = KU_KEY_CERT_SIGN; + requiredCertType = NS_CERT_TYPE_CA; + if (!(certType & NS_CERT_TYPE_CA)) { + certType |= NS_CERT_TYPE_CA; + } + break; + default: + PORT_Assert(0); + EXIT_IF_NOT_LOGGING(log); + requiredKeyUsage = 0; + requiredCertType = 0; + } + if (CERT_CheckKeyUsage(cert, requiredKeyUsage) != SECSuccess) { + PORT_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE); + LOG_ERROR_OR_EXIT(log, cert, 0, requiredKeyUsage); + } + if (!(certType & requiredCertType)) { + PORT_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE); + LOG_ERROR_OR_EXIT(log, cert, 0, requiredCertType); + } + + rv = cert_CheckLeafTrust(cert, certUsage, &failedFlags, &trusted); + if (rv == SECFailure) { + PORT_SetError(SEC_ERROR_UNTRUSTED_CERT); + LOG_ERROR_OR_EXIT(log, cert, 0, failedFlags); + } else if (trusted) { + goto done; + } + + rv = CERT_VerifyCertChain(handle, cert, checkSig, certUsage, + t, wincx, log); + if (rv != SECSuccess) { + EXIT_IF_NOT_LOGGING(log); + } + + /* + * Check revocation status, but only if the cert we are checking is not a + * status responder itself and the caller did not ask us to skip the check. + * We only do this in the case where we checked the cert chain (above); + * explicit trust "wins" (avoids status checking, just as it avoids CRL + * checking, which is all done inside VerifyCertChain) by bypassing this + * code. + */ + if (!(flags & CERT_VERIFYCERT_SKIP_OCSP) && + certUsage != certUsageStatusResponder) { + statusConfig = CERT_GetStatusConfig(handle); + if (statusConfig && statusConfig->statusChecker) { + rv = (*statusConfig->statusChecker)(handle, cert, + t, wincx); + if (rv != SECSuccess) { + LOG_ERROR_OR_EXIT(log, cert, 0, 0); + } + } + } + +done: + if (log && log->head) { + return SECFailure; + } + return (SECSuccess); + +loser: + rv = SECFailure; + + return (rv); +} + +/* + * verify a certificate by checking if its valid and that we + * trust the issuer. Verify time against now. + */ +SECStatus +CERT_VerifyCertificateNow(CERTCertDBHandle *handle, CERTCertificate *cert, + PRBool checkSig, SECCertificateUsage requiredUsages, + void *wincx, SECCertificateUsage *returnedUsages) +{ + return (CERT_VerifyCertificate(handle, cert, checkSig, + requiredUsages, PR_Now(), wincx, NULL, returnedUsages)); +} + +/* obsolete, do not use for new code */ +SECStatus +CERT_VerifyCertNow(CERTCertDBHandle *handle, CERTCertificate *cert, + PRBool checkSig, SECCertUsage certUsage, void *wincx) +{ + return (CERT_VerifyCert(handle, cert, checkSig, + certUsage, PR_Now(), wincx, NULL)); +} + +/* [ FROM pcertdb.c ] */ +/* + * Supported usage values and types: + * certUsageSSLClient + * certUsageSSLServer + * certUsageSSLServerWithStepUp + * certUsageIPsec + * certUsageEmailSigner + * certUsageEmailRecipient + * certUsageObjectSigner + */ + +CERTCertificate * +CERT_FindMatchingCert(CERTCertDBHandle *handle, SECItem *derName, + CERTCertOwner owner, SECCertUsage usage, + PRBool preferTrusted, PRTime validTime, PRBool validOnly) +{ + CERTCertList *certList = NULL; + CERTCertificate *cert = NULL; + CERTCertTrust certTrust; + unsigned int requiredTrustFlags; + SECTrustType requiredTrustType; + unsigned int flags; + + PRBool lookingForCA = PR_FALSE; + SECStatus rv; + CERTCertListNode *node; + CERTCertificate *saveUntrustedCA = NULL; + + /* if preferTrusted is set, must be a CA cert */ + PORT_Assert(!(preferTrusted && (owner != certOwnerCA))); + + if (owner == certOwnerCA) { + lookingForCA = PR_TRUE; + if (preferTrusted) { + rv = CERT_TrustFlagsForCACertUsage(usage, &requiredTrustFlags, + &requiredTrustType); + if (rv != SECSuccess) { + goto loser; + } + requiredTrustFlags |= CERTDB_VALID_CA; + } + } + + certList = CERT_CreateSubjectCertList(NULL, handle, derName, validTime, + validOnly); + if (certList != NULL) { + rv = CERT_FilterCertListByUsage(certList, usage, lookingForCA); + if (rv != SECSuccess) { + goto loser; + } + + node = CERT_LIST_HEAD(certList); + + while (!CERT_LIST_END(node, certList)) { + cert = node->cert; + + /* looking for a trusted CA cert */ + if ((owner == certOwnerCA) && preferTrusted && + (requiredTrustType != trustTypeNone)) { + + if (CERT_GetCertTrust(cert, &certTrust) != SECSuccess) { + flags = 0; + } else { + flags = SEC_GET_TRUST_FLAGS(&certTrust, requiredTrustType); + } + + if ((flags & requiredTrustFlags) != requiredTrustFlags) { + /* cert is not trusted */ + /* if this is the first cert to get this far, then save + * it, so we can use it if we can't find a trusted one + */ + if (saveUntrustedCA == NULL) { + saveUntrustedCA = cert; + } + goto endloop; + } + } + /* if we got this far, then this cert meets all criteria */ + break; + + endloop: + node = CERT_LIST_NEXT(node); + cert = NULL; + } + + /* use the saved one if we have it */ + if (cert == NULL) { + cert = saveUntrustedCA; + } + + /* if we found one then bump the ref count before freeing the list */ + if (cert != NULL) { + /* bump the ref count */ + cert = CERT_DupCertificate(cert); + } + + CERT_DestroyCertList(certList); + } + + return (cert); + +loser: + if (certList != NULL) { + CERT_DestroyCertList(certList); + } + + return (NULL); +} + +/* [ From certdb.c ] */ +/* + * Filter a list of certificates, removing those certs that do not have + * one of the named CA certs somewhere in their cert chain. + * + * "certList" - the list of certificates to filter + * "nCANames" - number of CA names + * "caNames" - array of CA names in string(rfc 1485) form + * "usage" - what use the certs are for, this is used when + * selecting CA certs + */ +SECStatus +CERT_FilterCertListByCANames(CERTCertList *certList, int nCANames, + char **caNames, SECCertUsage usage) +{ + CERTCertificate *issuerCert = NULL; + CERTCertificate *subjectCert; + CERTCertListNode *node, *freenode; + CERTCertificate *cert; + int n; + char **names; + PRBool found; + PRTime time; + + if (nCANames <= 0) { + return (SECSuccess); + } + + time = PR_Now(); + + node = CERT_LIST_HEAD(certList); + + while (!CERT_LIST_END(node, certList)) { + cert = node->cert; + + subjectCert = CERT_DupCertificate(cert); + + /* traverse the CA certs for this cert */ + found = PR_FALSE; + while (subjectCert != NULL) { + n = nCANames; + names = caNames; + + if (subjectCert->issuerName != NULL) { + while (n > 0) { + if (PORT_Strcmp(*names, subjectCert->issuerName) == 0) { + found = PR_TRUE; + break; + } + + n--; + names++; + } + } + + if (found) { + break; + } + + issuerCert = CERT_FindCertIssuer(subjectCert, time, usage); + if (issuerCert == subjectCert) { + CERT_DestroyCertificate(issuerCert); + issuerCert = NULL; + break; + } + CERT_DestroyCertificate(subjectCert); + subjectCert = issuerCert; + } + CERT_DestroyCertificate(subjectCert); + if (!found) { + /* CA was not found, so remove this cert from the list */ + freenode = node; + node = CERT_LIST_NEXT(node); + CERT_RemoveCertListNode(freenode); + } else { + /* CA was found, so leave it in the list */ + node = CERT_LIST_NEXT(node); + } + } + + return (SECSuccess); +} + +/* + * Given a certificate, return a string containing the nickname, and possibly + * one of the validity strings, based on the current validity state of the + * certificate. + * + * "arena" - arena to allocate returned string from. If NULL, then heap + * is used. + * "cert" - the cert to get nickname from + * "expiredString" - the string to append to the nickname if the cert is + * expired. + * "notYetGoodString" - the string to append to the nickname if the cert is + * not yet good. + */ +char * +CERT_GetCertNicknameWithValidity(PLArenaPool *arena, CERTCertificate *cert, + char *expiredString, char *notYetGoodString) +{ + SECCertTimeValidity validity; + char *nickname = NULL, *tmpstr = NULL; + const char *srcNickname = cert->nickname; + if (!srcNickname) { + srcNickname = "{???}"; + } + + validity = CERT_CheckCertValidTimes(cert, PR_Now(), PR_FALSE); + + /* if the cert is good, then just use the nickname directly */ + if (validity == secCertTimeValid) { + if (arena == NULL) { + nickname = PORT_Strdup(srcNickname); + } else { + nickname = PORT_ArenaStrdup(arena, srcNickname); + } + + if (nickname == NULL) { + goto loser; + } + } else { + + /* if the cert is not valid, then tack one of the strings on the + * end + */ + if (validity == secCertTimeExpired) { + tmpstr = PR_smprintf("%s%s", srcNickname, + expiredString); + } else if (validity == secCertTimeNotValidYet) { + /* not yet valid */ + tmpstr = PR_smprintf("%s%s", srcNickname, + notYetGoodString); + } else { + /* undetermined */ + tmpstr = PR_smprintf("%s", + "(NULL) (Validity Unknown)"); + } + + if (tmpstr == NULL) { + goto loser; + } + + if (arena) { + /* copy the string into the arena and free the malloc'd one */ + nickname = PORT_ArenaStrdup(arena, tmpstr); + PORT_Free(tmpstr); + } else { + nickname = tmpstr; + } + if (nickname == NULL) { + goto loser; + } + } + return (nickname); + +loser: + return (NULL); +} + +/* + * Collect the nicknames from all certs in a CertList. If the cert is not + * valid, append a string to that nickname. + * + * "certList" - the list of certificates + * "expiredString" - the string to append to the nickname of any expired cert + * "notYetGoodString" - the string to append to the nickname of any cert + * that is not yet valid + */ +CERTCertNicknames * +CERT_NicknameStringsFromCertList(CERTCertList *certList, char *expiredString, + char *notYetGoodString) +{ + CERTCertNicknames *names; + PLArenaPool *arena; + CERTCertListNode *node; + char **nn; + + /* allocate an arena */ + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + return (NULL); + } + + /* allocate the structure */ + names = PORT_ArenaAlloc(arena, sizeof(CERTCertNicknames)); + if (names == NULL) { + goto loser; + } + + /* init the structure */ + names->arena = arena; + names->head = NULL; + names->numnicknames = 0; + names->nicknames = NULL; + names->totallen = 0; + + /* count the certs in the list */ + node = CERT_LIST_HEAD(certList); + while (!CERT_LIST_END(node, certList)) { + names->numnicknames++; + node = CERT_LIST_NEXT(node); + } + + /* allocate nicknames array */ + names->nicknames = PORT_ArenaAlloc(arena, + sizeof(char *) * names->numnicknames); + if (names->nicknames == NULL) { + goto loser; + } + + /* just in case printf can't deal with null strings */ + if (expiredString == NULL) { + expiredString = ""; + } + + if (notYetGoodString == NULL) { + notYetGoodString = ""; + } + + /* traverse the list of certs and collect the nicknames */ + nn = names->nicknames; + node = CERT_LIST_HEAD(certList); + while (!CERT_LIST_END(node, certList)) { + *nn = CERT_GetCertNicknameWithValidity(arena, node->cert, + expiredString, + notYetGoodString); + if (*nn == NULL) { + goto loser; + } + + names->totallen += PORT_Strlen(*nn); + + nn++; + node = CERT_LIST_NEXT(node); + } + + return (names); + +loser: + PORT_FreeArena(arena, PR_FALSE); + return (NULL); +} + +/* + * Extract the nickname from a nickmake string that may have either + * expiredString or notYetGoodString appended. + * + * Args: + * "namestring" - the string containing the nickname, and possibly + * one of the validity label strings + * "expiredString" - the expired validity label string + * "notYetGoodString" - the not yet good validity label string + * + * Returns the raw nickname + */ +char * +CERT_ExtractNicknameString(char *namestring, char *expiredString, + char *notYetGoodString) +{ + int explen, nyglen, namelen; + int retlen; + char *retstr; + + namelen = PORT_Strlen(namestring); + explen = PORT_Strlen(expiredString); + nyglen = PORT_Strlen(notYetGoodString); + + if (namelen > explen) { + if (PORT_Strcmp(expiredString, &namestring[namelen - explen]) == 0) { + retlen = namelen - explen; + retstr = (char *)PORT_Alloc(retlen + 1); + if (retstr == NULL) { + goto loser; + } + + PORT_Memcpy(retstr, namestring, retlen); + retstr[retlen] = '\0'; + goto done; + } + } + + if (namelen > nyglen) { + if (PORT_Strcmp(notYetGoodString, &namestring[namelen - nyglen]) == 0) { + retlen = namelen - nyglen; + retstr = (char *)PORT_Alloc(retlen + 1); + if (retstr == NULL) { + goto loser; + } + + PORT_Memcpy(retstr, namestring, retlen); + retstr[retlen] = '\0'; + goto done; + } + } + + /* if name string is shorter than either invalid string, then it must + * be a raw nickname + */ + retstr = PORT_Strdup(namestring); + +done: + return (retstr); + +loser: + return (NULL); +} + +CERTCertList * +CERT_GetCertChainFromCert(CERTCertificate *cert, PRTime time, SECCertUsage usage) +{ + CERTCertList *chain = NULL; + int count = 0; + + if (NULL == cert) { + return NULL; + } + + cert = CERT_DupCertificate(cert); + if (NULL == cert) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + chain = CERT_NewCertList(); + if (NULL == chain) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + return NULL; + } + + while (cert != NULL && ++count <= CERT_MAX_CERT_CHAIN) { + if (SECSuccess != CERT_AddCertToListTail(chain, cert)) { + /* return partial chain */ + PORT_SetError(SEC_ERROR_NO_MEMORY); + return chain; + } + + if (cert->isRoot) { + /* return complete chain */ + return chain; + } + + cert = CERT_FindCertIssuer(cert, time, usage); + } + + /* return partial chain */ + PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER); + return chain; +} diff --git a/security/nss/lib/certhigh/certvfypkix.c b/security/nss/lib/certhigh/certvfypkix.c new file mode 100644 index 0000000000..1f6762b919 --- /dev/null +++ b/security/nss/lib/certhigh/certvfypkix.c @@ -0,0 +1,2302 @@ +/* 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/. */ +/* + * nss_pkix_proxy.h + * + * PKIX - NSS proxy functions + * + * NOTE: All structures, functions, data types are parts of library private + * api and are subjects to change in any following releases. + * + */ +#include "prerror.h" +#include "prprf.h" + +#include "nspr.h" +#include "pk11func.h" +#include "certdb.h" +#include "cert.h" +#include "secerr.h" +#include "nssb64.h" +#include "secasn1.h" +#include "secder.h" +#include "pkit.h" + +#ifndef NSS_DISABLE_LIBPKIX +#include "pkix_pl_common.h" + +extern PRLogModuleInfo *pkixLog; + +#ifdef PKIX_OBJECT_LEAK_TEST + +extern PKIX_UInt32 +pkix_pl_lifecycle_ObjectLeakCheck(int *); + +extern SECStatus +pkix_pl_lifecycle_ObjectTableUpdate(int *objCountTable); + +PRInt32 parallelFnInvocationCount; +#endif /* PKIX_OBJECT_LEAK_TEST */ + +static PRBool usePKIXValidationEngine = PR_FALSE; +#endif /* NSS_DISABLE_LIBPKIX */ + +/* + * FUNCTION: CERT_SetUsePKIXForValidation + * DESCRIPTION: + * + * Enables or disables use of libpkix for certificate validation + * + * PARAMETERS: + * "enable" + * PR_TRUE: enables use of libpkix for cert validation. + * PR_FALSE: disables. + * THREAD SAFETY: + * NOT Thread Safe. + * RETURNS: + * Returns SECSuccess if successfully enabled + */ +SECStatus +CERT_SetUsePKIXForValidation(PRBool enable) +{ +#ifdef NSS_DISABLE_LIBPKIX + PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); + return SECFailure; +#else + usePKIXValidationEngine = (enable > 0) ? PR_TRUE : PR_FALSE; + return SECSuccess; +#endif /* NSS_DISABLE_LIBPKIX */ +} + +/* + * FUNCTION: CERT_GetUsePKIXForValidation + * DESCRIPTION: + * + * Checks if libpkix building function should be use for certificate + * chain building. + * + * PARAMETERS: + * NONE + * THREAD SAFETY: + * NOT Thread Safe + * RETURNS: + * Returns PR_TRUE if libpkix should be used. PR_FALSE otherwise. + */ +PRBool +CERT_GetUsePKIXForValidation() +{ +#ifdef NSS_DISABLE_LIBPKIX + return PR_FALSE; +#else + return usePKIXValidationEngine; +#endif /* NSS_DISABLE_LIBPKIX */ +} + +#ifndef NSS_DISABLE_LIBPKIX +#ifdef NOTDEF +/* + * FUNCTION: cert_NssKeyUsagesToPkix + * DESCRIPTION: + * + * Converts nss key usage bit field(PRUint32) to pkix key usage + * bit field. + * + * PARAMETERS: + * "nssKeyUsage" + * Nss key usage bit field. + * "pkixKeyUsage" + * Pkix key usage big field. + * "plContext" + * Platform-specific context pointer. + * THREAD SAFETY: + * Thread Safe (see Thread Safety Definitions in Programmer's Guide) + * RETURNS: + * Returns NULL if the function succeeds. + * Returns a Fatal Error if the function fails in an unrecoverable way. + */ +static PKIX_Error * +cert_NssKeyUsagesToPkix( + PRUint32 nssKeyUsage, + PKIX_UInt32 *pPkixKeyUsage, + void *plContext) +{ + PKIX_UInt32 pkixKeyUsage = 0; + + PKIX_ENTER(CERTVFYPKIX, "cert_NssKeyUsagesToPkix"); + PKIX_NULLCHECK_ONE(pPkixKeyUsage); + + *pPkixKeyUsage = 0; + + if (nssKeyUsage & KU_DIGITAL_SIGNATURE) { + pkixKeyUsage |= PKIX_DIGITAL_SIGNATURE; + } + + if (nssKeyUsage & KU_NON_REPUDIATION) { + pkixKeyUsage |= PKIX_NON_REPUDIATION; + } + + if (nssKeyUsage & KU_KEY_ENCIPHERMENT) { + pkixKeyUsage |= PKIX_KEY_ENCIPHERMENT; + } + + if (nssKeyUsage & KU_DATA_ENCIPHERMENT) { + pkixKeyUsage |= PKIX_DATA_ENCIPHERMENT; + } + + if (nssKeyUsage & KU_KEY_AGREEMENT) { + pkixKeyUsage |= PKIX_KEY_AGREEMENT; + } + + if (nssKeyUsage & KU_KEY_CERT_SIGN) { + pkixKeyUsage |= PKIX_KEY_CERT_SIGN; + } + + if (nssKeyUsage & KU_CRL_SIGN) { + pkixKeyUsage |= PKIX_CRL_SIGN; + } + + if (nssKeyUsage & KU_ENCIPHER_ONLY) { + pkixKeyUsage |= PKIX_ENCIPHER_ONLY; + } + + /* Not supported. XXX we should support this once it is + * fixed in NSS */ + /* pkixKeyUsage |= PKIX_DECIPHER_ONLY; */ + + *pPkixKeyUsage = pkixKeyUsage; + + PKIX_RETURN(CERTVFYPKIX); +} + +extern SECOidTag ekuOidStrings[]; + +enum { + ekuIndexSSLServer = 0, + ekuIndexSSLClient, + ekuIndexCodeSigner, + ekuIndexEmail, + ekuIndexTimeStamp, + ekuIndexStatusResponder, + ekuIndexUnknown +} ekuIndex; + +typedef struct { + SECCertUsage certUsage; + PRUint32 ekuStringIndex; +} SECCertUsageToEku; + +const SECCertUsageToEku certUsageEkuStringMap[] = { + { certUsageSSLClient, ekuIndexSSLClient }, + { certUsageSSLServer, ekuIndexSSLServer }, + { certUsageSSLCA, ekuIndexSSLServer }, + { certUsageEmailSigner, ekuIndexEmail }, + { certUsageEmailRecipient, ekuIndexEmail }, + { certUsageObjectSigner, ekuIndexCodeSigner }, + { certUsageUserCertImport, ekuIndexUnknown }, + { certUsageVerifyCA, ekuIndexUnknown }, + { certUsageProtectedObjectSigner, ekuIndexUnknown }, + { certUsageStatusResponder, ekuIndexStatusResponder }, + { certUsageAnyCA, ekuIndexUnknown }, +}; + +/* + * FUNCTION: cert_NssCertificateUsageToPkixKUAndEKU + * DESCRIPTION: + * + * Converts nss CERTCertificateUsage bit field to pkix key and + * extended key usages. + * + * PARAMETERS: + * "cert" + * Pointer to CERTCertificate structure of validating cert. + * "requiredCertUsages" + * Required usage that will be converted to pkix eku and ku. + * "requiredKeyUsage", + * Additional key usages impose to cert. + * "isCA", + * it true, convert usages for cert that is a CA cert. + * "ppkixEKUList" + * Returned address of a list of pkix extended key usages. + * "ppkixKU" + * Returned address of pkix required key usages bit field. + * "plContext" + * Platform-specific context pointer. + * THREAD SAFETY: + * Thread Safe (see Thread Safety Definitions in Programmer's Guide) + * RETURNS: + * Returns NULL if the function succeeds. + * Returns a Cert Verify Error if the function fails in an unrecoverable way. + * Returns a Fatal Error if the function fails in an unrecoverable way. + */ +static PKIX_Error * +cert_NssCertificateUsageToPkixKUAndEKU( + CERTCertificate *cert, + SECCertUsage requiredCertUsage, + PRUint32 requiredKeyUsages, + PRBool isCA, + PKIX_List **ppkixEKUList, + PKIX_UInt32 *ppkixKU, + void *plContext) +{ + PKIX_List *ekuOidsList = NULL; + PKIX_PL_OID *ekuOid = NULL; + int i = 0; + int ekuIndex = ekuIndexUnknown; + + PKIX_ENTER(CERTVFYPKIX, "cert_NssCertificateUsageToPkixEku"); + PKIX_NULLCHECK_TWO(ppkixEKUList, ppkixKU); + + PKIX_CHECK( + PKIX_List_Create(&ekuOidsList, plContext), + PKIX_LISTCREATEFAILED); + + for (; i < PR_ARRAY_SIZE(certUsageEkuStringMap); i++) { + const SECCertUsageToEku *usageToEkuElem = + &certUsageEkuStringMap[i]; + if (usageToEkuElem->certUsage == requiredCertUsage) { + ekuIndex = usageToEkuElem->ekuStringIndex; + break; + } + } + if (ekuIndex != ekuIndexUnknown) { + PRUint32 reqKeyUsage = 0; + PRUint32 reqCertType = 0; + + CERT_KeyUsageAndTypeForCertUsage(requiredCertUsage, isCA, + &reqKeyUsage, + &reqCertType); + + requiredKeyUsages |= reqKeyUsage; + + PKIX_CHECK( + PKIX_PL_OID_Create(ekuOidStrings[ekuIndex], &ekuOid, + plContext), + PKIX_OIDCREATEFAILED); + + PKIX_CHECK( + PKIX_List_AppendItem(ekuOidsList, (PKIX_PL_Object *)ekuOid, + plContext), + PKIX_LISTAPPENDITEMFAILED); + + PKIX_DECREF(ekuOid); + } + + PKIX_CHECK( + cert_NssKeyUsagesToPkix(requiredKeyUsages, ppkixKU, plContext), + PKIX_NSSCERTIFICATEUSAGETOPKIXKUANDEKUFAILED); + + *ppkixEKUList = ekuOidsList; + ekuOidsList = NULL; + +cleanup: + + PKIX_DECREF(ekuOid); + PKIX_DECREF(ekuOidsList); + + PKIX_RETURN(CERTVFYPKIX); +} + +#endif + +/* + * FUNCTION: cert_ProcessingParamsSetKeyAndCertUsage + * DESCRIPTION: + * + * Converts cert usage to pkix KU type and sets + * converted data into PKIX_ProcessingParams object. It also sets + * proper cert usage into nsscontext object. + * + * PARAMETERS: + * "procParams" + * Pointer to PKIX_ProcessingParams used during validation. + * "requiredCertUsage" + * Required certificate usages the certificate and chain is built and + * validated for. + * "requiredKeyUsage" + * Request additional key usages the certificate should be validated for. + * "plContext" + * Platform-specific context pointer. + * THREAD SAFETY: + * Thread Safe (see Thread Safety Definitions in Programmer's Guide) + * RETURNS: + * Returns NULL if the function succeeds. + * Returns a Cert Verify Error if the function fails in an unrecoverable way. + * Returns a Fatal Error if the function fails in an unrecoverable way. + */ +static PKIX_Error * +cert_ProcessingParamsSetKeyAndCertUsage( + PKIX_ProcessingParams *procParams, + SECCertUsage requiredCertUsage, + PRUint32 requiredKeyUsages, + void *plContext) +{ + PKIX_CertSelector *certSelector = NULL; + PKIX_ComCertSelParams *certSelParams = NULL; + PKIX_PL_NssContext *nssContext = (PKIX_PL_NssContext *)plContext; + + PKIX_ENTER(CERTVFYPKIX, "cert_ProcessingParamsSetKeyAndCertUsage"); + PKIX_NULLCHECK_TWO(procParams, nssContext); + + PKIX_CHECK( + pkix_pl_NssContext_SetCertUsage( + ((SECCertificateUsage)1) << requiredCertUsage, nssContext), + PKIX_NSSCONTEXTSETCERTUSAGEFAILED); + + if (requiredKeyUsages) { + PKIX_CHECK( + PKIX_ProcessingParams_GetTargetCertConstraints(procParams, + &certSelector, plContext), + PKIX_PROCESSINGPARAMSGETTARGETCERTCONSTRAINTSFAILED); + + PKIX_CHECK( + PKIX_CertSelector_GetCommonCertSelectorParams(certSelector, + &certSelParams, plContext), + PKIX_CERTSELECTORGETCOMMONCERTSELECTORPARAMSFAILED); + + PKIX_CHECK( + PKIX_ComCertSelParams_SetKeyUsage(certSelParams, requiredKeyUsages, + plContext), + PKIX_COMCERTSELPARAMSSETKEYUSAGEFAILED); + } +cleanup: + PKIX_DECREF(certSelector); + PKIX_DECREF(certSelParams); + + PKIX_RETURN(CERTVFYPKIX); +} + +/* + * Unused parameters: + * + * CERTCertList *initialChain, + * CERTCertStores certStores, + * CERTCertRevCheckers certRevCheckers, + * CERTCertChainCheckers certChainCheckers, + * SECItem *initPolicies, + * PRBool policyQualifierRejected, + * PRBool anyPolicyInhibited, + * PRBool reqExplicitPolicy, + * PRBool policyMappingInhibited, + * PKIX_CertSelector certConstraints, + */ + +/* + * FUNCTION: cert_CreatePkixProcessingParams + * DESCRIPTION: + * + * Creates and fills in PKIX_ProcessingParams structure to be used + * for certificate chain building. + * + * PARAMETERS: + * "cert" + * Pointer to the CERTCertificate: the leaf certificate of a chain. + * "time" + * Validity time. + * "wincx" + * Nss db password token. + * "useArena" + * Flags to use arena for data allocation during chain building process. + * "pprocParams" + * Address to return created processing parameters. + * "plContext" + * Platform-specific context pointer. + * THREAD SAFETY: + * Thread Safe (see Thread Safety Definitions in Programmer's Guide) + * RETURNS: + * Returns NULL if the function succeeds. + * Returns a Cert Verify Error if the function fails in an unrecoverable way. + * Returns a Fatal Error if the function fails in an unrecoverable way. + */ +static PKIX_Error * +cert_CreatePkixProcessingParams( + CERTCertificate *cert, + PRBool checkSig, + PRTime time, + void *wincx, + PRBool useArena, + PRBool disableOCSPRemoteFetching, + PKIX_ProcessingParams **pprocParams, + void **pplContext) +{ + PKIX_List *anchors = NULL; + PKIX_PL_Cert *targetCert = NULL; + PKIX_PL_Date *date = NULL; + PKIX_ProcessingParams *procParams = NULL; + PKIX_CertSelector *certSelector = NULL; + PKIX_ComCertSelParams *certSelParams = NULL; + PKIX_CertStore *certStore = NULL; + PKIX_List *certStores = NULL; + PKIX_RevocationChecker *revChecker = NULL; + PKIX_UInt32 methodFlags = 0; + void *plContext = NULL; + CERTStatusConfig *statusConfig = NULL; + + PKIX_ENTER(CERTVFYPKIX, "cert_CreatePkixProcessingParams"); + PKIX_NULLCHECK_TWO(cert, pprocParams); + + PKIX_CHECK( + PKIX_PL_NssContext_Create(0, useArena, wincx, &plContext), + PKIX_NSSCONTEXTCREATEFAILED); + + *pplContext = plContext; + + /* Functions should be implemented in patch for 390532 */ + PKIX_CHECK( + pkix_pl_NssContext_SetCertSignatureCheck(checkSig, + (PKIX_PL_NssContext *)plContext), + PKIX_NSSCONTEXTSETCERTSIGNCHECKFAILED); + + PKIX_CHECK( + PKIX_ProcessingParams_Create(&procParams, plContext), + PKIX_PROCESSINGPARAMSCREATEFAILED); + + PKIX_CHECK( + PKIX_ComCertSelParams_Create(&certSelParams, plContext), + PKIX_COMCERTSELPARAMSCREATEFAILED); + + PKIX_CHECK( + PKIX_PL_Cert_CreateFromCERTCertificate(cert, &targetCert, plContext), + PKIX_CERTCREATEWITHNSSCERTFAILED); + + PKIX_CHECK( + PKIX_ComCertSelParams_SetCertificate(certSelParams, + targetCert, plContext), + PKIX_COMCERTSELPARAMSSETCERTIFICATEFAILED); + + PKIX_CHECK( + PKIX_CertSelector_Create(NULL, NULL, &certSelector, plContext), + PKIX_COULDNOTCREATECERTSELECTOROBJECT); + + PKIX_CHECK( + PKIX_CertSelector_SetCommonCertSelectorParams(certSelector, + certSelParams, plContext), + PKIX_CERTSELECTORSETCOMMONCERTSELECTORPARAMSFAILED); + + PKIX_CHECK( + PKIX_ProcessingParams_SetTargetCertConstraints(procParams, + certSelector, plContext), + PKIX_PROCESSINGPARAMSSETTARGETCERTCONSTRAINTSFAILED); + + /* Turn off quialification of target cert since leaf cert is + * already check for date validity, key usages and extended + * key usages. */ + PKIX_CHECK( + PKIX_ProcessingParams_SetQualifyTargetCert(procParams, PKIX_FALSE, + plContext), + PKIX_PROCESSINGPARAMSSETQUALIFYTARGETCERTFLAGFAILED); + + PKIX_CHECK( + PKIX_PL_Pk11CertStore_Create(&certStore, plContext), + PKIX_PK11CERTSTORECREATEFAILED); + + PKIX_CHECK( + PKIX_List_Create(&certStores, plContext), + PKIX_UNABLETOCREATELIST); + + PKIX_CHECK( + PKIX_List_AppendItem(certStores, (PKIX_PL_Object *)certStore, + plContext), + PKIX_LISTAPPENDITEMFAILED); + + PKIX_CHECK( + PKIX_ProcessingParams_SetCertStores(procParams, certStores, + plContext), + PKIX_PROCESSINGPARAMSADDCERTSTOREFAILED); + + PKIX_CHECK( + PKIX_PL_Date_CreateFromPRTime(time, &date, plContext), + PKIX_DATECREATEFROMPRTIMEFAILED); + + PKIX_CHECK( + PKIX_ProcessingParams_SetDate(procParams, date, plContext), + PKIX_PROCESSINGPARAMSSETDATEFAILED); + + PKIX_CHECK( + PKIX_RevocationChecker_Create( + PKIX_REV_MI_TEST_ALL_LOCAL_INFORMATION_FIRST | + PKIX_REV_MI_NO_OVERALL_INFO_REQUIREMENT, + PKIX_REV_MI_TEST_ALL_LOCAL_INFORMATION_FIRST | + PKIX_REV_MI_NO_OVERALL_INFO_REQUIREMENT, + &revChecker, plContext), + PKIX_REVOCATIONCHECKERCREATEFAILED); + + PKIX_CHECK( + PKIX_ProcessingParams_SetRevocationChecker(procParams, revChecker, + plContext), + PKIX_PROCESSINGPARAMSSETREVOCATIONCHECKERFAILED); + + /* CRL method flags */ + methodFlags = + PKIX_REV_M_TEST_USING_THIS_METHOD | + PKIX_REV_M_FORBID_NETWORK_FETCHING | + PKIX_REV_M_SKIP_TEST_ON_MISSING_SOURCE | /* 0 */ + PKIX_REV_M_IGNORE_MISSING_FRESH_INFO | /* 0 */ + PKIX_REV_M_CONTINUE_TESTING_ON_FRESH_INFO; + + /* add CRL revocation method to check the leaf certificate */ + PKIX_CHECK( + PKIX_RevocationChecker_CreateAndAddMethod(revChecker, procParams, + PKIX_RevocationMethod_CRL, methodFlags, + 0, NULL, PKIX_TRUE, plContext), + PKIX_REVOCATIONCHECKERADDMETHODFAILED); + + /* add CRL revocation method for other certs in the chain. */ + PKIX_CHECK( + PKIX_RevocationChecker_CreateAndAddMethod(revChecker, procParams, + PKIX_RevocationMethod_CRL, methodFlags, + 0, NULL, PKIX_FALSE, plContext), + PKIX_REVOCATIONCHECKERADDMETHODFAILED); + + /* For compatibility with the old code, need to check that + * statusConfig is set in the db handle and status checker + * is defined befor allow ocsp status check on the leaf cert.*/ + statusConfig = CERT_GetStatusConfig(CERT_GetDefaultCertDB()); + if (statusConfig != NULL && statusConfig->statusChecker != NULL) { + + /* Enable OCSP revocation checking for the leaf cert. */ + /* OCSP method flags */ + methodFlags = + PKIX_REV_M_TEST_USING_THIS_METHOD | + PKIX_REV_M_ALLOW_NETWORK_FETCHING | /* 0 */ + PKIX_REV_M_ALLOW_IMPLICIT_DEFAULT_SOURCE | /* 0 */ + PKIX_REV_M_SKIP_TEST_ON_MISSING_SOURCE | /* 0 */ + PKIX_REV_M_IGNORE_MISSING_FRESH_INFO | /* 0 */ + PKIX_REV_M_CONTINUE_TESTING_ON_FRESH_INFO; + + /* Disabling ocsp fetching when checking the status + * of ocsp response signer. Here and in the next if, + * adjust flags for ocsp signer cert validation case. */ + if (disableOCSPRemoteFetching) { + methodFlags |= PKIX_REV_M_FORBID_NETWORK_FETCHING; + } + + if (ocsp_FetchingFailureIsVerificationFailure() && + !disableOCSPRemoteFetching) { + methodFlags |= + PKIX_REV_M_FAIL_ON_MISSING_FRESH_INFO; + } + + /* add OCSP revocation method to check only the leaf certificate.*/ + PKIX_CHECK( + PKIX_RevocationChecker_CreateAndAddMethod(revChecker, procParams, + PKIX_RevocationMethod_OCSP, methodFlags, + 1, NULL, PKIX_TRUE, plContext), + PKIX_REVOCATIONCHECKERADDMETHODFAILED); + } + + PKIX_CHECK( + PKIX_ProcessingParams_SetAnyPolicyInhibited(procParams, PR_FALSE, + plContext), + PKIX_PROCESSINGPARAMSSETANYPOLICYINHIBITED); + + PKIX_CHECK( + PKIX_ProcessingParams_SetExplicitPolicyRequired(procParams, PR_FALSE, + plContext), + PKIX_PROCESSINGPARAMSSETEXPLICITPOLICYREQUIRED); + + PKIX_CHECK( + PKIX_ProcessingParams_SetPolicyMappingInhibited(procParams, PR_FALSE, + plContext), + PKIX_PROCESSINGPARAMSSETPOLICYMAPPINGINHIBITED); + + *pprocParams = procParams; + procParams = NULL; + +cleanup: + PKIX_DECREF(anchors); + PKIX_DECREF(targetCert); + PKIX_DECREF(date); + PKIX_DECREF(certSelector); + PKIX_DECREF(certSelParams); + PKIX_DECREF(certStore); + PKIX_DECREF(certStores); + PKIX_DECREF(procParams); + PKIX_DECREF(revChecker); + + PKIX_RETURN(CERTVFYPKIX); +} + +/* + * FUNCTION: cert_PkixToNssCertsChain + * DESCRIPTION: + * + * Converts pkix cert list into nss cert list. + * + * PARAMETERS: + * "pkixCertChain" + * Pkix certificate list. + * "pvalidChain" + * An address of returned nss certificate list. + * "plContext" + * Platform-specific context pointer. + * THREAD SAFETY: + * Thread Safe (see Thread Safety Definitions in Programmer's Guide) + * RETURNS: + * Returns NULL if the function succeeds. + * Returns a Cert Verify Error if the function fails in an unrecoverable way. + * Returns a Fatal Error if the function fails in an unrecoverable way. + */ +static PKIX_Error * +cert_PkixToNssCertsChain( + PKIX_List *pkixCertChain, + CERTCertList **pvalidChain, + void *plContext) +{ + PLArenaPool *arena = NULL; + CERTCertificate *nssCert = NULL; + CERTCertList *validChain = NULL; + PKIX_PL_Object *certItem = NULL; + PKIX_UInt32 length = 0; + PKIX_UInt32 i = 0; + + PKIX_ENTER(CERTVFYPKIX, "cert_PkixToNssCertsChain"); + PKIX_NULLCHECK_ONE(pvalidChain); + + if (pkixCertChain == NULL) { + goto cleanup; + } + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + PKIX_ERROR(PKIX_OUTOFMEMORY); + } + validChain = (CERTCertList *)PORT_ArenaZAlloc(arena, sizeof(CERTCertList)); + if (validChain == NULL) { + PKIX_ERROR(PKIX_PORTARENAALLOCFAILED); + } + PR_INIT_CLIST(&validChain->list); + validChain->arena = arena; + arena = NULL; + + PKIX_CHECK( + PKIX_List_GetLength(pkixCertChain, &length, plContext), + PKIX_LISTGETLENGTHFAILED); + + for (i = 0; i < length; i++) { + CERTCertListNode *node = NULL; + + PKIX_CHECK( + PKIX_List_GetItem(pkixCertChain, i, &certItem, plContext), + PKIX_LISTGETITEMFAILED); + + PKIX_CHECK( + PKIX_PL_Cert_GetCERTCertificate((PKIX_PL_Cert *)certItem, &nssCert, + plContext), + PKIX_CERTGETCERTCERTIFICATEFAILED); + + node = + (CERTCertListNode *)PORT_ArenaZAlloc(validChain->arena, + sizeof(CERTCertListNode)); + if (node == NULL) { + PKIX_ERROR(PKIX_PORTARENAALLOCFAILED); + } + + PR_INSERT_BEFORE(&node->links, &validChain->list); + + node->cert = nssCert; + nssCert = NULL; + + PKIX_DECREF(certItem); + } + + *pvalidChain = validChain; + +cleanup: + if (PKIX_ERROR_RECEIVED) { + if (validChain) { + CERT_DestroyCertList(validChain); + } else if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } + if (nssCert) { + CERT_DestroyCertificate(nssCert); + } + } + PKIX_DECREF(certItem); + + PKIX_RETURN(CERTVFYPKIX); +} + +/* + * FUNCTION: cert_BuildAndValidateChain + * DESCRIPTION: + * + * The function builds and validates a cert chain based on certificate + * selection criterias from procParams. This function call PKIX_BuildChain + * to accomplish chain building. If PKIX_BuildChain returns with incomplete + * IO, the function waits with PR_Poll until the blocking IO is finished and + * return control back to PKIX_BuildChain. + * + * PARAMETERS: + * "procParams" + * Processing parameters to be used during chain building. + * "pResult" + * Returned build result. + * "pVerifyNode" + * Returned pointed to verify node structure: the tree-like structure + * that reports points of chain building failures. + * "plContext" + * Platform-specific context pointer. + * THREAD SAFETY: + * Thread Safe (see Thread Safety Definitions in Programmer's Guide) + * RETURNS: + * Returns NULL if the function succeeds. + * Returns a Cert Verify Error if the function fails in an unrecoverable way. + * Returns a Fatal Error if the function fails in an unrecoverable way. + */ +static PKIX_Error * +cert_BuildAndValidateChain( + PKIX_ProcessingParams *procParams, + PKIX_BuildResult **pResult, + PKIX_VerifyNode **pVerifyNode, + void *plContext) +{ + PKIX_BuildResult *result = NULL; + PKIX_VerifyNode *verifyNode = NULL; + void *nbioContext = NULL; + void *state = NULL; + + PKIX_ENTER(CERTVFYPKIX, "cert_BuildAndVerifyChain"); + PKIX_NULLCHECK_TWO(procParams, pResult); + + do { + if (nbioContext && state) { + /* PKIX-XXX: need to test functionality of NBIO handling in libPkix. + * See bug 391180 */ + PRInt32 filesReady = 0; + PRPollDesc *pollDesc = (PRPollDesc *)nbioContext; + filesReady = PR_Poll(pollDesc, 1, PR_INTERVAL_NO_TIMEOUT); + if (filesReady <= 0) { + PKIX_ERROR(PKIX_PRPOLLRETBADFILENUM); + } + } + + PKIX_CHECK( + PKIX_BuildChain(procParams, &nbioContext, &state, + &result, &verifyNode, plContext), + PKIX_UNABLETOBUILDCHAIN); + + } while (nbioContext && state); + + *pResult = result; + +cleanup: + if (pVerifyNode) { + *pVerifyNode = verifyNode; + } + + PKIX_RETURN(CERTVFYPKIX); +} + +/* + * FUNCTION: cert_PkixErrorToNssCode + * DESCRIPTION: + * + * Converts pkix error(PKIX_Error) structure to PR error codes. + * + * PKIX-XXX to be implemented. See 391183. + * + * PARAMETERS: + * "error" + * Pkix error that will be converted. + * "nssCode" + * Corresponding nss error code. + * "plContext" + * Platform-specific context pointer. + * THREAD SAFETY: + * Thread Safe (see Thread Safety Definitions in Programmer's Guide) + * RETURNS: + * Returns NULL if the function succeeds. + * Returns a Cert Verify Error if the function fails in an unrecoverable way. + * Returns a Fatal Error if the function fails in an unrecoverable way. + */ +static PKIX_Error * +cert_PkixErrorToNssCode( + PKIX_Error *error, + SECErrorCodes *pNssErr, + void *plContext) +{ + int errLevel = 0; + (void)errLevel; /* Suppress unused var warning (Bug 1738028) */ + + PKIX_Int32 nssErr = 0; + PKIX_Error *errPtr = error; + + PKIX_ENTER(CERTVFYPKIX, "cert_PkixErrorToNssCode"); + PKIX_NULLCHECK_TWO(error, pNssErr); + + /* Loop until we find at least one error with non-null + * plErr code, that is going to be nss error code. */ + while (errPtr) { + if (errPtr->plErr && !nssErr) { + nssErr = errPtr->plErr; + if (!pkixLog) + break; + } + if (pkixLog) { +#ifdef PKIX_ERROR_DESCRIPTION + PR_LOG(pkixLog, 2, ("Error at level %d: %s\n", errLevel, PKIX_ErrorText[errPtr->errCode])); +#else + PR_LOG(pkixLog, 2, ("Error at level %d: Error code %d\n", errLevel, errPtr->errCode)); +#endif /* PKIX_ERROR_DESCRIPTION */ + } + errPtr = errPtr->cause; + errLevel += 1; + } + PORT_Assert(nssErr); + if (!nssErr) { + *pNssErr = SEC_ERROR_LIBPKIX_INTERNAL; + } else { + *pNssErr = nssErr; + } + + PKIX_RETURN(CERTVFYPKIX); +} + +/* + * FUNCTION: cert_GetLogFromVerifyNode + * DESCRIPTION: + * + * Recursive function that converts verify node tree-like set of structures + * to CERTVerifyLog. + * + * PARAMETERS: + * "log" + * Pointed to already allocated CERTVerifyLog structure. + * "node" + * A node of PKIX_VerifyNode tree. + * "plContext" + * Platform-specific context pointer. + * THREAD SAFETY: + * Thread Safe (see Thread Safety Definitions in Programmer's Guide) + * RETURNS: + * Returns NULL if the function succeeds. + * Returns a Cert Verify Error if the function fails in an unrecoverable way. + * Returns a Fatal Error if the function fails in an unrecoverable way. + */ +static PKIX_Error * +cert_GetLogFromVerifyNode( + CERTVerifyLog *log, + PKIX_VerifyNode *node, + void *plContext) +{ + PKIX_List *children = NULL; + PKIX_VerifyNode *childNode = NULL; + + PKIX_ENTER(CERTVFYPKIX, "cert_GetLogFromVerifyNode"); + + children = node->children; + + if (children == NULL) { + PKIX_ERRORCODE errCode = PKIX_ANCHORDIDNOTCHAINTOCERT; + if (node->error && node->error->errCode != errCode) { + if (log != NULL) { + SECErrorCodes nssErrorCode = 0; + CERTCertificate *cert = NULL; + + cert = node->verifyCert->nssCert; + + PKIX_CHECK( + cert_PkixErrorToNssCode(node->error, &nssErrorCode, + plContext), + PKIX_GETPKIXERRORCODEFAILED); + + cert_AddToVerifyLog(log, cert, nssErrorCode, node->depth, NULL); + } + } + PKIX_RETURN(CERTVFYPKIX); + } else { + PRUint32 i = 0; + PKIX_UInt32 length = 0; + + PKIX_CHECK( + PKIX_List_GetLength(children, &length, plContext), + PKIX_LISTGETLENGTHFAILED); + + for (i = 0; i < length; i++) { + + PKIX_CHECK( + PKIX_List_GetItem(children, i, (PKIX_PL_Object **)&childNode, + plContext), + PKIX_LISTGETITEMFAILED); + + PKIX_CHECK( + cert_GetLogFromVerifyNode(log, childNode, plContext), + PKIX_ERRORINRECURSIVEEQUALSCALL); + + PKIX_DECREF(childNode); + } + } + +cleanup: + PKIX_DECREF(childNode); + + PKIX_RETURN(CERTVFYPKIX); +} + +/* + * FUNCTION: cert_GetBuildResults + * DESCRIPTION: + * + * Converts pkix build results to nss results. This function is called + * regardless of build result. + * + * If it called after chain was successfully constructed, then it will + * convert: + * * pkix cert list that represent the chain to nss cert list + * * trusted root the chain was anchored to nss certificate. + * + * In case of failure it will convert: + * * pkix error to PR error code(will set it with PORT_SetError) + * * pkix validation log to nss CERTVerifyLog + * + * PARAMETERS: + * "buildResult" + * Build results returned by PKIX_BuildChain. + * "verifyNode" + * Tree-like structure of chain building/validation failures + * returned by PKIX_BuildChain. Ignored in case of success. + * "error" + * Final error returned by PKIX_BuildChain. Should be NULL in + * case of success. + * "log" + * Address of pre-allocated(if not NULL) CERTVerifyLog structure. + * "ptrustedRoot" + * Address of returned trusted root the chain was anchored to. + * "pvalidChain" + * Address of returned valid chain. + * "plContext" + * Platform-specific context pointer. + * THREAD SAFETY: + * Thread Safe (see Thread Safety Definitions in Programmer's Guide) + * RETURNS: + * Returns NULL if the function succeeds. + * Returns a Cert Verify Error if the function fails in an unrecoverable way. + * Returns a Fatal Error if the function fails in an unrecoverable way. + */ +static PKIX_Error * +cert_GetBuildResults( + PKIX_BuildResult *buildResult, + PKIX_VerifyNode *verifyNode, + PKIX_Error *error, + CERTVerifyLog *log, + CERTCertificate **ptrustedRoot, + CERTCertList **pvalidChain, + void *plContext) +{ + PKIX_ValidateResult *validResult = NULL; + CERTCertList *validChain = NULL; + CERTCertificate *trustedRoot = NULL; + PKIX_TrustAnchor *trustAnchor = NULL; + PKIX_PL_Cert *trustedCert = NULL; + PKIX_List *pkixCertChain = NULL; + + PKIX_ENTER(CERTVFYPKIX, "cert_GetBuildResults"); + if (buildResult == NULL && error == NULL) { + PKIX_ERROR(PKIX_NULLARGUMENT); + } + + if (error) { + SECErrorCodes nssErrorCode = 0; + if (verifyNode) { + PKIX_Error *tmpError = + cert_GetLogFromVerifyNode(log, verifyNode, plContext); + if (tmpError) { + PKIX_PL_Object_DecRef((PKIX_PL_Object *)tmpError, plContext); + } + } + cert_PkixErrorToNssCode(error, &nssErrorCode, plContext); + PORT_SetError(nssErrorCode); + goto cleanup; + } + + if (pvalidChain) { + PKIX_CHECK( + PKIX_BuildResult_GetCertChain(buildResult, &pkixCertChain, + plContext), + PKIX_BUILDRESULTGETCERTCHAINFAILED); + + PKIX_CHECK( + cert_PkixToNssCertsChain(pkixCertChain, &validChain, plContext), + PKIX_CERTCHAINTONSSCHAINFAILED); + } + + if (ptrustedRoot) { + PKIX_CHECK( + PKIX_BuildResult_GetValidateResult(buildResult, &validResult, + plContext), + PKIX_BUILDRESULTGETVALIDATERESULTFAILED); + + PKIX_CHECK( + PKIX_ValidateResult_GetTrustAnchor(validResult, &trustAnchor, + plContext), + PKIX_VALIDATERESULTGETTRUSTANCHORFAILED); + + PKIX_CHECK( + PKIX_TrustAnchor_GetTrustedCert(trustAnchor, &trustedCert, + plContext), + PKIX_TRUSTANCHORGETTRUSTEDCERTFAILED); + + PKIX_CHECK( + PKIX_PL_Cert_GetCERTCertificate(trustedCert, &trustedRoot, + plContext), + PKIX_CERTGETCERTCERTIFICATEFAILED); + } + + PORT_Assert(!PKIX_ERROR_RECEIVED); + + if (trustedRoot) { + *ptrustedRoot = trustedRoot; + } + if (validChain) { + *pvalidChain = validChain; + } + +cleanup: + if (PKIX_ERROR_RECEIVED) { + if (trustedRoot) { + CERT_DestroyCertificate(trustedRoot); + } + if (validChain) { + CERT_DestroyCertList(validChain); + } + } + PKIX_DECREF(trustAnchor); + PKIX_DECREF(trustedCert); + PKIX_DECREF(pkixCertChain); + PKIX_DECREF(validResult); + PKIX_DECREF(error); + PKIX_DECREF(verifyNode); + PKIX_DECREF(buildResult); + + PKIX_RETURN(CERTVFYPKIX); +} +#endif /* NSS_DISABLE_LIBPKIX */ + +/* + * FUNCTION: cert_VerifyCertChainPkix + * DESCRIPTION: + * + * The main wrapper function that is called from CERT_VerifyCert and + * CERT_VerifyCACertForUsage functions to validate cert with libpkix. + * + * PARAMETERS: + * "cert" + * Leaf certificate of a chain we want to build. + * "checkSig" + * Certificate signatures will not be verified if this + * flag is set to PR_FALSE. + * "requiredUsage" + * Required usage for certificate and chain. + * "time" + * Validity time. + * "wincx" + * Nss database password token. + * "log" + * Address of already allocated CERTVerifyLog structure. Not + * used if NULL; + * "pSigerror" + * Address of PRBool. If not NULL, returns true is cert chain + * was invalidated because of bad certificate signature. + * "pRevoked" + * Address of PRBool. If not NULL, returns true is cert chain + * was invalidated because a revoked certificate was found in + * the chain. + * THREAD SAFETY: + * Thread Safe (see Thread Safety Definitions in Programmer's Guide) + * RETURNS: + * SECFailure is chain building process has failed. SECSuccess otherwise. + */ +SECStatus +cert_VerifyCertChainPkix( + CERTCertificate *cert, + PRBool checkSig, + SECCertUsage requiredUsage, + PRTime time, + void *wincx, + CERTVerifyLog *log, + PRBool *pSigerror, + PRBool *pRevoked) +{ +#ifdef NSS_DISABLE_LIBPKIX + PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); + return SECFailure; +#else + PKIX_ProcessingParams *procParams = NULL; + PKIX_BuildResult *result = NULL; + PKIX_VerifyNode *verifyNode = NULL; + PKIX_Error *error = NULL; + + SECStatus rv = SECFailure; + void *plContext = NULL; + +#ifdef PKIX_OBJECT_LEAK_TEST + int leakedObjNum = 0; + int memLeakLoopCount = 0; + int objCountTable[PKIX_NUMTYPES]; + int fnInvLocalCount = 0; + PKIX_Boolean savedUsePkixEngFlag = usePKIXValidationEngine; + + if (usePKIXValidationEngine) { + /* current memory leak testing implementation does not allow + * to run simultaneous tests one the same or a different threads. + * Setting the variable to false, to make additional chain + * validations be handled by old nss. */ + usePKIXValidationEngine = PR_FALSE; + } + testStartFnStackPosition = 2; + fnStackNameArr[0] = "cert_VerifyCertChainPkix"; + fnStackInvCountArr[0] = 0; + PKIX_Boolean abortOnLeak = + (PR_GetEnvSecure("PKIX_OBJECT_LEAK_TEST_ABORT_ON_LEAK") == NULL) ? PKIX_FALSE + : PKIX_TRUE; + runningLeakTest = PKIX_TRUE; + + /* Prevent multi-threaded run of object leak test */ + fnInvLocalCount = PR_ATOMIC_INCREMENT(¶llelFnInvocationCount); + PORT_Assert(fnInvLocalCount == 1); + + do { + rv = SECFailure; + plContext = NULL; + procParams = NULL; + result = NULL; + verifyNode = NULL; + error = NULL; + errorGenerated = PKIX_FALSE; + stackPosition = 0; + + if (leakedObjNum) { + pkix_pl_lifecycle_ObjectTableUpdate(objCountTable); + } + memLeakLoopCount += 1; +#endif /* PKIX_OBJECT_LEAK_TEST */ + + error = + cert_CreatePkixProcessingParams(cert, checkSig, time, wincx, + PR_FALSE /*use arena*/, + requiredUsage == certUsageStatusResponder, + &procParams, &plContext); + if (error) { + goto cleanup; + } + + error = + cert_ProcessingParamsSetKeyAndCertUsage(procParams, requiredUsage, 0, + plContext); + if (error) { + goto cleanup; + } + + error = + cert_BuildAndValidateChain(procParams, &result, &verifyNode, plContext); + if (error) { + goto cleanup; + } + + if (pRevoked) { + /* Currently always PR_FALSE. Will be fixed as a part of 394077 */ + *pRevoked = PR_FALSE; + } + if (pSigerror) { + /* Currently always PR_FALSE. Will be fixed as a part of 394077 */ + *pSigerror = PR_FALSE; + } + rv = SECSuccess; + + cleanup: + error = cert_GetBuildResults(result, verifyNode, error, log, NULL, NULL, + plContext); + if (error) { + PKIX_PL_Object_DecRef((PKIX_PL_Object *)error, plContext); + } + if (procParams) { + PKIX_PL_Object_DecRef((PKIX_PL_Object *)procParams, plContext); + } + if (plContext) { + PKIX_PL_NssContext_Destroy(plContext); + } + +#ifdef PKIX_OBJECT_LEAK_TEST + leakedObjNum = + pkix_pl_lifecycle_ObjectLeakCheck(leakedObjNum ? objCountTable : NULL); + + if (pkixLog && leakedObjNum) { + PR_LOG(pkixLog, 1, ("The generated error caused an object leaks. Loop %d." + "Stack %s\n", + memLeakLoopCount, errorFnStackString)); + } + PR_Free(errorFnStackString); + errorFnStackString = NULL; + if (abortOnLeak) { + PORT_Assert(leakedObjNum == 0); + } + + } while (errorGenerated); + + runningLeakTest = PKIX_FALSE; + PR_ATOMIC_DECREMENT(¶llelFnInvocationCount); + usePKIXValidationEngine = savedUsePkixEngFlag; +#endif /* PKIX_OBJECT_LEAK_TEST */ + + return rv; +#endif /* NSS_DISABLE_LIBPKIX */ +} + +#ifndef NSS_DISABLE_LIBPKIX +PKIX_CertSelector * +cert_GetTargetCertConstraints(CERTCertificate *target, void *plContext) +{ + PKIX_ComCertSelParams *certSelParams = NULL; + PKIX_CertSelector *certSelector = NULL; + PKIX_CertSelector *r = NULL; + PKIX_PL_Cert *eeCert = NULL; + PKIX_Error *error = NULL; + + error = PKIX_PL_Cert_CreateFromCERTCertificate(target, &eeCert, plContext); + if (error != NULL) + goto cleanup; + + error = PKIX_CertSelector_Create(NULL, NULL, &certSelector, plContext); + if (error != NULL) + goto cleanup; + + error = PKIX_ComCertSelParams_Create(&certSelParams, plContext); + if (error != NULL) + goto cleanup; + + error = PKIX_ComCertSelParams_SetCertificate( + certSelParams, eeCert, plContext); + if (error != NULL) + goto cleanup; + + error = PKIX_CertSelector_SetCommonCertSelectorParams(certSelector, certSelParams, plContext); + if (error != NULL) + goto cleanup; + + error = PKIX_PL_Object_IncRef((PKIX_PL_Object *)certSelector, plContext); + if (error == NULL) + r = certSelector; + +cleanup: + if (certSelParams != NULL) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)certSelParams, plContext); + + if (eeCert != NULL) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)eeCert, plContext); + + if (certSelector != NULL) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)certSelector, plContext); + + if (error != NULL) { + SECErrorCodes nssErr; + + cert_PkixErrorToNssCode(error, &nssErr, plContext); + PKIX_PL_Object_DecRef((PKIX_PL_Object *)error, plContext); + PORT_SetError(nssErr); + } + + return r; +} + +static PKIX_List * +cert_GetCertStores(void *plContext) +{ + PKIX_CertStore *certStore = NULL; + PKIX_List *certStores = NULL; + PKIX_List *r = NULL; + PKIX_Error *error = NULL; + + error = PKIX_PL_Pk11CertStore_Create(&certStore, plContext); + if (error != NULL) + goto cleanup; + + error = PKIX_List_Create(&certStores, plContext); + if (error != NULL) + goto cleanup; + + error = PKIX_List_AppendItem(certStores, + (PKIX_PL_Object *)certStore, plContext); + if (error != NULL) + goto cleanup; + + error = PKIX_PL_Object_IncRef((PKIX_PL_Object *)certStores, plContext); + if (error == NULL) + r = certStores; + +cleanup: + if (certStores != NULL) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)certStores, plContext); + + if (certStore != NULL) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)certStore, plContext); + + if (error != NULL) { + SECErrorCodes nssErr; + + cert_PkixErrorToNssCode(error, &nssErr, plContext); + PKIX_PL_Object_DecRef((PKIX_PL_Object *)error, plContext); + PORT_SetError(nssErr); + } + + return r; +} + +struct fake_PKIX_PL_CertStruct { + CERTCertificate *nssCert; +}; + +/* This needs to be part of the PKIX_PL_* */ +/* This definitely needs to go away, and be replaced with + a real accessor function in PKIX */ +static CERTCertificate * +cert_NSSCertFromPKIXCert(const PKIX_PL_Cert *pkix_cert) +{ + struct fake_PKIX_PL_CertStruct *fcert = NULL; + + fcert = (struct fake_PKIX_PL_CertStruct *)pkix_cert; + + return CERT_DupCertificate(fcert->nssCert); +} + +PKIX_List * +cert_PKIXMakeOIDList(const SECOidTag *oids, int oidCount, void *plContext) +{ + PKIX_List *r = NULL; + PKIX_List *policyList = NULL; + PKIX_PL_OID *policyOID = NULL; + PKIX_Error *error = NULL; + int i; + + error = PKIX_List_Create(&policyList, plContext); + if (error != NULL) { + goto cleanup; + } + + for (i = 0; i < oidCount; i++) { + error = PKIX_PL_OID_Create(oids[i], &policyOID, plContext); + if (error) { + goto cleanup; + } + error = PKIX_List_AppendItem(policyList, + (PKIX_PL_Object *)policyOID, plContext); + if (error != NULL) { + goto cleanup; + } + PKIX_PL_Object_DecRef((PKIX_PL_Object *)policyOID, plContext); + policyOID = NULL; + } + + error = PKIX_List_SetImmutable(policyList, plContext); + if (error != NULL) + goto cleanup; + + error = PKIX_PL_Object_IncRef((PKIX_PL_Object *)policyList, plContext); + if (error == NULL) + r = policyList; + +cleanup: + if (policyOID != NULL) { + PKIX_PL_Object_DecRef((PKIX_PL_Object *)policyOID, plContext); + } + if (policyList != NULL) { + PKIX_PL_Object_DecRef((PKIX_PL_Object *)policyList, plContext); + } + if (error != NULL) { + PKIX_PL_Object_DecRef((PKIX_PL_Object *)error, plContext); + } + + return r; +} + +CERTValOutParam * +cert_pkix_FindOutputParam(CERTValOutParam *params, const CERTValParamOutType t) +{ + CERTValOutParam *i; + if (params == NULL) { + return NULL; + } + for (i = params; i->type != cert_po_end; i++) { + if (i->type == t) { + return i; + } + } + return NULL; +} + +static PKIX_Error * +setRevocationMethod(PKIX_RevocationChecker *revChecker, + PKIX_ProcessingParams *procParams, + const CERTRevocationTests *revTest, + CERTRevocationMethodIndex certRevMethod, + PKIX_RevocationMethodType pkixRevMethod, + PKIX_Boolean verifyResponderUsages, + PKIX_Boolean isLeafTest, + void *plContext) +{ + PKIX_UInt32 methodFlags = 0; + PKIX_Error *error = NULL; + PKIX_UInt32 priority = 0; + + if (revTest->number_of_defined_methods <= (PRUint32)certRevMethod) { + return NULL; + } + if (revTest->preferred_methods) { + unsigned int i = 0; + for (; i < revTest->number_of_preferred_methods; i++) { + if (revTest->preferred_methods[i] == certRevMethod) + break; + } + priority = i; + } + methodFlags = revTest->cert_rev_flags_per_method[certRevMethod]; + if (verifyResponderUsages && + pkixRevMethod == PKIX_RevocationMethod_OCSP) { + methodFlags |= PKIX_REV_M_FORBID_NETWORK_FETCHING; + } + error = + PKIX_RevocationChecker_CreateAndAddMethod(revChecker, procParams, + pkixRevMethod, methodFlags, + priority, NULL, + isLeafTest, plContext); + return error; +} + +SECStatus +cert_pkixSetParam(PKIX_ProcessingParams *procParams, + const CERTValInParam *param, void *plContext) +{ + PKIX_Error *error = NULL; + SECStatus r = SECSuccess; + PKIX_PL_Date *date = NULL; + PKIX_List *policyOIDList = NULL; + PKIX_List *certListPkix = NULL; + const CERTRevocationFlags *flags; + SECErrorCodes errCode = SEC_ERROR_INVALID_ARGS; + const CERTCertList *certList = NULL; + CERTCertListNode *node; + PKIX_PL_Cert *certPkix = NULL; + PKIX_TrustAnchor *trustAnchor = NULL; + PKIX_RevocationChecker *revChecker = NULL; + PKIX_PL_NssContext *nssContext = (PKIX_PL_NssContext *)plContext; + + /* XXX we need a way to map generic PKIX error to generic NSS errors */ + + switch (param->type) { + + case cert_pi_policyOID: + + /* needed? */ + error = PKIX_ProcessingParams_SetExplicitPolicyRequired( + procParams, PKIX_TRUE, plContext); + + if (error != NULL) { + break; + } + + policyOIDList = cert_PKIXMakeOIDList(param->value.array.oids, + param->value.arraySize, plContext); + if (policyOIDList == NULL) { + r = SECFailure; + PORT_SetError(SEC_ERROR_INVALID_ARGS); + break; + } + + error = PKIX_ProcessingParams_SetInitialPolicies( + procParams, policyOIDList, plContext); + break; + + case cert_pi_date: + if (param->value.scalar.time == 0) { + error = PKIX_PL_Date_Create_UTCTime(NULL, &date, plContext); + if (error != NULL) { + errCode = SEC_ERROR_INVALID_TIME; + break; + } + } else { + error = pkix_pl_Date_CreateFromPRTime(param->value.scalar.time, + &date, plContext); + if (error != NULL) { + errCode = SEC_ERROR_INVALID_TIME; + break; + } + } + + error = PKIX_ProcessingParams_SetDate(procParams, date, plContext); + if (error != NULL) { + errCode = SEC_ERROR_INVALID_TIME; + } + break; + + case cert_pi_revocationFlags: { + PKIX_UInt32 leafIMFlags = 0; + PKIX_UInt32 chainIMFlags = 0; + PKIX_Boolean validatingResponderCert = PKIX_FALSE; + + flags = param->value.pointer.revocation; + if (!flags) { + PORT_SetError(errCode); + r = SECFailure; + break; + } + + leafIMFlags = + flags->leafTests.cert_rev_method_independent_flags; + chainIMFlags = + flags->chainTests.cert_rev_method_independent_flags; + + error = + PKIX_RevocationChecker_Create(leafIMFlags, chainIMFlags, + &revChecker, plContext); + if (error) { + break; + } + + error = + PKIX_ProcessingParams_SetRevocationChecker(procParams, + revChecker, plContext); + if (error) { + break; + } + + if (((PKIX_PL_NssContext *)plContext)->certificateUsage & + certificateUsageStatusResponder) { + validatingResponderCert = PKIX_TRUE; + } + + error = setRevocationMethod(revChecker, + procParams, &flags->leafTests, + cert_revocation_method_crl, + PKIX_RevocationMethod_CRL, + validatingResponderCert, + PKIX_TRUE, plContext); + if (error) { + break; + } + + error = setRevocationMethod(revChecker, + procParams, &flags->leafTests, + cert_revocation_method_ocsp, + PKIX_RevocationMethod_OCSP, + validatingResponderCert, + PKIX_TRUE, plContext); + if (error) { + break; + } + + error = setRevocationMethod(revChecker, + procParams, &flags->chainTests, + cert_revocation_method_crl, + PKIX_RevocationMethod_CRL, + validatingResponderCert, + PKIX_FALSE, plContext); + if (error) { + break; + } + + error = setRevocationMethod(revChecker, + procParams, &flags->chainTests, + cert_revocation_method_ocsp, + PKIX_RevocationMethod_OCSP, + validatingResponderCert, + PKIX_FALSE, plContext); + if (error) { + break; + } + + } break; + + case cert_pi_trustAnchors: + certList = param->value.pointer.chain; + if (!certList) { + PORT_SetError(errCode); + r = SECFailure; + break; + } + error = PKIX_List_Create(&certListPkix, plContext); + if (error != NULL) { + break; + } + for (node = CERT_LIST_HEAD(certList); !CERT_LIST_END(node, certList); + node = CERT_LIST_NEXT(node)) { + error = PKIX_PL_Cert_CreateFromCERTCertificate(node->cert, + &certPkix, plContext); + if (error) { + break; + } + error = PKIX_TrustAnchor_CreateWithCert(certPkix, &trustAnchor, + plContext); + if (error) { + break; + } + error = PKIX_List_AppendItem(certListPkix, + (PKIX_PL_Object *)trustAnchor, plContext); + if (error) { + break; + } + PKIX_PL_Object_DecRef((PKIX_PL_Object *)trustAnchor, plContext); + trustAnchor = NULL; + PKIX_PL_Object_DecRef((PKIX_PL_Object *)certPkix, plContext); + certPkix = NULL; + } + error = + PKIX_ProcessingParams_SetTrustAnchors(procParams, certListPkix, + plContext); + break; + + case cert_pi_useAIACertFetch: + error = + PKIX_ProcessingParams_SetUseAIAForCertFetching(procParams, + (PRBool)(param->value.scalar.b != + 0), + plContext); + break; + + case cert_pi_chainVerifyCallback: { + const CERTChainVerifyCallback *chainVerifyCallback = + param->value.pointer.chainVerifyCallback; + if (!chainVerifyCallback || !chainVerifyCallback->isChainValid) { + PORT_SetError(errCode); + r = SECFailure; + break; + } + + nssContext->chainVerifyCallback = *chainVerifyCallback; + } break; + + case cert_pi_useOnlyTrustAnchors: + error = + PKIX_ProcessingParams_SetUseOnlyTrustAnchors(procParams, + (PRBool)(param->value.scalar.b != + 0), + plContext); + break; + + default: + PORT_SetError(errCode); + r = SECFailure; + break; + } + + if (policyOIDList != NULL) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)policyOIDList, plContext); + + if (date != NULL) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)date, plContext); + + if (revChecker != NULL) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)revChecker, plContext); + + if (certListPkix) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)certListPkix, plContext); + + if (trustAnchor) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)trustAnchor, plContext); + + if (certPkix) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)certPkix, plContext); + + if (error != NULL) { + PKIX_PL_Object_DecRef((PKIX_PL_Object *)error, plContext); + PORT_SetError(errCode); + r = SECFailure; + } + + return r; +} + +void +cert_pkixDestroyValOutParam(CERTValOutParam *params) +{ + CERTValOutParam *i; + + if (params == NULL) { + return; + } + for (i = params; i->type != cert_po_end; i++) { + switch (i->type) { + case cert_po_trustAnchor: + if (i->value.pointer.cert) { + CERT_DestroyCertificate(i->value.pointer.cert); + i->value.pointer.cert = NULL; + } + break; + + case cert_po_certList: + if (i->value.pointer.chain) { + CERT_DestroyCertList(i->value.pointer.chain); + i->value.pointer.chain = NULL; + } + break; + + default: + break; + } + } +} + +static PRUint64 certRev_NSS_3_11_Ocsp_Enabled_Soft_Policy_LeafFlags[2] = { + /* crl */ + CERT_REV_M_TEST_USING_THIS_METHOD | + CERT_REV_M_FORBID_NETWORK_FETCHING | + CERT_REV_M_CONTINUE_TESTING_ON_FRESH_INFO, + /* ocsp */ + CERT_REV_M_TEST_USING_THIS_METHOD +}; + +static PRUint64 certRev_NSS_3_11_Ocsp_Enabled_Soft_Policy_ChainFlags[2] = { + /* crl */ + CERT_REV_M_TEST_USING_THIS_METHOD | + CERT_REV_M_FORBID_NETWORK_FETCHING | + CERT_REV_M_CONTINUE_TESTING_ON_FRESH_INFO, + /* ocsp */ + 0 +}; + +static CERTRevocationMethodIndex + certRev_NSS_3_11_Ocsp_Enabled_Soft_Policy_Method_Preference = { + cert_revocation_method_crl + }; + +static const CERTRevocationFlags certRev_NSS_3_11_Ocsp_Enabled_Soft_Policy = { + { /* leafTests */ + 2, + certRev_NSS_3_11_Ocsp_Enabled_Soft_Policy_LeafFlags, + 1, + &certRev_NSS_3_11_Ocsp_Enabled_Soft_Policy_Method_Preference, + 0 }, + { /* chainTests */ + 2, + certRev_NSS_3_11_Ocsp_Enabled_Soft_Policy_ChainFlags, + 0, + 0, + 0 } +}; +#endif /* NSS_DISABLE_LIBPKIX */ + +extern const CERTRevocationFlags * +CERT_GetClassicOCSPEnabledSoftFailurePolicy() +{ +#ifdef NSS_DISABLE_LIBPKIX + PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); + return NULL; +#else + return &certRev_NSS_3_11_Ocsp_Enabled_Soft_Policy; +#endif /* NSS_DISABLE_LIBPKIX */ +} + +#ifndef NSS_DISABLE_LIBPKIX +static PRUint64 certRev_NSS_3_11_Ocsp_Enabled_Hard_Policy_LeafFlags[2] = { + /* crl */ + CERT_REV_M_TEST_USING_THIS_METHOD | + CERT_REV_M_FORBID_NETWORK_FETCHING | + CERT_REV_M_CONTINUE_TESTING_ON_FRESH_INFO, + /* ocsp */ + CERT_REV_M_TEST_USING_THIS_METHOD | + CERT_REV_M_FAIL_ON_MISSING_FRESH_INFO +}; + +static PRUint64 certRev_NSS_3_11_Ocsp_Enabled_Hard_Policy_ChainFlags[2] = { + /* crl */ + CERT_REV_M_TEST_USING_THIS_METHOD | + CERT_REV_M_FORBID_NETWORK_FETCHING | + CERT_REV_M_CONTINUE_TESTING_ON_FRESH_INFO, + /* ocsp */ + 0 +}; + +static CERTRevocationMethodIndex + certRev_NSS_3_11_Ocsp_Enabled_Hard_Policy_Method_Preference = { + cert_revocation_method_crl + }; + +static const CERTRevocationFlags certRev_NSS_3_11_Ocsp_Enabled_Hard_Policy = { + { /* leafTests */ + 2, + certRev_NSS_3_11_Ocsp_Enabled_Hard_Policy_LeafFlags, + 1, + &certRev_NSS_3_11_Ocsp_Enabled_Hard_Policy_Method_Preference, + 0 }, + { /* chainTests */ + 2, + certRev_NSS_3_11_Ocsp_Enabled_Hard_Policy_ChainFlags, + 0, + 0, + 0 } +}; +#endif /* NSS_DISABLE_LIBPKIX */ + +extern const CERTRevocationFlags * +CERT_GetClassicOCSPEnabledHardFailurePolicy() +{ +#ifdef NSS_DISABLE_LIBPKIX + PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); + return NULL; +#else + return &certRev_NSS_3_11_Ocsp_Enabled_Hard_Policy; +#endif /* NSS_DISABLE_LIBPKIX */ +} + +#ifndef NSS_DISABLE_LIBPKIX +static PRUint64 certRev_NSS_3_11_Ocsp_Disabled_Policy_LeafFlags[2] = { + /* crl */ + CERT_REV_M_TEST_USING_THIS_METHOD | + CERT_REV_M_FORBID_NETWORK_FETCHING | + CERT_REV_M_CONTINUE_TESTING_ON_FRESH_INFO, + /* ocsp */ + 0 +}; + +static PRUint64 certRev_NSS_3_11_Ocsp_Disabled_Policy_ChainFlags[2] = { + /* crl */ + CERT_REV_M_TEST_USING_THIS_METHOD | + CERT_REV_M_FORBID_NETWORK_FETCHING | + CERT_REV_M_CONTINUE_TESTING_ON_FRESH_INFO, + /* ocsp */ + 0 +}; + +static const CERTRevocationFlags certRev_NSS_3_11_Ocsp_Disabled_Policy = { + { /* leafTests */ + 2, + certRev_NSS_3_11_Ocsp_Disabled_Policy_LeafFlags, + 0, + 0, + 0 }, + { /* chainTests */ + 2, + certRev_NSS_3_11_Ocsp_Disabled_Policy_ChainFlags, + 0, + 0, + 0 } +}; +#endif /* NSS_DISABLE_LIBPKIX */ + +extern const CERTRevocationFlags * +CERT_GetClassicOCSPDisabledPolicy() +{ +#ifdef NSS_DISABLE_LIBPKIX + PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); + return NULL; +#else + return &certRev_NSS_3_11_Ocsp_Disabled_Policy; +#endif /* NSS_DISABLE_LIBPKIX */ +} + +#ifndef NSS_DISABLE_LIBPKIX +static PRUint64 certRev_PKIX_Verify_Nist_Policy_LeafFlags[2] = { + /* crl */ + CERT_REV_M_TEST_USING_THIS_METHOD | + CERT_REV_M_FAIL_ON_MISSING_FRESH_INFO | + CERT_REV_M_REQUIRE_INFO_ON_MISSING_SOURCE, + /* ocsp */ + 0 +}; + +static PRUint64 certRev_PKIX_Verify_Nist_Policy_ChainFlags[2] = { + /* crl */ + CERT_REV_M_TEST_USING_THIS_METHOD | + CERT_REV_M_FAIL_ON_MISSING_FRESH_INFO | + CERT_REV_M_REQUIRE_INFO_ON_MISSING_SOURCE, + /* ocsp */ + 0 +}; + +static const CERTRevocationFlags certRev_PKIX_Verify_Nist_Policy = { + { /* leafTests */ + 2, + certRev_PKIX_Verify_Nist_Policy_LeafFlags, + 0, + 0, + 0 }, + { /* chainTests */ + 2, + certRev_PKIX_Verify_Nist_Policy_ChainFlags, + 0, + 0, + 0 } +}; +#endif /* NSS_DISABLE_LIBPKIX */ + +extern const CERTRevocationFlags * +CERT_GetPKIXVerifyNistRevocationPolicy() +{ +#ifdef NSS_DISABLE_LIBPKIX + PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); + return NULL; +#else + return &certRev_PKIX_Verify_Nist_Policy; +#endif /* NSS_DISABLE_LIBPKIX */ +} + +CERTRevocationFlags * +CERT_AllocCERTRevocationFlags( + PRUint32 number_leaf_methods, PRUint32 number_leaf_pref_methods, + PRUint32 number_chain_methods, PRUint32 number_chain_pref_methods) +{ +#ifdef NSS_DISABLE_LIBPKIX + PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); + return NULL; +#else + CERTRevocationFlags *flags; + + flags = PORT_New(CERTRevocationFlags); + if (!flags) + return (NULL); + + flags->leafTests.number_of_defined_methods = number_leaf_methods; + flags->leafTests.cert_rev_flags_per_method = + PORT_NewArray(PRUint64, number_leaf_methods); + + flags->leafTests.number_of_preferred_methods = number_leaf_pref_methods; + flags->leafTests.preferred_methods = + PORT_NewArray(CERTRevocationMethodIndex, number_leaf_pref_methods); + + flags->chainTests.number_of_defined_methods = number_chain_methods; + flags->chainTests.cert_rev_flags_per_method = + PORT_NewArray(PRUint64, number_chain_methods); + + flags->chainTests.number_of_preferred_methods = number_chain_pref_methods; + flags->chainTests.preferred_methods = + PORT_NewArray(CERTRevocationMethodIndex, number_chain_pref_methods); + + if (!flags->leafTests.cert_rev_flags_per_method || + !flags->leafTests.preferred_methods || + !flags->chainTests.cert_rev_flags_per_method || + !flags->chainTests.preferred_methods) { + CERT_DestroyCERTRevocationFlags(flags); + return (NULL); + } + + return flags; +#endif /* NSS_DISABLE_LIBPKIX */ +} + +void +CERT_DestroyCERTRevocationFlags(CERTRevocationFlags *flags) +{ +#ifdef NSS_DISABLE_LIBPKIX + PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); + return; +#else + if (!flags) + return; + + if (flags->leafTests.cert_rev_flags_per_method) + PORT_Free(flags->leafTests.cert_rev_flags_per_method); + + if (flags->leafTests.preferred_methods) + PORT_Free(flags->leafTests.preferred_methods); + + if (flags->chainTests.cert_rev_flags_per_method) + PORT_Free(flags->chainTests.cert_rev_flags_per_method); + + if (flags->chainTests.preferred_methods) + PORT_Free(flags->chainTests.preferred_methods); + + PORT_Free(flags); +#endif /* NSS_DISABLE_LIBPKIX */ +} + +/* + * CERT_PKIXVerifyCert + * + * Verify a Certificate using the PKIX library. + * + * Parameters: + * cert - the target certificate to verify. Must be non-null + * params - an array of type/value parameters which can be + * used to modify the behavior of the validation + * algorithm, or supply additional constraints. + * + * outputTrustAnchor - the trust anchor which the certificate + * chains to. The caller is responsible + * for freeing this. + * + * Example Usage: + * CERTValParam args[3]; + * args[0].type = cvpt_policyOID; + * args[0].value.si = oid; + * args[1].type = revCheckRequired; + * args[1].value.b = PR_TRUE; + * args[2].type = cvpt_end; + * + * CERT_PKIXVerifyCert(cert, &output, args + */ +SECStatus +CERT_PKIXVerifyCert( + CERTCertificate *cert, + SECCertificateUsage usages, + CERTValInParam *paramsIn, + CERTValOutParam *paramsOut, + void *wincx) +{ +#ifdef NSS_DISABLE_LIBPKIX + PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); + return SECFailure; +#else + SECStatus r = SECFailure; + PKIX_Error *error = NULL; + PKIX_ProcessingParams *procParams = NULL; + PKIX_BuildResult *buildResult = NULL; + void *nbioContext = NULL; /* for non-blocking IO */ + void *buildState = NULL; /* for non-blocking IO */ + PKIX_CertSelector *certSelector = NULL; + PKIX_List *certStores = NULL; + PKIX_ValidateResult *valResult = NULL; + PKIX_VerifyNode *verifyNode = NULL; + PKIX_TrustAnchor *trustAnchor = NULL; + PKIX_PL_Cert *trustAnchorCert = NULL; + PKIX_List *builtCertList = NULL; + CERTValOutParam *oparam = NULL; + int i = 0; + + void *plContext = NULL; + +#ifdef PKIX_OBJECT_LEAK_TEST + int leakedObjNum = 0; + int memLeakLoopCount = 0; + int objCountTable[PKIX_NUMTYPES]; + int fnInvLocalCount = 0; + PKIX_Boolean savedUsePkixEngFlag = usePKIXValidationEngine; + + if (usePKIXValidationEngine) { + /* current memory leak testing implementation does not allow + * to run simultaneous tests one the same or a different threads. + * Setting the variable to false, to make additional chain + * validations be handled by old nss. */ + usePKIXValidationEngine = PR_FALSE; + } + testStartFnStackPosition = 1; + fnStackNameArr[0] = "CERT_PKIXVerifyCert"; + fnStackInvCountArr[0] = 0; + PKIX_Boolean abortOnLeak = + (PR_GetEnvSecure("PKIX_OBJECT_LEAK_TEST_ABORT_ON_LEAK") == NULL) ? PKIX_FALSE + : PKIX_TRUE; + runningLeakTest = PKIX_TRUE; + + /* Prevent multi-threaded run of object leak test */ + fnInvLocalCount = PR_ATOMIC_INCREMENT(¶llelFnInvocationCount); + PORT_Assert(fnInvLocalCount == 1); + + do { + r = SECFailure; + error = NULL; + procParams = NULL; + buildResult = NULL; + nbioContext = NULL; /* for non-blocking IO */ + buildState = NULL; /* for non-blocking IO */ + certSelector = NULL; + certStores = NULL; + valResult = NULL; + verifyNode = NULL; + trustAnchor = NULL; + trustAnchorCert = NULL; + builtCertList = NULL; + oparam = NULL; + i = 0; + errorGenerated = PKIX_FALSE; + stackPosition = 0; + + if (leakedObjNum) { + pkix_pl_lifecycle_ObjectTableUpdate(objCountTable); + } + memLeakLoopCount += 1; +#endif /* PKIX_OBJECT_LEAK_TEST */ + + error = PKIX_PL_NssContext_Create( + 0, PR_FALSE /*use arena*/, wincx, &plContext); + if (error != NULL) { /* need pkix->nss error map */ + PORT_SetError(SEC_ERROR_CERT_NOT_VALID); + goto cleanup; + } + + error = pkix_pl_NssContext_SetCertUsage(usages, plContext); + if (error != NULL) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + goto cleanup; + } + + error = PKIX_ProcessingParams_Create(&procParams, plContext); + if (error != NULL) { /* need pkix->nss error map */ + PORT_SetError(SEC_ERROR_CERT_NOT_VALID); + goto cleanup; + } + + /* local cert store should be set into procParams before + * filling in revocation settings. */ + certStores = cert_GetCertStores(plContext); + if (certStores == NULL) { + goto cleanup; + } + error = PKIX_ProcessingParams_SetCertStores(procParams, certStores, plContext); + if (error != NULL) { + goto cleanup; + } + + /* now process the extensible input parameters structure */ + if (paramsIn != NULL) { + i = 0; + while (paramsIn[i].type != cert_pi_end) { + if (paramsIn[i].type >= cert_pi_max) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + goto cleanup; + } + if (cert_pkixSetParam(procParams, + ¶msIn[i], plContext) != + SECSuccess) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + goto cleanup; + } + i++; + } + } + + certSelector = cert_GetTargetCertConstraints(cert, plContext); + if (certSelector == NULL) { + goto cleanup; + } + error = PKIX_ProcessingParams_SetTargetCertConstraints(procParams, certSelector, plContext); + if (error != NULL) { + goto cleanup; + } + + error = PKIX_BuildChain(procParams, &nbioContext, + &buildState, &buildResult, &verifyNode, + plContext); + if (error != NULL) { + goto cleanup; + } + + error = PKIX_BuildResult_GetValidateResult(buildResult, &valResult, + plContext); + if (error != NULL) { + goto cleanup; + } + + error = PKIX_ValidateResult_GetTrustAnchor(valResult, &trustAnchor, + plContext); + if (error != NULL) { + goto cleanup; + } + + if (trustAnchor != NULL) { + error = PKIX_TrustAnchor_GetTrustedCert(trustAnchor, &trustAnchorCert, + plContext); + if (error != NULL) { + goto cleanup; + } + } + +#ifdef PKIX_OBJECT_LEAK_TEST + /* Can not continue if error was generated but not returned. + * Jumping to cleanup. */ + if (errorGenerated) + goto cleanup; +#endif /* PKIX_OBJECT_LEAK_TEST */ + + oparam = cert_pkix_FindOutputParam(paramsOut, cert_po_trustAnchor); + if (oparam != NULL) { + if (trustAnchorCert != NULL) { + oparam->value.pointer.cert = + cert_NSSCertFromPKIXCert(trustAnchorCert); + } else { + oparam->value.pointer.cert = NULL; + } + } + + error = PKIX_BuildResult_GetCertChain(buildResult, &builtCertList, + plContext); + if (error != NULL) { + goto cleanup; + } + + oparam = cert_pkix_FindOutputParam(paramsOut, cert_po_certList); + if (oparam != NULL) { + error = cert_PkixToNssCertsChain(builtCertList, + &oparam->value.pointer.chain, + plContext); + if (error) + goto cleanup; + } + + r = SECSuccess; + + cleanup: + if (verifyNode) { + /* Return validation log only upon error. */ + oparam = cert_pkix_FindOutputParam(paramsOut, cert_po_errorLog); +#ifdef PKIX_OBJECT_LEAK_TEST + if (!errorGenerated) +#endif /* PKIX_OBJECT_LEAK_TEST */ + if (r && oparam != NULL) { + PKIX_Error *tmpError = + cert_GetLogFromVerifyNode(oparam->value.pointer.log, + verifyNode, plContext); + if (tmpError) { + PKIX_PL_Object_DecRef((PKIX_PL_Object *)tmpError, plContext); + } + } + PKIX_PL_Object_DecRef((PKIX_PL_Object *)verifyNode, plContext); + } + + if (procParams != NULL) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)procParams, plContext); + + if (trustAnchorCert != NULL) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)trustAnchorCert, plContext); + + if (trustAnchor != NULL) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)trustAnchor, plContext); + + if (valResult != NULL) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)valResult, plContext); + + if (buildResult != NULL) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)buildResult, plContext); + + if (certStores != NULL) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)certStores, plContext); + + if (certSelector != NULL) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)certSelector, plContext); + + if (builtCertList != NULL) + PKIX_PL_Object_DecRef((PKIX_PL_Object *)builtCertList, plContext); + + if (error != NULL) { + SECErrorCodes nssErrorCode = 0; + + cert_PkixErrorToNssCode(error, &nssErrorCode, plContext); + cert_pkixDestroyValOutParam(paramsOut); + PORT_SetError(nssErrorCode); + PKIX_PL_Object_DecRef((PKIX_PL_Object *)error, plContext); + } + + PKIX_PL_NssContext_Destroy(plContext); + +#ifdef PKIX_OBJECT_LEAK_TEST + leakedObjNum = + pkix_pl_lifecycle_ObjectLeakCheck(leakedObjNum ? objCountTable : NULL); + + if (pkixLog && leakedObjNum) { + PR_LOG(pkixLog, 1, ("The generated error caused an object leaks. Loop %d." + "Stack %s\n", + memLeakLoopCount, errorFnStackString)); + } + PR_Free(errorFnStackString); + errorFnStackString = NULL; + if (abortOnLeak) { + PORT_Assert(leakedObjNum == 0); + } + + } while (errorGenerated); + + runningLeakTest = PKIX_FALSE; + PR_ATOMIC_DECREMENT(¶llelFnInvocationCount); + usePKIXValidationEngine = savedUsePkixEngFlag; +#endif /* PKIX_OBJECT_LEAK_TEST */ + + return r; +#endif /* NSS_DISABLE_LIBPKIX */ +} diff --git a/security/nss/lib/certhigh/crlv2.c b/security/nss/lib/certhigh/crlv2.c new file mode 100644 index 0000000000..d58d4e083b --- /dev/null +++ b/security/nss/lib/certhigh/crlv2.c @@ -0,0 +1,160 @@ +/* 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/. */ + +/* + * Code for dealing with x.509 v3 crl and crl entries extensions. + */ + +#include "cert.h" +#include "secitem.h" +#include "secoid.h" +#include "secoidt.h" +#include "secder.h" +#include "secasn1.h" +#include "certxutl.h" + +SECStatus +CERT_FindCRLExtensionByOID(CERTCrl *crl, SECItem *oid, SECItem *value) +{ + return (cert_FindExtensionByOID(crl->extensions, oid, value)); +} + +SECStatus +CERT_FindCRLExtension(CERTCrl *crl, int tag, SECItem *value) +{ + return (cert_FindExtension(crl->extensions, tag, value)); +} + +/* Callback to set extensions and adjust verison */ +static void +SetCrlExts(void *object, CERTCertExtension **exts) +{ + CERTCrl *crl = (CERTCrl *)object; + + crl->extensions = exts; + DER_SetUInteger(crl->arena, &crl->version, SEC_CRL_VERSION_2); +} + +void * +CERT_StartCRLExtensions(CERTCrl *crl) +{ + return (cert_StartExtensions((void *)crl, crl->arena, SetCrlExts)); +} + +static void +SetCrlEntryExts(void *object, CERTCertExtension **exts) +{ + CERTCrlEntry *crlEntry = (CERTCrlEntry *)object; + + crlEntry->extensions = exts; +} + +void * +CERT_StartCRLEntryExtensions(CERTCrl *crl, CERTCrlEntry *entry) +{ + return (cert_StartExtensions(entry, crl->arena, SetCrlEntryExts)); +} + +SECStatus +CERT_FindCRLNumberExten(PLArenaPool *arena, CERTCrl *crl, + SECItem *value) +{ + SECItem encodedExtenValue; + SECItem *tmpItem = NULL; + SECStatus rv; + void *mark = NULL; + + encodedExtenValue.data = NULL; + encodedExtenValue.len = 0; + + rv = cert_FindExtension(crl->extensions, SEC_OID_X509_CRL_NUMBER, + &encodedExtenValue); + if (rv != SECSuccess) + return (rv); + + mark = PORT_ArenaMark(arena); + + tmpItem = SECITEM_ArenaDupItem(arena, &encodedExtenValue); + if (tmpItem) { + rv = SEC_QuickDERDecodeItem(arena, value, + SEC_ASN1_GET(SEC_IntegerTemplate), + tmpItem); + } else { + rv = SECFailure; + } + + PORT_Free(encodedExtenValue.data); + if (rv == SECFailure) { + PORT_ArenaRelease(arena, mark); + } else { + PORT_ArenaUnmark(arena, mark); + } + return (rv); +} + +SECStatus +CERT_FindCRLEntryReasonExten(CERTCrlEntry *crlEntry, + CERTCRLEntryReasonCode *value) +{ + SECItem wrapperItem = { siBuffer, 0 }; + SECItem tmpItem = { siBuffer, 0 }; + SECStatus rv; + PLArenaPool *arena = NULL; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!arena) { + return (SECFailure); + } + + rv = cert_FindExtension(crlEntry->extensions, SEC_OID_X509_REASON_CODE, + &wrapperItem); + if (rv != SECSuccess) { + goto loser; + } + + rv = SEC_QuickDERDecodeItem(arena, &tmpItem, + SEC_ASN1_GET(SEC_EnumeratedTemplate), + &wrapperItem); + + if (rv != SECSuccess) { + goto loser; + } + + *value = (CERTCRLEntryReasonCode)DER_GetInteger(&tmpItem); + +loser: + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } + + if (wrapperItem.data) { + PORT_Free(wrapperItem.data); + } + + return (rv); +} + +SECStatus +CERT_FindInvalidDateExten(CERTCrl *crl, PRTime *value) +{ + SECItem encodedExtenValue; + SECItem decodedExtenValue = { siBuffer, 0 }; + SECStatus rv; + + encodedExtenValue.data = decodedExtenValue.data = NULL; + encodedExtenValue.len = decodedExtenValue.len = 0; + + rv = cert_FindExtension(crl->extensions, SEC_OID_X509_INVALID_DATE, &encodedExtenValue); + if (rv != SECSuccess) + return (rv); + + rv = SEC_ASN1DecodeItem(NULL, &decodedExtenValue, + SEC_ASN1_GET(SEC_GeneralizedTimeTemplate), + &encodedExtenValue); + if (rv == SECSuccess) + rv = DER_GeneralizedTimeToTime(value, &encodedExtenValue); + PORT_Free(decodedExtenValue.data); + PORT_Free(encodedExtenValue.data); + return (rv); +} diff --git a/security/nss/lib/certhigh/exports.gyp b/security/nss/lib/certhigh/exports.gyp new file mode 100644 index 0000000000..b8e2f3ebca --- /dev/null +++ b/security/nss/lib/certhigh/exports.gyp @@ -0,0 +1,33 @@ +# 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/. +{ + 'includes': [ + '../../coreconf/config.gypi' + ], + 'targets': [ + { + 'target_name': 'lib_certhigh_exports', + 'type': 'none', + 'copies': [ + { + 'files': [ + 'ocsp.h', + 'ocspt.h' + ], + 'destination': '<(nss_public_dist_dir)/<(module)' + }, + { + 'files': [ + 'ocspi.h', + 'ocspti.h' + ], + 'destination': '<(nss_private_dist_dir)/<(module)' + } + ] + } + ], + 'variables': { + 'module': 'nss' + } +} diff --git a/security/nss/lib/certhigh/manifest.mn b/security/nss/lib/certhigh/manifest.mn new file mode 100644 index 0000000000..b843e06869 --- /dev/null +++ b/security/nss/lib/certhigh/manifest.mn @@ -0,0 +1,35 @@ +# +# 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/. +CORE_DEPTH = ../.. + +EXPORTS = \ + ocsp.h \ + ocspt.h \ + $(NULL) + +PRIVATE_EXPORTS = \ + ocspti.h \ + ocspi.h \ + $(NULL) + +MODULE = nss + +CSRCS = \ + certhtml.c \ + certreq.c \ + crlv2.c \ + ocsp.c \ + ocspsig.c \ + certhigh.c \ + certvfy.c \ + certvfypkix.c \ + xcrldist.c \ + $(NULL) + +LIBRARY_NAME = certhi +SHARED_LIBRARY = $(NULL) + +# This part of the code, including all sub-dirs, can be optimized for size +export ALLOW_OPT_CODE_SIZE = 1 diff --git a/security/nss/lib/certhigh/ocsp.c b/security/nss/lib/certhigh/ocsp.c new file mode 100644 index 0000000000..4fccbc9012 --- /dev/null +++ b/security/nss/lib/certhigh/ocsp.c @@ -0,0 +1,6119 @@ +/* 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/. */ + +/* + * Implementation of OCSP services, for both client and server. + * (XXX, really, mostly just for client right now, but intended to do both.) + */ + +#include "prerror.h" +#include "prprf.h" +#include "plarena.h" +#include "prnetdb.h" + +#include "seccomon.h" +#include "secitem.h" +#include "secoidt.h" +#include "secasn1.h" +#include "secder.h" +#include "cert.h" +#include "certi.h" +#include "xconst.h" +#include "secerr.h" +#include "secoid.h" +#include "hasht.h" +#include "sechash.h" +#include "secasn1.h" +#include "plbase64.h" +#include "keyhi.h" +#include "cryptohi.h" +#include "ocsp.h" +#include "ocspti.h" +#include "ocspi.h" +#include "genname.h" +#include "certxutl.h" +#include "pk11func.h" /* for PK11_HashBuf */ +#include <stdarg.h> +#include <plhash.h> + +#define DEFAULT_OCSP_CACHE_SIZE 1000 +#define DEFAULT_MINIMUM_SECONDS_TO_NEXT_OCSP_FETCH_ATTEMPT 1 * 60 * 60L +#define DEFAULT_MAXIMUM_SECONDS_TO_NEXT_OCSP_FETCH_ATTEMPT 24 * 60 * 60L +#define DEFAULT_OSCP_TIMEOUT_SECONDS 60 +#define MICROSECONDS_PER_SECOND 1000000L + +typedef struct OCSPCacheItemStr OCSPCacheItem; +typedef struct OCSPCacheDataStr OCSPCacheData; + +struct OCSPCacheItemStr { + /* LRU linking */ + OCSPCacheItem *moreRecent; + OCSPCacheItem *lessRecent; + + /* key */ + CERTOCSPCertID *certID; + /* CertID's arena also used to allocate "this" cache item */ + + /* cache control information */ + PRTime nextFetchAttemptTime; + + /* Cached contents. Use a separate arena, because lifetime is different */ + PLArenaPool *certStatusArena; /* NULL means: no cert status cached */ + ocspCertStatus certStatus; + + /* This may contain an error code when no OCSP response is available. */ + SECErrorCodes missingResponseError; + + PRPackedBool haveThisUpdate; + PRPackedBool haveNextUpdate; + PRTime thisUpdate; + PRTime nextUpdate; +}; + +struct OCSPCacheDataStr { + PLHashTable *entries; + PRUint32 numberOfEntries; + OCSPCacheItem *MRUitem; /* most recently used cache item */ + OCSPCacheItem *LRUitem; /* least recently used cache item */ +}; + +static struct OCSPGlobalStruct { + PRMonitor *monitor; + const SEC_HttpClientFcn *defaultHttpClientFcn; + PRInt32 maxCacheEntries; + PRUint32 minimumSecondsToNextFetchAttempt; + PRUint32 maximumSecondsToNextFetchAttempt; + PRUint32 timeoutSeconds; + OCSPCacheData cache; + SEC_OcspFailureMode ocspFailureMode; + CERT_StringFromCertFcn alternateOCSPAIAFcn; + PRBool forcePost; +} OCSP_Global = { NULL, + NULL, + DEFAULT_OCSP_CACHE_SIZE, + DEFAULT_MINIMUM_SECONDS_TO_NEXT_OCSP_FETCH_ATTEMPT, + DEFAULT_MAXIMUM_SECONDS_TO_NEXT_OCSP_FETCH_ATTEMPT, + DEFAULT_OSCP_TIMEOUT_SECONDS, + { NULL, 0, NULL, NULL }, + ocspMode_FailureIsVerificationFailure, + NULL, + PR_FALSE }; + +/* Forward declarations */ +static SECItem * +ocsp_GetEncodedOCSPResponseFromRequest(PLArenaPool *arena, + CERTOCSPRequest *request, + const char *location, + const char *method, + PRTime time, + PRBool addServiceLocator, + void *pwArg, + CERTOCSPRequest **pRequest); +static SECStatus +ocsp_GetOCSPStatusFromNetwork(CERTCertDBHandle *handle, + CERTOCSPCertID *certID, + CERTCertificate *cert, + PRTime time, + void *pwArg, + PRBool *certIDWasConsumed, + SECStatus *rv_ocsp); + +static SECStatus +ocsp_GetDecodedVerifiedSingleResponseForID(CERTCertDBHandle *handle, + CERTOCSPCertID *certID, + CERTCertificate *cert, + PRTime time, + void *pwArg, + const SECItem *encodedResponse, + CERTOCSPResponse **pDecodedResponse, + CERTOCSPSingleResponse **pSingle); + +static SECStatus +ocsp_CertRevokedAfter(ocspRevokedInfo *revokedInfo, PRTime time); + +static CERTOCSPCertID * +cert_DupOCSPCertID(const CERTOCSPCertID *src); + +#ifndef DEBUG +#define OCSP_TRACE(msg) +#define OCSP_TRACE_TIME(msg, time) +#define OCSP_TRACE_CERT(cert) +#define OCSP_TRACE_CERTID(certid) +#else +#define OCSP_TRACE(msg) ocsp_Trace msg +#define OCSP_TRACE_TIME(msg, time) ocsp_dumpStringWithTime(msg, time) +#define OCSP_TRACE_CERT(cert) dumpCertificate(cert) +#define OCSP_TRACE_CERTID(certid) dumpCertID(certid) + +#if defined(XP_UNIX) || defined(XP_WIN32) || defined(XP_MACOSX) +#define NSS_HAVE_GETENV 1 +#endif + +static PRBool +wantOcspTrace(void) +{ + static PRBool firstTime = PR_TRUE; + static PRBool wantTrace = PR_FALSE; + +#ifdef NSS_HAVE_GETENV + if (firstTime) { + char *ev = PR_GetEnvSecure("NSS_TRACE_OCSP"); + if (ev && ev[0]) { + wantTrace = PR_TRUE; + } + firstTime = PR_FALSE; + } +#endif + return wantTrace; +} + +static void +ocsp_Trace(const char *format, ...) +{ + char buf[2000]; + va_list args; + + if (!wantOcspTrace()) + return; + va_start(args, format); + PR_vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + PR_LogPrint("%s", buf); +} + +static void +ocsp_dumpStringWithTime(const char *str, PRTime time) +{ + PRExplodedTime timePrintable; + char timestr[256]; + + if (!wantOcspTrace()) + return; + PR_ExplodeTime(time, PR_GMTParameters, &timePrintable); + if (PR_FormatTime(timestr, 256, "%a %b %d %H:%M:%S %Y", &timePrintable)) { + ocsp_Trace("OCSP %s %s\n", str, timestr); + } +} + +static void +printHexString(const char *prefix, SECItem *hexval) +{ + unsigned int i; + char *hexbuf = NULL; + + for (i = 0; i < hexval->len; i++) { + if (i != hexval->len - 1) { + hexbuf = PR_sprintf_append(hexbuf, "%02x:", hexval->data[i]); + } else { + hexbuf = PR_sprintf_append(hexbuf, "%02x", hexval->data[i]); + } + } + if (hexbuf) { + ocsp_Trace("%s %s\n", prefix, hexbuf); + PR_smprintf_free(hexbuf); + } +} + +static void +dumpCertificate(CERTCertificate *cert) +{ + if (!wantOcspTrace()) + return; + + ocsp_Trace("OCSP ----------------\n"); + ocsp_Trace("OCSP ## SUBJECT: %s\n", cert->subjectName); + { + PRTime timeBefore, timeAfter; + PRExplodedTime beforePrintable, afterPrintable; + char beforestr[256], afterstr[256]; + PRStatus rv1, rv2; + DER_DecodeTimeChoice(&timeBefore, &cert->validity.notBefore); + DER_DecodeTimeChoice(&timeAfter, &cert->validity.notAfter); + PR_ExplodeTime(timeBefore, PR_GMTParameters, &beforePrintable); + PR_ExplodeTime(timeAfter, PR_GMTParameters, &afterPrintable); + rv1 = PR_FormatTime(beforestr, 256, "%a %b %d %H:%M:%S %Y", + &beforePrintable); + rv2 = PR_FormatTime(afterstr, 256, "%a %b %d %H:%M:%S %Y", + &afterPrintable); + ocsp_Trace("OCSP ## VALIDITY: %s to %s\n", rv1 ? beforestr : "", + rv2 ? afterstr : ""); + } + ocsp_Trace("OCSP ## ISSUER: %s\n", cert->issuerName); + printHexString("OCSP ## SERIAL NUMBER:", &cert->serialNumber); +} + +static void +dumpCertID(CERTOCSPCertID *certID) +{ + if (!wantOcspTrace()) + return; + + printHexString("OCSP certID issuer", &certID->issuerNameHash); + printHexString("OCSP certID serial", &certID->serialNumber); +} +#endif + +SECStatus +SEC_RegisterDefaultHttpClient(const SEC_HttpClientFcn *fcnTable) +{ + if (!OCSP_Global.monitor) { + PORT_SetError(SEC_ERROR_NOT_INITIALIZED); + return SECFailure; + } + + PR_EnterMonitor(OCSP_Global.monitor); + OCSP_Global.defaultHttpClientFcn = fcnTable; + PR_ExitMonitor(OCSP_Global.monitor); + + return SECSuccess; +} + +SECStatus +CERT_RegisterAlternateOCSPAIAInfoCallBack( + CERT_StringFromCertFcn newCallback, + CERT_StringFromCertFcn *oldCallback) +{ + CERT_StringFromCertFcn old; + + if (!OCSP_Global.monitor) { + PORT_SetError(SEC_ERROR_NOT_INITIALIZED); + return SECFailure; + } + + PR_EnterMonitor(OCSP_Global.monitor); + old = OCSP_Global.alternateOCSPAIAFcn; + OCSP_Global.alternateOCSPAIAFcn = newCallback; + PR_ExitMonitor(OCSP_Global.monitor); + if (oldCallback) + *oldCallback = old; + return SECSuccess; +} + +static PLHashNumber PR_CALLBACK +ocsp_CacheKeyHashFunction(const void *key) +{ + CERTOCSPCertID *cid = (CERTOCSPCertID *)key; + PLHashNumber hash = 0; + unsigned int i; + unsigned char *walk; + + /* a very simple hash calculation for the initial coding phase */ + walk = (unsigned char *)cid->issuerNameHash.data; + for (i = 0; i < cid->issuerNameHash.len; ++i, ++walk) { + hash += *walk; + } + walk = (unsigned char *)cid->issuerKeyHash.data; + for (i = 0; i < cid->issuerKeyHash.len; ++i, ++walk) { + hash += *walk; + } + walk = (unsigned char *)cid->serialNumber.data; + for (i = 0; i < cid->serialNumber.len; ++i, ++walk) { + hash += *walk; + } + return hash; +} + +static PRIntn PR_CALLBACK +ocsp_CacheKeyCompareFunction(const void *v1, const void *v2) +{ + CERTOCSPCertID *cid1 = (CERTOCSPCertID *)v1; + CERTOCSPCertID *cid2 = (CERTOCSPCertID *)v2; + + return (SECEqual == SECITEM_CompareItem(&cid1->issuerNameHash, + &cid2->issuerNameHash) && + SECEqual == SECITEM_CompareItem(&cid1->issuerKeyHash, + &cid2->issuerKeyHash) && + SECEqual == SECITEM_CompareItem(&cid1->serialNumber, + &cid2->serialNumber)); +} + +static SECStatus +ocsp_CopyRevokedInfo(PLArenaPool *arena, ocspCertStatus *dest, + ocspRevokedInfo *src) +{ + SECStatus rv = SECFailure; + void *mark; + + mark = PORT_ArenaMark(arena); + + dest->certStatusInfo.revokedInfo = + (ocspRevokedInfo *)PORT_ArenaZAlloc(arena, sizeof(ocspRevokedInfo)); + if (!dest->certStatusInfo.revokedInfo) { + goto loser; + } + + rv = SECITEM_CopyItem(arena, + &dest->certStatusInfo.revokedInfo->revocationTime, + &src->revocationTime); + if (rv != SECSuccess) { + goto loser; + } + + if (src->revocationReason) { + dest->certStatusInfo.revokedInfo->revocationReason = + SECITEM_ArenaDupItem(arena, src->revocationReason); + if (!dest->certStatusInfo.revokedInfo->revocationReason) { + goto loser; + } + } else { + dest->certStatusInfo.revokedInfo->revocationReason = NULL; + } + + PORT_ArenaUnmark(arena, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(arena, mark); + return SECFailure; +} + +static SECStatus +ocsp_CopyCertStatus(PLArenaPool *arena, ocspCertStatus *dest, + ocspCertStatus *src) +{ + SECStatus rv = SECFailure; + dest->certStatusType = src->certStatusType; + + switch (src->certStatusType) { + case ocspCertStatus_good: + dest->certStatusInfo.goodInfo = + SECITEM_ArenaDupItem(arena, src->certStatusInfo.goodInfo); + if (dest->certStatusInfo.goodInfo != NULL) { + rv = SECSuccess; + } + break; + case ocspCertStatus_revoked: + rv = ocsp_CopyRevokedInfo(arena, dest, + src->certStatusInfo.revokedInfo); + break; + case ocspCertStatus_unknown: + dest->certStatusInfo.unknownInfo = + SECITEM_ArenaDupItem(arena, src->certStatusInfo.unknownInfo); + if (dest->certStatusInfo.unknownInfo != NULL) { + rv = SECSuccess; + } + break; + case ocspCertStatus_other: + default: + PORT_Assert(src->certStatusType == ocspCertStatus_other); + dest->certStatusInfo.otherInfo = + SECITEM_ArenaDupItem(arena, src->certStatusInfo.otherInfo); + if (dest->certStatusInfo.otherInfo != NULL) { + rv = SECSuccess; + } + break; + } + return rv; +} + +static void +ocsp_AddCacheItemToLinkedList(OCSPCacheData *cache, OCSPCacheItem *new_most_recent) +{ + PR_EnterMonitor(OCSP_Global.monitor); + + if (!cache->LRUitem) { + cache->LRUitem = new_most_recent; + } + new_most_recent->lessRecent = cache->MRUitem; + new_most_recent->moreRecent = NULL; + + if (cache->MRUitem) { + cache->MRUitem->moreRecent = new_most_recent; + } + cache->MRUitem = new_most_recent; + + PR_ExitMonitor(OCSP_Global.monitor); +} + +static void +ocsp_RemoveCacheItemFromLinkedList(OCSPCacheData *cache, OCSPCacheItem *item) +{ + PR_EnterMonitor(OCSP_Global.monitor); + + if (!item->lessRecent && !item->moreRecent) { + /* + * Fail gracefully on attempts to remove an item from the list, + * which is currently not part of the list. + * But check for the edge case it is the single entry in the list. + */ + if (item == cache->LRUitem && + item == cache->MRUitem) { + /* remove the single entry */ + PORT_Assert(cache->numberOfEntries == 1); + PORT_Assert(item->moreRecent == NULL); + cache->MRUitem = NULL; + cache->LRUitem = NULL; + } + PR_ExitMonitor(OCSP_Global.monitor); + return; + } + + PORT_Assert(cache->numberOfEntries > 1); + + if (item == cache->LRUitem) { + PORT_Assert(item != cache->MRUitem); + PORT_Assert(item->lessRecent == NULL); + PORT_Assert(item->moreRecent != NULL); + PORT_Assert(item->moreRecent->lessRecent == item); + cache->LRUitem = item->moreRecent; + cache->LRUitem->lessRecent = NULL; + } else if (item == cache->MRUitem) { + PORT_Assert(item->moreRecent == NULL); + PORT_Assert(item->lessRecent != NULL); + PORT_Assert(item->lessRecent->moreRecent == item); + cache->MRUitem = item->lessRecent; + cache->MRUitem->moreRecent = NULL; + } else { + /* remove an entry in the middle of the list */ + PORT_Assert(item->moreRecent != NULL); + PORT_Assert(item->lessRecent != NULL); + PORT_Assert(item->lessRecent->moreRecent == item); + PORT_Assert(item->moreRecent->lessRecent == item); + item->moreRecent->lessRecent = item->lessRecent; + item->lessRecent->moreRecent = item->moreRecent; + } + + item->lessRecent = NULL; + item->moreRecent = NULL; + + PR_ExitMonitor(OCSP_Global.monitor); +} + +static void +ocsp_MakeCacheEntryMostRecent(OCSPCacheData *cache, OCSPCacheItem *new_most_recent) +{ + OCSP_TRACE(("OCSP ocsp_MakeCacheEntryMostRecent THREADID %p\n", + PR_GetCurrentThread())); + PR_EnterMonitor(OCSP_Global.monitor); + if (cache->MRUitem == new_most_recent) { + OCSP_TRACE(("OCSP ocsp_MakeCacheEntryMostRecent ALREADY MOST\n")); + PR_ExitMonitor(OCSP_Global.monitor); + return; + } + OCSP_TRACE(("OCSP ocsp_MakeCacheEntryMostRecent NEW entry\n")); + ocsp_RemoveCacheItemFromLinkedList(cache, new_most_recent); + ocsp_AddCacheItemToLinkedList(cache, new_most_recent); + PR_ExitMonitor(OCSP_Global.monitor); +} + +static PRBool +ocsp_IsCacheDisabled(void) +{ + /* + * maxCacheEntries == 0 means unlimited cache entries + * maxCacheEntries < 0 means cache is disabled + */ + PRBool retval; + PR_EnterMonitor(OCSP_Global.monitor); + retval = (OCSP_Global.maxCacheEntries < 0); + PR_ExitMonitor(OCSP_Global.monitor); + return retval; +} + +static OCSPCacheItem * +ocsp_FindCacheEntry(OCSPCacheData *cache, CERTOCSPCertID *certID) +{ + OCSPCacheItem *found_ocsp_item = NULL; + OCSP_TRACE(("OCSP ocsp_FindCacheEntry\n")); + OCSP_TRACE_CERTID(certID); + PR_EnterMonitor(OCSP_Global.monitor); + if (ocsp_IsCacheDisabled()) + goto loser; + + found_ocsp_item = (OCSPCacheItem *)PL_HashTableLookup( + cache->entries, certID); + if (!found_ocsp_item) + goto loser; + + OCSP_TRACE(("OCSP ocsp_FindCacheEntry FOUND!\n")); + ocsp_MakeCacheEntryMostRecent(cache, found_ocsp_item); + +loser: + PR_ExitMonitor(OCSP_Global.monitor); + return found_ocsp_item; +} + +static void +ocsp_FreeCacheItem(OCSPCacheItem *item) +{ + OCSP_TRACE(("OCSP ocsp_FreeCacheItem\n")); + if (item->certStatusArena) { + PORT_FreeArena(item->certStatusArena, PR_FALSE); + } + if (item->certID->poolp) { + /* freeing this poolp arena will also free item */ + PORT_FreeArena(item->certID->poolp, PR_FALSE); + } +} + +static void +ocsp_RemoveCacheItem(OCSPCacheData *cache, OCSPCacheItem *item) +{ + /* The item we're removing could be either the least recently used item, + * or it could be an item that couldn't get updated with newer status info + * because of an allocation failure, or it could get removed because we're + * cleaning up. + */ + OCSP_TRACE(("OCSP ocsp_RemoveCacheItem, THREADID %p\n", PR_GetCurrentThread())); + PR_EnterMonitor(OCSP_Global.monitor); + + ocsp_RemoveCacheItemFromLinkedList(cache, item); +#ifdef DEBUG + { + PRBool couldRemoveFromHashTable = PL_HashTableRemove(cache->entries, + item->certID); + PORT_Assert(couldRemoveFromHashTable); + } +#else + PL_HashTableRemove(cache->entries, item->certID); +#endif + --cache->numberOfEntries; + ocsp_FreeCacheItem(item); + PR_ExitMonitor(OCSP_Global.monitor); +} + +static void +ocsp_CheckCacheSize(OCSPCacheData *cache) +{ + OCSP_TRACE(("OCSP ocsp_CheckCacheSize\n")); + PR_EnterMonitor(OCSP_Global.monitor); + if (OCSP_Global.maxCacheEntries > 0) { + /* Cache is not disabled. Number of cache entries is limited. + * The monitor ensures that maxCacheEntries remains positive. + */ + while (cache->numberOfEntries > + (PRUint32)OCSP_Global.maxCacheEntries) { + ocsp_RemoveCacheItem(cache, cache->LRUitem); + } + } + PR_ExitMonitor(OCSP_Global.monitor); +} + +SECStatus +CERT_ClearOCSPCache(void) +{ + OCSP_TRACE(("OCSP CERT_ClearOCSPCache\n")); + PR_EnterMonitor(OCSP_Global.monitor); + while (OCSP_Global.cache.numberOfEntries > 0) { + ocsp_RemoveCacheItem(&OCSP_Global.cache, + OCSP_Global.cache.LRUitem); + } + PR_ExitMonitor(OCSP_Global.monitor); + return SECSuccess; +} + +static SECStatus +ocsp_CreateCacheItemAndConsumeCertID(OCSPCacheData *cache, + CERTOCSPCertID *certID, + OCSPCacheItem **pCacheItem) +{ + PLArenaPool *arena; + void *mark; + PLHashEntry *new_hash_entry; + OCSPCacheItem *item; + + PORT_Assert(pCacheItem != NULL); + *pCacheItem = NULL; + + PR_EnterMonitor(OCSP_Global.monitor); + arena = certID->poolp; + mark = PORT_ArenaMark(arena); + + /* ZAlloc will init all Bools to False and all Pointers to NULL + and all error codes to zero/good. */ + item = (OCSPCacheItem *)PORT_ArenaZAlloc(certID->poolp, + sizeof(OCSPCacheItem)); + if (!item) { + goto loser; + } + item->certID = certID; + new_hash_entry = PL_HashTableAdd(cache->entries, item->certID, + item); + if (!new_hash_entry) { + goto loser; + } + ++cache->numberOfEntries; + PORT_ArenaUnmark(arena, mark); + ocsp_AddCacheItemToLinkedList(cache, item); + *pCacheItem = item; + + PR_ExitMonitor(OCSP_Global.monitor); + return SECSuccess; + +loser: + PORT_ArenaRelease(arena, mark); + PR_ExitMonitor(OCSP_Global.monitor); + return SECFailure; +} + +static SECStatus +ocsp_SetCacheItemResponse(OCSPCacheItem *item, + const CERTOCSPSingleResponse *response) +{ + if (item->certStatusArena) { + PORT_FreeArena(item->certStatusArena, PR_FALSE); + item->certStatusArena = NULL; + } + item->haveThisUpdate = item->haveNextUpdate = PR_FALSE; + if (response) { + SECStatus rv; + item->certStatusArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (item->certStatusArena == NULL) { + return SECFailure; + } + rv = ocsp_CopyCertStatus(item->certStatusArena, &item->certStatus, + response->certStatus); + if (rv != SECSuccess) { + PORT_FreeArena(item->certStatusArena, PR_FALSE); + item->certStatusArena = NULL; + return rv; + } + item->missingResponseError = 0; + rv = DER_GeneralizedTimeToTime(&item->thisUpdate, + &response->thisUpdate); + item->haveThisUpdate = (rv == SECSuccess); + if (response->nextUpdate) { + rv = DER_GeneralizedTimeToTime(&item->nextUpdate, + response->nextUpdate); + item->haveNextUpdate = (rv == SECSuccess); + } else { + item->haveNextUpdate = PR_FALSE; + } + } + return SECSuccess; +} + +static void +ocsp_FreshenCacheItemNextFetchAttemptTime(OCSPCacheItem *cacheItem) +{ + PRTime now; + PRTime earliestAllowedNextFetchAttemptTime; + PRTime latestTimeWhenResponseIsConsideredFresh; + + OCSP_TRACE(("OCSP ocsp_FreshenCacheItemNextFetchAttemptTime\n")); + + PR_EnterMonitor(OCSP_Global.monitor); + + now = PR_Now(); + OCSP_TRACE_TIME("now:", now); + + if (cacheItem->haveThisUpdate) { + OCSP_TRACE_TIME("thisUpdate:", cacheItem->thisUpdate); + latestTimeWhenResponseIsConsideredFresh = cacheItem->thisUpdate + + OCSP_Global.maximumSecondsToNextFetchAttempt * + MICROSECONDS_PER_SECOND; + OCSP_TRACE_TIME("latestTimeWhenResponseIsConsideredFresh:", + latestTimeWhenResponseIsConsideredFresh); + } else { + latestTimeWhenResponseIsConsideredFresh = now + + OCSP_Global.minimumSecondsToNextFetchAttempt * + MICROSECONDS_PER_SECOND; + OCSP_TRACE_TIME("no thisUpdate, " + "latestTimeWhenResponseIsConsideredFresh:", + latestTimeWhenResponseIsConsideredFresh); + } + + if (cacheItem->haveNextUpdate) { + OCSP_TRACE_TIME("have nextUpdate:", cacheItem->nextUpdate); + } + + if (cacheItem->haveNextUpdate && + cacheItem->nextUpdate < latestTimeWhenResponseIsConsideredFresh) { + latestTimeWhenResponseIsConsideredFresh = cacheItem->nextUpdate; + OCSP_TRACE_TIME("nextUpdate is smaller than latestFresh, setting " + "latestTimeWhenResponseIsConsideredFresh:", + latestTimeWhenResponseIsConsideredFresh); + } + + earliestAllowedNextFetchAttemptTime = now + + OCSP_Global.minimumSecondsToNextFetchAttempt * + MICROSECONDS_PER_SECOND; + OCSP_TRACE_TIME("earliestAllowedNextFetchAttemptTime:", + earliestAllowedNextFetchAttemptTime); + + if (latestTimeWhenResponseIsConsideredFresh < + earliestAllowedNextFetchAttemptTime) { + latestTimeWhenResponseIsConsideredFresh = + earliestAllowedNextFetchAttemptTime; + OCSP_TRACE_TIME("latest < earliest, setting latest to:", + latestTimeWhenResponseIsConsideredFresh); + } + + cacheItem->nextFetchAttemptTime = + latestTimeWhenResponseIsConsideredFresh; + OCSP_TRACE_TIME("nextFetchAttemptTime", + latestTimeWhenResponseIsConsideredFresh); + + PR_ExitMonitor(OCSP_Global.monitor); +} + +static PRBool +ocsp_IsCacheItemFresh(OCSPCacheItem *cacheItem) +{ + PRTime now; + PRBool fresh; + + now = PR_Now(); + + fresh = cacheItem->nextFetchAttemptTime > now; + + /* Work around broken OCSP responders that return unknown responses for + * certificates, especially certificates that were just recently issued. + */ + if (fresh && cacheItem->certStatusArena && + cacheItem->certStatus.certStatusType == ocspCertStatus_unknown) { + fresh = PR_FALSE; + } + + OCSP_TRACE(("OCSP ocsp_IsCacheItemFresh: %d\n", fresh)); + + return fresh; +} + +/* + * Status in *certIDWasConsumed will always be correct, regardless of + * return value. + * If the caller is unable to transfer ownership of certID, + * then the caller must set certIDWasConsumed to NULL, + * and this function will potentially duplicate the certID object. + */ +static SECStatus +ocsp_CreateOrUpdateCacheEntry(OCSPCacheData *cache, + CERTOCSPCertID *certID, + CERTOCSPSingleResponse *single, + PRBool *certIDWasConsumed) +{ + SECStatus rv; + OCSPCacheItem *cacheItem; + OCSP_TRACE(("OCSP ocsp_CreateOrUpdateCacheEntry\n")); + + if (certIDWasConsumed) + *certIDWasConsumed = PR_FALSE; + + PR_EnterMonitor(OCSP_Global.monitor); + PORT_Assert(OCSP_Global.maxCacheEntries >= 0); + + cacheItem = ocsp_FindCacheEntry(cache, certID); + + /* Don't replace an unknown or revoked entry with an error entry, even if + * the existing entry is expired. Instead, we'll continue to use the + * existing (possibly expired) cache entry until we receive a valid signed + * response to replace it. + */ + if (!single && cacheItem && cacheItem->certStatusArena && + (cacheItem->certStatus.certStatusType == ocspCertStatus_revoked || + cacheItem->certStatus.certStatusType == ocspCertStatus_unknown)) { + PR_ExitMonitor(OCSP_Global.monitor); + return SECSuccess; + } + + if (!cacheItem) { + CERTOCSPCertID *myCertID; + if (certIDWasConsumed) { + myCertID = certID; + *certIDWasConsumed = PR_TRUE; + } else { + myCertID = cert_DupOCSPCertID(certID); + if (!myCertID) { + PR_ExitMonitor(OCSP_Global.monitor); + PORT_SetError(PR_OUT_OF_MEMORY_ERROR); + return SECFailure; + } + } + + rv = ocsp_CreateCacheItemAndConsumeCertID(cache, myCertID, + &cacheItem); + if (rv != SECSuccess) { + PR_ExitMonitor(OCSP_Global.monitor); + return rv; + } + } + if (single) { + PRTime thisUpdate; + rv = DER_GeneralizedTimeToTime(&thisUpdate, &single->thisUpdate); + + if (!cacheItem->haveThisUpdate || + (rv == SECSuccess && cacheItem->thisUpdate < thisUpdate)) { + rv = ocsp_SetCacheItemResponse(cacheItem, single); + if (rv != SECSuccess) { + ocsp_RemoveCacheItem(cache, cacheItem); + PR_ExitMonitor(OCSP_Global.monitor); + return rv; + } + } else { + OCSP_TRACE(("Not caching response because the response is not " + "newer than the cache")); + } + } else { + cacheItem->missingResponseError = PORT_GetError(); + if (cacheItem->certStatusArena) { + PORT_FreeArena(cacheItem->certStatusArena, PR_FALSE); + cacheItem->certStatusArena = NULL; + } + } + ocsp_FreshenCacheItemNextFetchAttemptTime(cacheItem); + ocsp_CheckCacheSize(cache); + + PR_ExitMonitor(OCSP_Global.monitor); + return SECSuccess; +} + +extern SECStatus +CERT_SetOCSPFailureMode(SEC_OcspFailureMode ocspFailureMode) +{ + switch (ocspFailureMode) { + case ocspMode_FailureIsVerificationFailure: + case ocspMode_FailureIsNotAVerificationFailure: + break; + default: + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + PR_EnterMonitor(OCSP_Global.monitor); + OCSP_Global.ocspFailureMode = ocspFailureMode; + PR_ExitMonitor(OCSP_Global.monitor); + return SECSuccess; +} + +SECStatus +CERT_OCSPCacheSettings(PRInt32 maxCacheEntries, + PRUint32 minimumSecondsToNextFetchAttempt, + PRUint32 maximumSecondsToNextFetchAttempt) +{ + if (minimumSecondsToNextFetchAttempt > maximumSecondsToNextFetchAttempt || + maxCacheEntries < -1) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + PR_EnterMonitor(OCSP_Global.monitor); + + if (maxCacheEntries < 0) { + OCSP_Global.maxCacheEntries = -1; /* disable cache */ + } else if (maxCacheEntries == 0) { + OCSP_Global.maxCacheEntries = 0; /* unlimited cache entries */ + } else { + OCSP_Global.maxCacheEntries = maxCacheEntries; + } + + if (minimumSecondsToNextFetchAttempt < + OCSP_Global.minimumSecondsToNextFetchAttempt || + maximumSecondsToNextFetchAttempt < + OCSP_Global.maximumSecondsToNextFetchAttempt) { + /* + * Ensure our existing cache entries are not used longer than the + * new settings allow, we're lazy and just clear the cache + */ + CERT_ClearOCSPCache(); + } + + OCSP_Global.minimumSecondsToNextFetchAttempt = + minimumSecondsToNextFetchAttempt; + OCSP_Global.maximumSecondsToNextFetchAttempt = + maximumSecondsToNextFetchAttempt; + ocsp_CheckCacheSize(&OCSP_Global.cache); + + PR_ExitMonitor(OCSP_Global.monitor); + return SECSuccess; +} + +SECStatus +CERT_SetOCSPTimeout(PRUint32 seconds) +{ + /* no locking, see bug 406120 */ + OCSP_Global.timeoutSeconds = seconds; + return SECSuccess; +} + +/* this function is called at NSS initialization time */ +SECStatus +OCSP_InitGlobal(void) +{ + SECStatus rv = SECFailure; + + if (OCSP_Global.monitor == NULL) { + OCSP_Global.monitor = PR_NewMonitor(); + } + if (!OCSP_Global.monitor) + return SECFailure; + + PR_EnterMonitor(OCSP_Global.monitor); + if (!OCSP_Global.cache.entries) { + OCSP_Global.cache.entries = + PL_NewHashTable(0, + ocsp_CacheKeyHashFunction, + ocsp_CacheKeyCompareFunction, + PL_CompareValues, + NULL, + NULL); + OCSP_Global.ocspFailureMode = ocspMode_FailureIsVerificationFailure; + OCSP_Global.cache.numberOfEntries = 0; + OCSP_Global.cache.MRUitem = NULL; + OCSP_Global.cache.LRUitem = NULL; + } else { + /* + * NSS might call this function twice while attempting to init. + * But it's not allowed to call this again after any activity. + */ + PORT_Assert(OCSP_Global.cache.numberOfEntries == 0); + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + } + if (OCSP_Global.cache.entries) + rv = SECSuccess; + PR_ExitMonitor(OCSP_Global.monitor); + return rv; +} + +SECStatus +OCSP_ShutdownGlobal(void) +{ + if (!OCSP_Global.monitor) + return SECSuccess; + + PR_EnterMonitor(OCSP_Global.monitor); + if (OCSP_Global.cache.entries) { + CERT_ClearOCSPCache(); + PL_HashTableDestroy(OCSP_Global.cache.entries); + OCSP_Global.cache.entries = NULL; + } + PORT_Assert(OCSP_Global.cache.numberOfEntries == 0); + OCSP_Global.cache.MRUitem = NULL; + OCSP_Global.cache.LRUitem = NULL; + + OCSP_Global.defaultHttpClientFcn = NULL; + OCSP_Global.maxCacheEntries = DEFAULT_OCSP_CACHE_SIZE; + OCSP_Global.minimumSecondsToNextFetchAttempt = + DEFAULT_MINIMUM_SECONDS_TO_NEXT_OCSP_FETCH_ATTEMPT; + OCSP_Global.maximumSecondsToNextFetchAttempt = + DEFAULT_MAXIMUM_SECONDS_TO_NEXT_OCSP_FETCH_ATTEMPT; + OCSP_Global.ocspFailureMode = + ocspMode_FailureIsVerificationFailure; + PR_ExitMonitor(OCSP_Global.monitor); + + PR_DestroyMonitor(OCSP_Global.monitor); + OCSP_Global.monitor = NULL; + return SECSuccess; +} + +/* + * A return value of NULL means: + * The application did not register it's own HTTP client. + */ +const SEC_HttpClientFcn * +SEC_GetRegisteredHttpClient(void) +{ + const SEC_HttpClientFcn *retval; + + if (!OCSP_Global.monitor) { + PORT_SetError(SEC_ERROR_NOT_INITIALIZED); + return NULL; + } + + PR_EnterMonitor(OCSP_Global.monitor); + retval = OCSP_Global.defaultHttpClientFcn; + PR_ExitMonitor(OCSP_Global.monitor); + + return retval; +} + +/* + * The following structure is only used internally. It is allocated when + * someone turns on OCSP checking, and hangs off of the status-configuration + * structure in the certdb structure. We use it to keep configuration + * information specific to OCSP checking. + */ +typedef struct ocspCheckingContextStr { + PRBool useDefaultResponder; + char *defaultResponderURI; + char *defaultResponderNickname; + CERTCertificate *defaultResponderCert; +} ocspCheckingContext; + +SEC_ASN1_MKSUB(SEC_AnyTemplate) +SEC_ASN1_MKSUB(SEC_IntegerTemplate) +SEC_ASN1_MKSUB(SEC_NullTemplate) +SEC_ASN1_MKSUB(SEC_OctetStringTemplate) +SEC_ASN1_MKSUB(SEC_PointerToAnyTemplate) +SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate) +SEC_ASN1_MKSUB(SEC_SequenceOfAnyTemplate) +SEC_ASN1_MKSUB(SEC_PointerToGeneralizedTimeTemplate) +SEC_ASN1_MKSUB(SEC_PointerToEnumeratedTemplate) + +/* + * Forward declarations of sub-types, so I can lay out the types in the + * same order as the ASN.1 is laid out in the OCSP spec itself. + * + * These are in alphabetical order (case-insensitive); please keep it that way! + */ +extern const SEC_ASN1Template ocsp_CertIDTemplate[]; +extern const SEC_ASN1Template ocsp_PointerToSignatureTemplate[]; +extern const SEC_ASN1Template ocsp_PointerToResponseBytesTemplate[]; +extern const SEC_ASN1Template ocsp_ResponseDataTemplate[]; +extern const SEC_ASN1Template ocsp_RevokedInfoTemplate[]; +extern const SEC_ASN1Template ocsp_SingleRequestTemplate[]; +extern const SEC_ASN1Template ocsp_SingleResponseTemplate[]; +extern const SEC_ASN1Template ocsp_TBSRequestTemplate[]; + +/* + * Request-related templates... + */ + +/* + * OCSPRequest ::= SEQUENCE { + * tbsRequest TBSRequest, + * optionalSignature [0] EXPLICIT Signature OPTIONAL } + */ +static const SEC_ASN1Template ocsp_OCSPRequestTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(CERTOCSPRequest) }, + { SEC_ASN1_POINTER, + offsetof(CERTOCSPRequest, tbsRequest), + ocsp_TBSRequestTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(CERTOCSPRequest, optionalSignature), + ocsp_PointerToSignatureTemplate }, + { 0 } +}; + +/* + * TBSRequest ::= SEQUENCE { + * version [0] EXPLICIT Version DEFAULT v1, + * requestorName [1] EXPLICIT GeneralName OPTIONAL, + * requestList SEQUENCE OF Request, + * requestExtensions [2] EXPLICIT Extensions OPTIONAL } + * + * Version ::= INTEGER { v1(0) } + * + * Note: this should be static but the AIX compiler doesn't like it (because it + * was forward-declared above); it is not meant to be exported, but this + * is the only way it will compile. + */ +const SEC_ASN1Template ocsp_TBSRequestTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(ocspTBSRequest) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | /* XXX DER_DEFAULT */ + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, + offsetof(ocspTBSRequest, version), + SEC_ASN1_SUB(SEC_IntegerTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 1, + offsetof(ocspTBSRequest, derRequestorName), + SEC_ASN1_SUB(SEC_PointerToAnyTemplate) }, + { SEC_ASN1_SEQUENCE_OF, + offsetof(ocspTBSRequest, requestList), + ocsp_SingleRequestTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 2, + offsetof(ocspTBSRequest, requestExtensions), + CERT_SequenceOfCertExtensionTemplate }, + { 0 } +}; + +/* + * Signature ::= SEQUENCE { + * signatureAlgorithm AlgorithmIdentifier, + * signature BIT STRING, + * certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } + */ +static const SEC_ASN1Template ocsp_SignatureTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(ocspSignature) }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, + offsetof(ocspSignature, signatureAlgorithm), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_BIT_STRING, + offsetof(ocspSignature, signature) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, + offsetof(ocspSignature, derCerts), + SEC_ASN1_SUB(SEC_SequenceOfAnyTemplate) }, + { 0 } +}; + +/* + * This template is just an extra level to use in an explicitly-tagged + * reference to a Signature. + * + * Note: this should be static but the AIX compiler doesn't like it (because it + * was forward-declared above); it is not meant to be exported, but this + * is the only way it will compile. + */ +const SEC_ASN1Template ocsp_PointerToSignatureTemplate[] = { + { SEC_ASN1_POINTER, 0, ocsp_SignatureTemplate } +}; + +/* + * Request ::= SEQUENCE { + * reqCert CertID, + * singleRequestExtensions [0] EXPLICIT Extensions OPTIONAL } + * + * Note: this should be static but the AIX compiler doesn't like it (because it + * was forward-declared above); it is not meant to be exported, but this + * is the only way it will compile. + */ +const SEC_ASN1Template ocsp_SingleRequestTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(ocspSingleRequest) }, + { SEC_ASN1_POINTER, + offsetof(ocspSingleRequest, reqCert), + ocsp_CertIDTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(ocspSingleRequest, singleRequestExtensions), + CERT_SequenceOfCertExtensionTemplate }, + { 0 } +}; + +/* + * This data structure and template (CertID) is used by both OCSP + * requests and responses. It is the only one that is shared. + * + * CertID ::= SEQUENCE { + * hashAlgorithm AlgorithmIdentifier, + * issuerNameHash OCTET STRING, -- Hash of Issuer DN + * issuerKeyHash OCTET STRING, -- Hash of Issuer public key + * serialNumber CertificateSerialNumber } + * + * CertificateSerialNumber ::= INTEGER + * + * Note: this should be static but the AIX compiler doesn't like it (because it + * was forward-declared above); it is not meant to be exported, but this + * is the only way it will compile. + */ +const SEC_ASN1Template ocsp_CertIDTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(CERTOCSPCertID) }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, + offsetof(CERTOCSPCertID, hashAlgorithm), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_OCTET_STRING, + offsetof(CERTOCSPCertID, issuerNameHash) }, + { SEC_ASN1_OCTET_STRING, + offsetof(CERTOCSPCertID, issuerKeyHash) }, + { SEC_ASN1_INTEGER, + offsetof(CERTOCSPCertID, serialNumber) }, + { 0 } +}; + +/* + * Response-related templates... + */ + +/* + * OCSPResponse ::= SEQUENCE { + * responseStatus OCSPResponseStatus, + * responseBytes [0] EXPLICIT ResponseBytes OPTIONAL } + */ +const SEC_ASN1Template ocsp_OCSPResponseTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(CERTOCSPResponse) }, + { SEC_ASN1_ENUMERATED, + offsetof(CERTOCSPResponse, responseStatus) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(CERTOCSPResponse, responseBytes), + ocsp_PointerToResponseBytesTemplate }, + { 0 } +}; + +/* + * ResponseBytes ::= SEQUENCE { + * responseType OBJECT IDENTIFIER, + * response OCTET STRING } + */ +const SEC_ASN1Template ocsp_ResponseBytesTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(ocspResponseBytes) }, + { SEC_ASN1_OBJECT_ID, + offsetof(ocspResponseBytes, responseType) }, + { SEC_ASN1_OCTET_STRING, + offsetof(ocspResponseBytes, response) }, + { 0 } +}; + +/* + * This template is just an extra level to use in an explicitly-tagged + * reference to a ResponseBytes. + * + * Note: this should be static but the AIX compiler doesn't like it (because it + * was forward-declared above); it is not meant to be exported, but this + * is the only way it will compile. + */ +const SEC_ASN1Template ocsp_PointerToResponseBytesTemplate[] = { + { SEC_ASN1_POINTER, 0, ocsp_ResponseBytesTemplate } +}; + +/* + * BasicOCSPResponse ::= SEQUENCE { + * tbsResponseData ResponseData, + * signatureAlgorithm AlgorithmIdentifier, + * signature BIT STRING, + * certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } + */ +static const SEC_ASN1Template ocsp_BasicOCSPResponseTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(ocspBasicOCSPResponse) }, + { SEC_ASN1_ANY | SEC_ASN1_SAVE, + offsetof(ocspBasicOCSPResponse, tbsResponseDataDER) }, + { SEC_ASN1_POINTER, + offsetof(ocspBasicOCSPResponse, tbsResponseData), + ocsp_ResponseDataTemplate }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, + offsetof(ocspBasicOCSPResponse, responseSignature.signatureAlgorithm), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_BIT_STRING, + offsetof(ocspBasicOCSPResponse, responseSignature.signature) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, + offsetof(ocspBasicOCSPResponse, responseSignature.derCerts), + SEC_ASN1_SUB(SEC_SequenceOfAnyTemplate) }, + { 0 } +}; + +/* + * ResponseData ::= SEQUENCE { + * version [0] EXPLICIT Version DEFAULT v1, + * responderID ResponderID, + * producedAt GeneralizedTime, + * responses SEQUENCE OF SingleResponse, + * responseExtensions [1] EXPLICIT Extensions OPTIONAL } + * + * Note: this should be static but the AIX compiler doesn't like it (because it + * was forward-declared above); it is not meant to be exported, but this + * is the only way it will compile. + */ +const SEC_ASN1Template ocsp_ResponseDataTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(ocspResponseData) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | /* XXX DER_DEFAULT */ + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, + offsetof(ocspResponseData, version), + SEC_ASN1_SUB(SEC_IntegerTemplate) }, + { SEC_ASN1_ANY, + offsetof(ocspResponseData, derResponderID) }, + { SEC_ASN1_GENERALIZED_TIME, + offsetof(ocspResponseData, producedAt) }, + { SEC_ASN1_SEQUENCE_OF, + offsetof(ocspResponseData, responses), + ocsp_SingleResponseTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(ocspResponseData, responseExtensions), + CERT_SequenceOfCertExtensionTemplate }, + { 0 } +}; + +/* + * ResponderID ::= CHOICE { + * byName [1] EXPLICIT Name, + * byKey [2] EXPLICIT KeyHash } + * + * KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key + * (excluding the tag and length fields) + * + * XXX Because the ASN.1 encoder and decoder currently do not provide + * a way to automatically handle a CHOICE, we need to do it in two + * steps, looking at the type tag and feeding the exact choice back + * to the ASN.1 code. Hopefully that will change someday and this + * can all be simplified down into a single template. Anyway, for + * now we list each choice as its own template: + */ +const SEC_ASN1Template ocsp_ResponderIDByNameTemplate[] = { + { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(ocspResponderID, responderIDValue.name), + CERT_NameTemplate } +}; +const SEC_ASN1Template ocsp_ResponderIDByKeyTemplate[] = { + { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | + SEC_ASN1_XTRN | 2, + offsetof(ocspResponderID, responderIDValue.keyHash), + SEC_ASN1_SUB(SEC_OctetStringTemplate) } +}; +static const SEC_ASN1Template ocsp_ResponderIDOtherTemplate[] = { + { SEC_ASN1_ANY, + offsetof(ocspResponderID, responderIDValue.other) } +}; + +/* Decode choice container, but leave x509 name object encoded */ +static const SEC_ASN1Template ocsp_ResponderIDDerNameTemplate[] = { + { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | + SEC_ASN1_XTRN | 1, + 0, SEC_ASN1_SUB(SEC_AnyTemplate) } +}; + +/* + * SingleResponse ::= SEQUENCE { + * certID CertID, + * certStatus CertStatus, + * thisUpdate GeneralizedTime, + * nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL, + * singleExtensions [1] EXPLICIT Extensions OPTIONAL } + * + * Note: this should be static but the AIX compiler doesn't like it (because it + * was forward-declared above); it is not meant to be exported, but this + * is the only way it will compile. + */ +const SEC_ASN1Template ocsp_SingleResponseTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(CERTOCSPSingleResponse) }, + { SEC_ASN1_POINTER, + offsetof(CERTOCSPSingleResponse, certID), + ocsp_CertIDTemplate }, + { SEC_ASN1_ANY, + offsetof(CERTOCSPSingleResponse, derCertStatus) }, + { SEC_ASN1_GENERALIZED_TIME, + offsetof(CERTOCSPSingleResponse, thisUpdate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, + offsetof(CERTOCSPSingleResponse, nextUpdate), + SEC_ASN1_SUB(SEC_PointerToGeneralizedTimeTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(CERTOCSPSingleResponse, singleExtensions), + CERT_SequenceOfCertExtensionTemplate }, + { 0 } +}; + +/* + * CertStatus ::= CHOICE { + * good [0] IMPLICIT NULL, + * revoked [1] IMPLICIT RevokedInfo, + * unknown [2] IMPLICIT UnknownInfo } + * + * Because the ASN.1 encoder and decoder currently do not provide + * a way to automatically handle a CHOICE, we need to do it in two + * steps, looking at the type tag and feeding the exact choice back + * to the ASN.1 code. Hopefully that will change someday and this + * can all be simplified down into a single template. Anyway, for + * now we list each choice as its own template: + */ +static const SEC_ASN1Template ocsp_CertStatusGoodTemplate[] = { + { SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 0, + offsetof(ocspCertStatus, certStatusInfo.goodInfo), + SEC_ASN1_SUB(SEC_NullTemplate) } +}; +static const SEC_ASN1Template ocsp_CertStatusRevokedTemplate[] = { + { SEC_ASN1_POINTER | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(ocspCertStatus, certStatusInfo.revokedInfo), + ocsp_RevokedInfoTemplate } +}; +static const SEC_ASN1Template ocsp_CertStatusUnknownTemplate[] = { + { SEC_ASN1_POINTER | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 2, + offsetof(ocspCertStatus, certStatusInfo.unknownInfo), + SEC_ASN1_SUB(SEC_NullTemplate) } +}; +static const SEC_ASN1Template ocsp_CertStatusOtherTemplate[] = { + { SEC_ASN1_POINTER | SEC_ASN1_XTRN, + offsetof(ocspCertStatus, certStatusInfo.otherInfo), + SEC_ASN1_SUB(SEC_AnyTemplate) } +}; + +/* + * RevokedInfo ::= SEQUENCE { + * revocationTime GeneralizedTime, + * revocationReason [0] EXPLICIT CRLReason OPTIONAL } + * + * Note: this should be static but the AIX compiler doesn't like it (because it + * was forward-declared above); it is not meant to be exported, but this + * is the only way it will compile. + */ +const SEC_ASN1Template ocsp_RevokedInfoTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(ocspRevokedInfo) }, + { SEC_ASN1_GENERALIZED_TIME, + offsetof(ocspRevokedInfo, revocationTime) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | + SEC_ASN1_XTRN | 0, + offsetof(ocspRevokedInfo, revocationReason), + SEC_ASN1_SUB(SEC_PointerToEnumeratedTemplate) }, + { 0 } +}; + +/* + * OCSP-specific extension templates: + */ + +/* + * ServiceLocator ::= SEQUENCE { + * issuer Name, + * locator AuthorityInfoAccessSyntax OPTIONAL } + */ +static const SEC_ASN1Template ocsp_ServiceLocatorTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(ocspServiceLocator) }, + { SEC_ASN1_POINTER, + offsetof(ocspServiceLocator, issuer), + CERT_NameTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_ANY, + offsetof(ocspServiceLocator, locator) }, + { 0 } +}; + +/* + * REQUEST SUPPORT FUNCTIONS (encode/create/decode/destroy): + */ + +/* + * FUNCTION: CERT_EncodeOCSPRequest + * DER encodes an OCSP Request, possibly adding a signature as well. + * XXX Signing is not yet supported, however; see comments in code. + * INPUTS: + * PLArenaPool *arena + * The return value is allocated from here. + * If a NULL is passed in, allocation is done from the heap instead. + * CERTOCSPRequest *request + * The request to be encoded. + * void *pwArg + * Pointer to argument for password prompting, if needed. (Definitely + * not needed if not signing.) + * RETURN: + * Returns a NULL on error and a pointer to the SECItem with the + * encoded value otherwise. Any error is likely to be low-level + * (e.g. no memory). + */ +SECItem * +CERT_EncodeOCSPRequest(PLArenaPool *arena, CERTOCSPRequest *request, + void *pwArg) +{ + SECStatus rv; + + /* XXX All of these should generate errors if they fail. */ + PORT_Assert(request); + PORT_Assert(request->tbsRequest); + + if (request->tbsRequest->extensionHandle != NULL) { + rv = CERT_FinishExtensions(request->tbsRequest->extensionHandle); + request->tbsRequest->extensionHandle = NULL; + if (rv != SECSuccess) + return NULL; + } + + /* + * XXX When signed requests are supported and request->optionalSignature + * is not NULL: + * - need to encode tbsRequest->requestorName + * - need to encode tbsRequest + * - need to sign that encoded result (using cert in sig), filling in the + * request->optionalSignature structure with the result, the signing + * algorithm and (perhaps?) the cert (and its chain?) in derCerts + */ + + return SEC_ASN1EncodeItem(arena, NULL, request, ocsp_OCSPRequestTemplate); +} + +/* + * FUNCTION: CERT_DecodeOCSPRequest + * Decode a DER encoded OCSP Request. + * INPUTS: + * SECItem *src + * Pointer to a SECItem holding DER encoded OCSP Request. + * RETURN: + * Returns a pointer to a CERTOCSPRequest containing the decoded request. + * On error, returns NULL. Most likely error is trouble decoding + * (SEC_ERROR_OCSP_MALFORMED_REQUEST), or low-level problem (no memory). + */ +CERTOCSPRequest * +CERT_DecodeOCSPRequest(const SECItem *src) +{ + PLArenaPool *arena = NULL; + SECStatus rv = SECFailure; + CERTOCSPRequest *dest = NULL; + int i; + SECItem newSrc; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + goto loser; + } + dest = (CERTOCSPRequest *)PORT_ArenaZAlloc(arena, + sizeof(CERTOCSPRequest)); + if (dest == NULL) { + goto loser; + } + dest->arena = arena; + + /* copy the DER into the arena, since Quick DER returns data that points + into the DER input, which may get freed by the caller */ + rv = SECITEM_CopyItem(arena, &newSrc, src); + if (rv != SECSuccess) { + goto loser; + } + + rv = SEC_QuickDERDecodeItem(arena, dest, ocsp_OCSPRequestTemplate, &newSrc); + if (rv != SECSuccess) { + if (PORT_GetError() == SEC_ERROR_BAD_DER) + PORT_SetError(SEC_ERROR_OCSP_MALFORMED_REQUEST); + goto loser; + } + + /* + * XXX I would like to find a way to get rid of the necessity + * of doing this copying of the arena pointer. + */ + for (i = 0; dest->tbsRequest->requestList[i] != NULL; i++) { + dest->tbsRequest->requestList[i]->arena = arena; + } + + return dest; + +loser: + if (arena != NULL) { + PORT_FreeArena(arena, PR_FALSE); + } + return NULL; +} + +SECStatus +CERT_DestroyOCSPCertID(CERTOCSPCertID *certID) +{ + if (certID && certID->poolp) { + PORT_FreeArena(certID->poolp, PR_FALSE); + return SECSuccess; + } + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; +} + +/* + * Digest data using the specified algorithm. + * The necessary storage for the digest data is allocated. If "fill" is + * non-null, the data is put there, otherwise a SECItem is allocated. + * Allocation from "arena" if it is non-null, heap otherwise. Any problem + * results in a NULL being returned (and an appropriate error set). + */ + +SECItem * +ocsp_DigestValue(PLArenaPool *arena, SECOidTag digestAlg, + SECItem *fill, const SECItem *src) +{ + const SECHashObject *digestObject; + SECItem *result = NULL; + void *mark = NULL; + void *digestBuff = NULL; + + if (arena != NULL) { + mark = PORT_ArenaMark(arena); + } + + digestObject = HASH_GetHashObjectByOidTag(digestAlg); + if (digestObject == NULL) { + goto loser; + } + + if (fill == NULL || fill->data == NULL) { + result = SECITEM_AllocItem(arena, fill, digestObject->length); + if (result == NULL) { + goto loser; + } + digestBuff = result->data; + } else { + if (fill->len < digestObject->length) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + goto loser; + } + digestBuff = fill->data; + } + + if (PK11_HashBuf(digestAlg, digestBuff, + src->data, src->len) != SECSuccess) { + goto loser; + } + + if (arena != NULL) { + PORT_ArenaUnmark(arena, mark); + } + + if (result == NULL) { + result = fill; + } + return result; + +loser: + if (arena != NULL) { + PORT_ArenaRelease(arena, mark); + } else { + if (result != NULL) { + SECITEM_FreeItem(result, (fill == NULL) ? PR_TRUE : PR_FALSE); + } + } + return (NULL); +} + +/* + * Digest the cert's subject public key using the specified algorithm. + * The necessary storage for the digest data is allocated. If "fill" is + * non-null, the data is put there, otherwise a SECItem is allocated. + * Allocation from "arena" if it is non-null, heap otherwise. Any problem + * results in a NULL being returned (and an appropriate error set). + */ +SECItem * +CERT_GetSubjectPublicKeyDigest(PLArenaPool *arena, const CERTCertificate *cert, + SECOidTag digestAlg, SECItem *fill) +{ + SECItem spk; + + /* + * Copy just the length and data pointer (nothing needs to be freed) + * of the subject public key so we can convert the length from bits + * to bytes, which is what the digest function expects. + */ + spk = cert->subjectPublicKeyInfo.subjectPublicKey; + DER_ConvertBitString(&spk); + + return ocsp_DigestValue(arena, digestAlg, fill, &spk); +} + +/* + * Digest the cert's subject name using the specified algorithm. + */ +SECItem * +CERT_GetSubjectNameDigest(PLArenaPool *arena, const CERTCertificate *cert, + SECOidTag digestAlg, SECItem *fill) +{ + SECItem name; + + /* + * Copy just the length and data pointer (nothing needs to be freed) + * of the subject name + */ + name = cert->derSubject; + + return ocsp_DigestValue(arena, digestAlg, fill, &name); +} + +/* + * Create and fill-in a CertID. This function fills in the hash values + * (issuerNameHash and issuerKeyHash), and is hardwired to use SHA1. + * Someday it might need to be more flexible about hash algorithm, but + * for now we have no intention/need to create anything else. + * + * Error causes a null to be returned; most likely cause is trouble + * finding the certificate issuer (SEC_ERROR_UNKNOWN_ISSUER). + * Other errors are low-level problems (no memory, bad database, etc.). + */ +static CERTOCSPCertID * +ocsp_CreateCertID(PLArenaPool *arena, CERTCertificate *cert, PRTime time) +{ + CERTOCSPCertID *certID; + CERTCertificate *issuerCert = NULL; + void *mark = PORT_ArenaMark(arena); + SECStatus rv; + + PORT_Assert(arena != NULL); + + certID = PORT_ArenaZNew(arena, CERTOCSPCertID); + if (certID == NULL) { + goto loser; + } + + rv = SECOID_SetAlgorithmID(arena, &certID->hashAlgorithm, SEC_OID_SHA1, + NULL); + if (rv != SECSuccess) { + goto loser; + } + + issuerCert = CERT_FindCertIssuer(cert, time, certUsageAnyCA); + if (issuerCert == NULL) { + goto loser; + } + + if (CERT_GetSubjectNameDigest(arena, issuerCert, SEC_OID_SHA1, + &(certID->issuerNameHash)) == NULL) { + goto loser; + } + certID->issuerSHA1NameHash.data = certID->issuerNameHash.data; + certID->issuerSHA1NameHash.len = certID->issuerNameHash.len; + + if (CERT_GetSubjectNameDigest(arena, issuerCert, SEC_OID_MD5, + &(certID->issuerMD5NameHash)) == NULL) { + goto loser; + } + + if (CERT_GetSubjectNameDigest(arena, issuerCert, SEC_OID_MD2, + &(certID->issuerMD2NameHash)) == NULL) { + goto loser; + } + + if (CERT_GetSubjectPublicKeyDigest(arena, issuerCert, SEC_OID_SHA1, + &certID->issuerKeyHash) == NULL) { + goto loser; + } + certID->issuerSHA1KeyHash.data = certID->issuerKeyHash.data; + certID->issuerSHA1KeyHash.len = certID->issuerKeyHash.len; + /* cache the other two hash algorithms as well */ + if (CERT_GetSubjectPublicKeyDigest(arena, issuerCert, SEC_OID_MD5, + &certID->issuerMD5KeyHash) == NULL) { + goto loser; + } + if (CERT_GetSubjectPublicKeyDigest(arena, issuerCert, SEC_OID_MD2, + &certID->issuerMD2KeyHash) == NULL) { + goto loser; + } + + /* now we are done with issuerCert */ + CERT_DestroyCertificate(issuerCert); + issuerCert = NULL; + + rv = SECITEM_CopyItem(arena, &certID->serialNumber, &cert->serialNumber); + if (rv != SECSuccess) { + goto loser; + } + + PORT_ArenaUnmark(arena, mark); + return certID; + +loser: + if (issuerCert != NULL) { + CERT_DestroyCertificate(issuerCert); + } + PORT_ArenaRelease(arena, mark); + return NULL; +} + +CERTOCSPCertID * +CERT_CreateOCSPCertID(CERTCertificate *cert, PRTime time) +{ + PLArenaPool *arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + CERTOCSPCertID *certID; + PORT_Assert(arena != NULL); + if (!arena) + return NULL; + + certID = ocsp_CreateCertID(arena, cert, time); + if (!certID) { + PORT_FreeArena(arena, PR_FALSE); + return NULL; + } + certID->poolp = arena; + return certID; +} + +static CERTOCSPCertID * +cert_DupOCSPCertID(const CERTOCSPCertID *src) +{ + CERTOCSPCertID *dest; + PLArenaPool *arena = NULL; + + if (!src) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!arena) + goto loser; + + dest = PORT_ArenaZNew(arena, CERTOCSPCertID); + if (!dest) + goto loser; + +#define DUPHELP(element) \ + if (src->element.data && \ + SECITEM_CopyItem(arena, &dest->element, &src->element) != \ + SECSuccess) { \ + goto loser; \ + } + + DUPHELP(hashAlgorithm.algorithm) + DUPHELP(hashAlgorithm.parameters) + DUPHELP(issuerNameHash) + DUPHELP(issuerKeyHash) + DUPHELP(serialNumber) + DUPHELP(issuerSHA1NameHash) + DUPHELP(issuerMD5NameHash) + DUPHELP(issuerMD2NameHash) + DUPHELP(issuerSHA1KeyHash) + DUPHELP(issuerMD5KeyHash) + DUPHELP(issuerMD2KeyHash) + + dest->poolp = arena; + return dest; + +loser: + if (arena) + PORT_FreeArena(arena, PR_FALSE); + PORT_SetError(PR_OUT_OF_MEMORY_ERROR); + return NULL; +} + +/* + * Callback to set Extensions in request object + */ +void +SetSingleReqExts(void *object, CERTCertExtension **exts) +{ + ocspSingleRequest *singleRequest = + (ocspSingleRequest *)object; + + singleRequest->singleRequestExtensions = exts; +} + +/* + * Add the Service Locator extension to the singleRequestExtensions + * for the given singleRequest. + * + * All errors are internal or low-level problems (e.g. no memory). + */ +static SECStatus +ocsp_AddServiceLocatorExtension(ocspSingleRequest *singleRequest, + CERTCertificate *cert) +{ + ocspServiceLocator *serviceLocator = NULL; + void *extensionHandle = NULL; + SECStatus rv = SECFailure; + + serviceLocator = PORT_ZNew(ocspServiceLocator); + if (serviceLocator == NULL) + goto loser; + + /* + * Normally it would be a bad idea to do a direct reference like + * this rather than allocate and copy the name *or* at least dup + * a reference of the cert. But all we need is to be able to read + * the issuer name during the encoding we are about to do, so a + * copy is just a waste of time. + */ + serviceLocator->issuer = &cert->issuer; + + rv = CERT_FindCertExtension(cert, SEC_OID_X509_AUTH_INFO_ACCESS, + &serviceLocator->locator); + if (rv != SECSuccess) { + if (PORT_GetError() != SEC_ERROR_EXTENSION_NOT_FOUND) + goto loser; + } + + /* prepare for following loser gotos */ + rv = SECFailure; + PORT_SetError(0); + + extensionHandle = cert_StartExtensions(singleRequest, + singleRequest->arena, SetSingleReqExts); + if (extensionHandle == NULL) + goto loser; + + rv = CERT_EncodeAndAddExtension(extensionHandle, + SEC_OID_PKIX_OCSP_SERVICE_LOCATOR, + serviceLocator, PR_FALSE, + ocsp_ServiceLocatorTemplate); + +loser: + if (extensionHandle != NULL) { + /* + * Either way we have to finish out the extension context (so it gets + * freed). But careful not to override any already-set bad status. + */ + SECStatus tmprv = CERT_FinishExtensions(extensionHandle); + if (rv == SECSuccess) + rv = tmprv; + } + + /* + * Finally, free the serviceLocator structure itself and we are done. + */ + if (serviceLocator != NULL) { + if (serviceLocator->locator.data != NULL) + SECITEM_FreeItem(&serviceLocator->locator, PR_FALSE); + PORT_Free(serviceLocator); + } + + return rv; +} + +/* + * Creates an array of ocspSingleRequest based on a list of certs. + * Note that the code which later compares the request list with the + * response expects this array to be in the exact same order as the + * certs are found in the list. It would be harder to change that + * order than preserve it, but since the requirement is not obvious, + * it deserves to be mentioned. + * + * Any problem causes a null return and error set: + * SEC_ERROR_UNKNOWN_ISSUER + * Other errors are low-level problems (no memory, bad database, etc.). + */ +static ocspSingleRequest ** +ocsp_CreateSingleRequestList(PLArenaPool *arena, CERTCertList *certList, + PRTime time, PRBool includeLocator) +{ + ocspSingleRequest **requestList = NULL; + CERTCertListNode *node = NULL; + int i, count; + void *mark = PORT_ArenaMark(arena); + + node = CERT_LIST_HEAD(certList); + for (count = 0; !CERT_LIST_END(node, certList); count++) { + node = CERT_LIST_NEXT(node); + } + + if (count == 0) + goto loser; + + requestList = PORT_ArenaNewArray(arena, ocspSingleRequest *, count + 1); + if (requestList == NULL) + goto loser; + + node = CERT_LIST_HEAD(certList); + for (i = 0; !CERT_LIST_END(node, certList); i++) { + requestList[i] = PORT_ArenaZNew(arena, ocspSingleRequest); + if (requestList[i] == NULL) + goto loser; + + OCSP_TRACE(("OCSP CERT_CreateOCSPRequest %s\n", node->cert->subjectName)); + requestList[i]->arena = arena; + requestList[i]->reqCert = ocsp_CreateCertID(arena, node->cert, time); + if (requestList[i]->reqCert == NULL) + goto loser; + + if (includeLocator == PR_TRUE) { + SECStatus rv; + + rv = ocsp_AddServiceLocatorExtension(requestList[i], node->cert); + if (rv != SECSuccess) + goto loser; + } + + node = CERT_LIST_NEXT(node); + } + + PORT_Assert(i == count); + + PORT_ArenaUnmark(arena, mark); + requestList[i] = NULL; + return requestList; + +loser: + PORT_ArenaRelease(arena, mark); + return NULL; +} + +static ocspSingleRequest ** +ocsp_CreateRequestFromCert(PLArenaPool *arena, + CERTOCSPCertID *certID, + CERTCertificate *singleCert, + PRTime time, + PRBool includeLocator) +{ + ocspSingleRequest **requestList = NULL; + void *mark = PORT_ArenaMark(arena); + PORT_Assert(certID != NULL && singleCert != NULL); + + /* meaning of value 2: one entry + one end marker */ + requestList = PORT_ArenaNewArray(arena, ocspSingleRequest *, 2); + if (requestList == NULL) + goto loser; + requestList[0] = PORT_ArenaZNew(arena, ocspSingleRequest); + if (requestList[0] == NULL) + goto loser; + requestList[0]->arena = arena; + /* certID will live longer than the request */ + requestList[0]->reqCert = certID; + + if (includeLocator == PR_TRUE) { + SECStatus rv; + rv = ocsp_AddServiceLocatorExtension(requestList[0], singleCert); + if (rv != SECSuccess) + goto loser; + } + + PORT_ArenaUnmark(arena, mark); + requestList[1] = NULL; + return requestList; + +loser: + PORT_ArenaRelease(arena, mark); + return NULL; +} + +static CERTOCSPRequest * +ocsp_prepareEmptyOCSPRequest(void) +{ + PLArenaPool *arena = NULL; + CERTOCSPRequest *request = NULL; + ocspTBSRequest *tbsRequest = NULL; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + goto loser; + } + request = PORT_ArenaZNew(arena, CERTOCSPRequest); + if (request == NULL) { + goto loser; + } + request->arena = arena; + + tbsRequest = PORT_ArenaZNew(arena, ocspTBSRequest); + if (tbsRequest == NULL) { + goto loser; + } + request->tbsRequest = tbsRequest; + /* version 1 is the default, so we need not fill in a version number */ + return request; + +loser: + if (arena != NULL) { + PORT_FreeArena(arena, PR_FALSE); + } + return NULL; +} + +CERTOCSPRequest * +cert_CreateSingleCertOCSPRequest(CERTOCSPCertID *certID, + CERTCertificate *singleCert, + PRTime time, + PRBool addServiceLocator, + CERTCertificate *signerCert) +{ + CERTOCSPRequest *request; + OCSP_TRACE(("OCSP cert_CreateSingleCertOCSPRequest %s\n", singleCert->subjectName)); + + /* XXX Support for signerCert may be implemented later, + * see also the comment in CERT_CreateOCSPRequest. + */ + if (signerCert != NULL) { + PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); + return NULL; + } + + request = ocsp_prepareEmptyOCSPRequest(); + if (!request) + return NULL; + /* + * Version 1 is the default, so we need not fill in a version number. + * Now create the list of single requests, one for each cert. + */ + request->tbsRequest->requestList = + ocsp_CreateRequestFromCert(request->arena, + certID, + singleCert, + time, + addServiceLocator); + if (request->tbsRequest->requestList == NULL) { + PORT_FreeArena(request->arena, PR_FALSE); + return NULL; + } + return request; +} + +/* + * FUNCTION: CERT_CreateOCSPRequest + * Creates a CERTOCSPRequest, requesting the status of the certs in + * the given list. + * INPUTS: + * CERTCertList *certList + * A list of certs for which status will be requested. + * Note that all of these certificates should have the same issuer, + * or it's expected the response will be signed by a trusted responder. + * If the certs need to be broken up into multiple requests, that + * must be handled by the caller (and thus by having multiple calls + * to this routine), who knows about where the request(s) are being + * sent and whether there are any trusted responders in place. + * PRTime time + * Indicates the time for which the certificate status is to be + * determined -- this may be used in the search for the cert's issuer + * but has no effect on the request itself. + * PRBool addServiceLocator + * If true, the Service Locator extension should be added to the + * single request(s) for each cert. + * CERTCertificate *signerCert + * If non-NULL, means sign the request using this cert. Otherwise, + * do not sign. + * XXX note that request signing is not yet supported; see comment in code + * RETURN: + * A pointer to a CERTOCSPRequest structure containing an OCSP request + * for the cert list. On error, null is returned, with an error set + * indicating the reason. This is likely SEC_ERROR_UNKNOWN_ISSUER. + * (The issuer is needed to create a request for the certificate.) + * Other errors are low-level problems (no memory, bad database, etc.). + */ +CERTOCSPRequest * +CERT_CreateOCSPRequest(CERTCertList *certList, PRTime time, + PRBool addServiceLocator, + CERTCertificate *signerCert) +{ + CERTOCSPRequest *request = NULL; + + if (!certList) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + /* + * XXX When we are prepared to put signing of requests back in, + * we will need to allocate a signature + * structure for the request, fill in the "derCerts" field in it, + * save the signerCert there, as well as fill in the "requestorName" + * field of the tbsRequest. + */ + if (signerCert != NULL) { + PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); + return NULL; + } + request = ocsp_prepareEmptyOCSPRequest(); + if (!request) + return NULL; + /* + * Now create the list of single requests, one for each cert. + */ + request->tbsRequest->requestList = + ocsp_CreateSingleRequestList(request->arena, + certList, + time, + addServiceLocator); + if (request->tbsRequest->requestList == NULL) { + PORT_FreeArena(request->arena, PR_FALSE); + return NULL; + } + return request; +} + +/* + * FUNCTION: CERT_AddOCSPAcceptableResponses + * Add the AcceptableResponses extension to an OCSP Request. + * INPUTS: + * CERTOCSPRequest *request + * The request to which the extension should be added. + * ... + * A list (of one or more) of SECOidTag -- each of the response types + * to be added. The last OID *must* be SEC_OID_PKIX_OCSP_BASIC_RESPONSE. + * (This marks the end of the list, and it must be specified because a + * client conforming to the OCSP standard is required to handle the basic + * response type.) The OIDs are not checked in any way. + * RETURN: + * SECSuccess if the extension is added; SECFailure if anything goes wrong. + * All errors are internal or low-level problems (e.g. no memory). + */ + +void +SetRequestExts(void *object, CERTCertExtension **exts) +{ + CERTOCSPRequest *request = (CERTOCSPRequest *)object; + + request->tbsRequest->requestExtensions = exts; +} + +#if defined(__GNUC__) && !defined(NSS_NO_GCC48) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wvarargs" +#endif +SECStatus +CERT_AddOCSPAcceptableResponses(CERTOCSPRequest *request, + SECOidTag responseType0, ...) +{ + void *extHandle; + va_list ap; + int i, count; + SECOidTag responseType; + SECOidData *responseOid; + SECItem **acceptableResponses = NULL; + SECStatus rv = SECFailure; + + extHandle = request->tbsRequest->extensionHandle; + if (extHandle == NULL) { + extHandle = cert_StartExtensions(request, request->arena, SetRequestExts); + if (extHandle == NULL) + goto loser; + } + + /* Count number of OIDS going into the extension value. */ + count = 1; + if (responseType0 != SEC_OID_PKIX_OCSP_BASIC_RESPONSE) { + va_start(ap, responseType0); + do { + count++; + responseType = va_arg(ap, SECOidTag); + } while (responseType != SEC_OID_PKIX_OCSP_BASIC_RESPONSE); + va_end(ap); + } + + acceptableResponses = PORT_NewArray(SECItem *, count + 1); + if (acceptableResponses == NULL) + goto loser; + + i = 0; + responseOid = SECOID_FindOIDByTag(responseType0); + acceptableResponses[i++] = &(responseOid->oid); + if (count > 1) { + va_start(ap, responseType0); + for (; i < count; i++) { + responseType = va_arg(ap, SECOidTag); + responseOid = SECOID_FindOIDByTag(responseType); + acceptableResponses[i] = &(responseOid->oid); + } + va_end(ap); + } + acceptableResponses[i] = NULL; + + rv = CERT_EncodeAndAddExtension(extHandle, SEC_OID_PKIX_OCSP_RESPONSE, + &acceptableResponses, PR_FALSE, + SEC_ASN1_GET(SEC_SequenceOfObjectIDTemplate)); + if (rv != SECSuccess) + goto loser; + + PORT_Free(acceptableResponses); + if (request->tbsRequest->extensionHandle == NULL) + request->tbsRequest->extensionHandle = extHandle; + return SECSuccess; + +loser: + if (acceptableResponses != NULL) + PORT_Free(acceptableResponses); + if (extHandle != NULL) + (void)CERT_FinishExtensions(extHandle); + return rv; +} +#if defined(__GNUC__) && !defined(NSS_NO_GCC48) +#pragma GCC diagnostic pop +#endif + +/* + * FUNCTION: CERT_DestroyOCSPRequest + * Frees an OCSP Request structure. + * INPUTS: + * CERTOCSPRequest *request + * Pointer to CERTOCSPRequest to be freed. + * RETURN: + * No return value; no errors. + */ +void +CERT_DestroyOCSPRequest(CERTOCSPRequest *request) +{ + if (request == NULL) + return; + + if (request->tbsRequest != NULL) { + if (request->tbsRequest->requestorName != NULL) + CERT_DestroyGeneralNameList(request->tbsRequest->requestorName); + if (request->tbsRequest->extensionHandle != NULL) + (void)CERT_FinishExtensions(request->tbsRequest->extensionHandle); + } + + if (request->optionalSignature != NULL) { + if (request->optionalSignature->cert != NULL) + CERT_DestroyCertificate(request->optionalSignature->cert); + + /* + * XXX Need to free derCerts? Or do they come out of arena? + * (Currently we never fill in derCerts, which is why the + * answer is not obvious. Once we do, add any necessary code + * here and remove this comment.) + */ + } + + /* + * We should actually never have a request without an arena, + * but check just in case. (If there isn't one, there is not + * much we can do about it...) + */ + PORT_Assert(request->arena != NULL); + if (request->arena != NULL) + PORT_FreeArena(request->arena, PR_FALSE); +} + +/* + * RESPONSE SUPPORT FUNCTIONS (encode/create/decode/destroy): + */ + +/* + * Helper function for encoding or decoding a ResponderID -- based on the + * given type, return the associated template for that choice. + */ +static const SEC_ASN1Template * +ocsp_ResponderIDTemplateByType(CERTOCSPResponderIDType responderIDType) +{ + const SEC_ASN1Template *responderIDTemplate; + + switch (responderIDType) { + case ocspResponderID_byName: + responderIDTemplate = ocsp_ResponderIDByNameTemplate; + break; + case ocspResponderID_byKey: + responderIDTemplate = ocsp_ResponderIDByKeyTemplate; + break; + case ocspResponderID_other: + default: + PORT_Assert(responderIDType == ocspResponderID_other); + responderIDTemplate = ocsp_ResponderIDOtherTemplate; + break; + } + + return responderIDTemplate; +} + +/* + * Helper function for encoding or decoding a CertStatus -- based on the + * given type, return the associated template for that choice. + */ +static const SEC_ASN1Template * +ocsp_CertStatusTemplateByType(ocspCertStatusType certStatusType) +{ + const SEC_ASN1Template *certStatusTemplate; + + switch (certStatusType) { + case ocspCertStatus_good: + certStatusTemplate = ocsp_CertStatusGoodTemplate; + break; + case ocspCertStatus_revoked: + certStatusTemplate = ocsp_CertStatusRevokedTemplate; + break; + case ocspCertStatus_unknown: + certStatusTemplate = ocsp_CertStatusUnknownTemplate; + break; + case ocspCertStatus_other: + default: + PORT_Assert(certStatusType == ocspCertStatus_other); + certStatusTemplate = ocsp_CertStatusOtherTemplate; + break; + } + + return certStatusTemplate; +} + +/* + * Helper function for decoding a certStatus -- turn the actual DER tag + * into our local translation. + */ +static ocspCertStatusType +ocsp_CertStatusTypeByTag(int derTag) +{ + ocspCertStatusType certStatusType; + + switch (derTag) { + case 0: + certStatusType = ocspCertStatus_good; + break; + case 1: + certStatusType = ocspCertStatus_revoked; + break; + case 2: + certStatusType = ocspCertStatus_unknown; + break; + default: + certStatusType = ocspCertStatus_other; + break; + } + + return certStatusType; +} + +/* + * Helper function for decoding SingleResponses -- they each contain + * a status which is encoded as CHOICE, which needs to be decoded "by hand". + * + * Note -- on error, this routine does not release the memory it may + * have allocated; it expects its caller to do that. + */ +static SECStatus +ocsp_FinishDecodingSingleResponses(PLArenaPool *reqArena, + CERTOCSPSingleResponse **responses) +{ + ocspCertStatus *certStatus; + ocspCertStatusType certStatusType; + const SEC_ASN1Template *certStatusTemplate; + int derTag; + int i; + SECStatus rv = SECFailure; + + if (!reqArena) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (responses == NULL) /* nothing to do */ + return SECSuccess; + + for (i = 0; responses[i] != NULL; i++) { + SECItem *newStatus; + /* + * The following assert points out internal errors (problems in + * the template definitions or in the ASN.1 decoder itself, etc.). + */ + PORT_Assert(responses[i]->derCertStatus.data != NULL); + + derTag = responses[i]->derCertStatus.data[0] & SEC_ASN1_TAGNUM_MASK; + certStatusType = ocsp_CertStatusTypeByTag(derTag); + certStatusTemplate = ocsp_CertStatusTemplateByType(certStatusType); + + certStatus = PORT_ArenaZAlloc(reqArena, sizeof(ocspCertStatus)); + if (certStatus == NULL) { + goto loser; + } + newStatus = SECITEM_ArenaDupItem(reqArena, &responses[i]->derCertStatus); + if (!newStatus) { + goto loser; + } + rv = SEC_QuickDERDecodeItem(reqArena, certStatus, certStatusTemplate, + newStatus); + if (rv != SECSuccess) { + if (PORT_GetError() == SEC_ERROR_BAD_DER) + PORT_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE); + goto loser; + } + + certStatus->certStatusType = certStatusType; + responses[i]->certStatus = certStatus; + } + + return SECSuccess; + +loser: + return rv; +} + +/* + * Helper function for decoding a responderID -- turn the actual DER tag + * into our local translation. + */ +static CERTOCSPResponderIDType +ocsp_ResponderIDTypeByTag(int derTag) +{ + CERTOCSPResponderIDType responderIDType; + + switch (derTag) { + case 1: + responderIDType = ocspResponderID_byName; + break; + case 2: + responderIDType = ocspResponderID_byKey; + break; + default: + responderIDType = ocspResponderID_other; + break; + } + + return responderIDType; +} + +/* + * Decode "src" as a BasicOCSPResponse, returning the result. + */ +static ocspBasicOCSPResponse * +ocsp_DecodeBasicOCSPResponse(PLArenaPool *arena, SECItem *src) +{ + void *mark; + ocspBasicOCSPResponse *basicResponse; + ocspResponseData *responseData; + ocspResponderID *responderID; + CERTOCSPResponderIDType responderIDType; + const SEC_ASN1Template *responderIDTemplate; + int derTag; + SECStatus rv; + SECItem newsrc; + + mark = PORT_ArenaMark(arena); + + basicResponse = PORT_ArenaZAlloc(arena, sizeof(ocspBasicOCSPResponse)); + if (basicResponse == NULL) { + goto loser; + } + + /* copy the DER into the arena, since Quick DER returns data that points + into the DER input, which may get freed by the caller */ + rv = SECITEM_CopyItem(arena, &newsrc, src); + if (rv != SECSuccess) { + goto loser; + } + + rv = SEC_QuickDERDecodeItem(arena, basicResponse, + ocsp_BasicOCSPResponseTemplate, &newsrc); + if (rv != SECSuccess) { + if (PORT_GetError() == SEC_ERROR_BAD_DER) + PORT_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE); + goto loser; + } + + responseData = basicResponse->tbsResponseData; + + /* + * The following asserts point out internal errors (problems in + * the template definitions or in the ASN.1 decoder itself, etc.). + */ + PORT_Assert(responseData != NULL); + PORT_Assert(responseData->derResponderID.data != NULL); + + /* + * XXX Because responderID is a CHOICE, which is not currently handled + * by our ASN.1 decoder, we have to decode it "by hand". + */ + derTag = responseData->derResponderID.data[0] & SEC_ASN1_TAGNUM_MASK; + responderIDType = ocsp_ResponderIDTypeByTag(derTag); + responderIDTemplate = ocsp_ResponderIDTemplateByType(responderIDType); + + responderID = PORT_ArenaZAlloc(arena, sizeof(ocspResponderID)); + if (responderID == NULL) { + goto loser; + } + + rv = SEC_QuickDERDecodeItem(arena, responderID, responderIDTemplate, + &responseData->derResponderID); + if (rv != SECSuccess) { + if (PORT_GetError() == SEC_ERROR_BAD_DER) + PORT_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE); + goto loser; + } + + responderID->responderIDType = responderIDType; + responseData->responderID = responderID; + + /* + * XXX Each SingleResponse also contains a CHOICE, which has to be + * fixed up by hand. + */ + rv = ocsp_FinishDecodingSingleResponses(arena, responseData->responses); + if (rv != SECSuccess) { + goto loser; + } + + PORT_ArenaUnmark(arena, mark); + return basicResponse; + +loser: + PORT_ArenaRelease(arena, mark); + return NULL; +} + +/* + * Decode the responseBytes based on the responseType found in "rbytes", + * leaving the resulting translated/decoded information in there as well. + */ +static SECStatus +ocsp_DecodeResponseBytes(PLArenaPool *arena, ocspResponseBytes *rbytes) +{ + if (rbytes == NULL) { + PORT_SetError(SEC_ERROR_OCSP_UNKNOWN_RESPONSE_TYPE); + return SECFailure; + } + + rbytes->responseTypeTag = SECOID_FindOIDTag(&rbytes->responseType); + switch (rbytes->responseTypeTag) { + case SEC_OID_PKIX_OCSP_BASIC_RESPONSE: { + ocspBasicOCSPResponse *basicResponse; + + basicResponse = ocsp_DecodeBasicOCSPResponse(arena, + &rbytes->response); + if (basicResponse == NULL) + return SECFailure; + + rbytes->decodedResponse.basic = basicResponse; + } break; + + /* + * Add new/future response types here. + */ + + default: + PORT_SetError(SEC_ERROR_OCSP_UNKNOWN_RESPONSE_TYPE); + return SECFailure; + } + + return SECSuccess; +} + +/* + * FUNCTION: CERT_DecodeOCSPResponse + * Decode a DER encoded OCSP Response. + * INPUTS: + * SECItem *src + * Pointer to a SECItem holding DER encoded OCSP Response. + * RETURN: + * Returns a pointer to a CERTOCSPResponse (the decoded OCSP Response); + * the caller is responsible for destroying it. Or NULL if error (either + * response could not be decoded (SEC_ERROR_OCSP_MALFORMED_RESPONSE), + * it was of an unexpected type (SEC_ERROR_OCSP_UNKNOWN_RESPONSE_TYPE), + * or a low-level or internal error occurred). + */ +CERTOCSPResponse * +CERT_DecodeOCSPResponse(const SECItem *src) +{ + PLArenaPool *arena = NULL; + CERTOCSPResponse *response = NULL; + SECStatus rv = SECFailure; + ocspResponseStatus sv; + SECItem newSrc; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + goto loser; + } + response = (CERTOCSPResponse *)PORT_ArenaZAlloc(arena, + sizeof(CERTOCSPResponse)); + if (response == NULL) { + goto loser; + } + response->arena = arena; + + /* copy the DER into the arena, since Quick DER returns data that points + into the DER input, which may get freed by the caller */ + rv = SECITEM_CopyItem(arena, &newSrc, src); + if (rv != SECSuccess) { + goto loser; + } + + rv = SEC_QuickDERDecodeItem(arena, response, ocsp_OCSPResponseTemplate, &newSrc); + if (rv != SECSuccess) { + if (PORT_GetError() == SEC_ERROR_BAD_DER) + PORT_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE); + goto loser; + } + + sv = (ocspResponseStatus)DER_GetInteger(&response->responseStatus); + response->statusValue = sv; + if (sv != ocspResponse_successful) { + /* + * If the response status is anything but successful, then we + * are all done with decoding; the status is all there is. + */ + return response; + } + + /* + * A successful response contains much more information, still encoded. + * Now we need to decode that. + */ + rv = ocsp_DecodeResponseBytes(arena, response->responseBytes); + if (rv != SECSuccess) { + goto loser; + } + + return response; + +loser: + if (arena != NULL) { + PORT_FreeArena(arena, PR_FALSE); + } + return NULL; +} + +/* + * The way an OCSPResponse is defined, there are many levels to descend + * before getting to the actual response information. And along the way + * we need to check that the response *type* is recognizable, which for + * now means that it is a BasicOCSPResponse, because that is the only + * type currently defined. Rather than force all routines to perform + * a bunch of sanity checking every time they want to work on a response, + * this function isolates that and gives back the interesting part. + * Note that no copying is done, this just returns a pointer into the + * substructure of the response which is passed in. + * + * XXX This routine only works when a valid response structure is passed + * into it; this is checked with many assertions. Assuming the response + * was creating by decoding, it wouldn't make it this far without being + * okay. That is a sufficient assumption since the entire OCSP interface + * is only used internally. When this interface is officially exported, + * each assertion below will need to be followed-up with setting an error + * and returning (null). + * + * FUNCTION: ocsp_GetResponseData + * Returns ocspResponseData structure and a pointer to tbs response + * data DER from a valid ocsp response. + * INPUTS: + * CERTOCSPResponse *response + * structure of a valid ocsp response + * RETURN: + * Returns a pointer to ocspResponseData structure: decoded OCSP response + * data, and a pointer(tbsResponseDataDER) to its undecoded data DER. + */ +ocspResponseData * +ocsp_GetResponseData(CERTOCSPResponse *response, SECItem **tbsResponseDataDER) +{ + ocspBasicOCSPResponse *basic; + ocspResponseData *responseData; + + PORT_Assert(response != NULL); + + PORT_Assert(response->responseBytes != NULL); + + PORT_Assert(response->responseBytes->responseTypeTag == + SEC_OID_PKIX_OCSP_BASIC_RESPONSE); + + basic = response->responseBytes->decodedResponse.basic; + PORT_Assert(basic != NULL); + + responseData = basic->tbsResponseData; + PORT_Assert(responseData != NULL); + + if (tbsResponseDataDER) { + *tbsResponseDataDER = &basic->tbsResponseDataDER; + + PORT_Assert((*tbsResponseDataDER)->data != NULL); + PORT_Assert((*tbsResponseDataDER)->len != 0); + } + + return responseData; +} + +/* + * Much like the routine above, except it returns the response signature. + * Again, no copy is done. + */ +ocspSignature * +ocsp_GetResponseSignature(CERTOCSPResponse *response) +{ + ocspBasicOCSPResponse *basic; + + PORT_Assert(response != NULL); + if (NULL == response->responseBytes) { + return NULL; + } + if (response->responseBytes->responseTypeTag != + SEC_OID_PKIX_OCSP_BASIC_RESPONSE) { + return NULL; + } + basic = response->responseBytes->decodedResponse.basic; + PORT_Assert(basic != NULL); + + return &(basic->responseSignature); +} + +/* + * FUNCTION: CERT_DestroyOCSPResponse + * Frees an OCSP Response structure. + * INPUTS: + * CERTOCSPResponse *request + * Pointer to CERTOCSPResponse to be freed. + * RETURN: + * No return value; no errors. + */ +void +CERT_DestroyOCSPResponse(CERTOCSPResponse *response) +{ + if (response != NULL) { + ocspSignature *signature = ocsp_GetResponseSignature(response); + if (signature && signature->cert != NULL) + CERT_DestroyCertificate(signature->cert); + + /* + * We should actually never have a response without an arena, + * but check just in case. (If there isn't one, there is not + * much we can do about it...) + */ + PORT_Assert(response->arena != NULL); + if (response->arena != NULL) { + PORT_FreeArena(response->arena, PR_FALSE); + } + } +} + +/* + * OVERALL OCSP CLIENT SUPPORT (make and send a request, verify a response): + */ + +/* + * Pick apart a URL, saving the important things in the passed-in pointers. + * + * We expect to find "http://<hostname>[:<port>]/[path]", though we will + * tolerate that final slash character missing, as well as beginning and + * trailing whitespace, and any-case-characters for "http". All of that + * tolerance is what complicates this routine. What we want is just to + * pick out the hostname, the port, and the path. + * + * On a successful return, the caller will need to free the output pieces + * of hostname and path, which are copies of the values found in the url. + */ +static SECStatus +ocsp_ParseURL(const char *url, char **pHostname, PRUint16 *pPort, char **pPath) +{ + unsigned short port = 80; /* default, in case not in url */ + char *hostname = NULL; + char *path = NULL; + const char *save; + char c; + int len; + + if (url == NULL) + goto loser; + + /* + * Skip beginning whitespace. + */ + c = *url; + while ((c == ' ' || c == '\t') && c != '\0') { + url++; + c = *url; + } + if (c == '\0') + goto loser; + + /* + * Confirm, then skip, protocol. (Since we only know how to do http, + * that is all we will accept). + */ + if (PORT_Strncasecmp(url, "http://", 7) != 0) + goto loser; + url += 7; + + /* + * Whatever comes next is the hostname (or host IP address). We just + * save it aside and then search for its end so we can determine its + * length and copy it. + * + * XXX Note that because we treat a ':' as a terminator character + * (and below, we expect that to mean there is a port specification + * immediately following), we will not handle IPv6 addresses. That is + * apparently an acceptable limitation, for the time being. Some day, + * when there is a clear way to specify a URL with an IPv6 address that + * can be parsed unambiguously, this code should be made to do that. + */ + save = url; + c = *url; + while (c != '/' && c != ':' && c != '\0' && c != ' ' && c != '\t') { + url++; + c = *url; + } + len = url - save; + hostname = PORT_Alloc(len + 1); + if (hostname == NULL) + goto loser; + PORT_Memcpy(hostname, save, len); + hostname[len] = '\0'; + + /* + * Now we figure out if there was a port specified or not. + * If so, we need to parse it (as a number) and skip it. + */ + if (c == ':') { + url++; + port = (unsigned short)PORT_Atoi(url); + c = *url; + while (c != '/' && c != '\0' && c != ' ' && c != '\t') { + if (c < '0' || c > '9') + goto loser; + url++; + c = *url; + } + } + + /* + * Last thing to find is a path. There *should* be a slash, + * if nothing else -- but if there is not we provide one. + */ + if (c == '/') { + save = url; + while (c != '\0' && c != ' ' && c != '\t') { + url++; + c = *url; + } + len = url - save; + path = PORT_Alloc(len + 1); + if (path == NULL) + goto loser; + PORT_Memcpy(path, save, len); + path[len] = '\0'; + } else { + path = PORT_Strdup("/"); + if (path == NULL) + goto loser; + } + + *pHostname = hostname; + *pPort = port; + *pPath = path; + return SECSuccess; + +loser: + if (hostname != NULL) + PORT_Free(hostname); + PORT_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION); + return SECFailure; +} + +/* + * Open a socket to the specified host on the specified port, and return it. + * The host is either a hostname or an IP address. + */ +static PRFileDesc * +ocsp_ConnectToHost(const char *host, PRUint16 port) +{ + PRFileDesc *sock = NULL; + PRIntervalTime timeout; + PRNetAddr addr; + char *netdbbuf = NULL; + + sock = PR_NewTCPSocket(); + if (sock == NULL) + goto loser; + + /* XXX Some day need a way to set (and get?) the following value */ + timeout = PR_SecondsToInterval(30); + + /* + * If the following converts an IP address string in "dot notation" + * into a PRNetAddr. If it fails, we assume that is because we do not + * have such an address, but instead a host *name*. In that case we + * then lookup the host by name. Using the NSPR function this way + * means we do not have to have our own logic for distinguishing a + * valid numerical IP address from a hostname. + */ + if (PR_StringToNetAddr(host, &addr) != PR_SUCCESS) { + PRIntn hostIndex; + PRHostEnt hostEntry; + + netdbbuf = PORT_Alloc(PR_NETDB_BUF_SIZE); + if (netdbbuf == NULL) + goto loser; + + if (PR_GetHostByName(host, netdbbuf, PR_NETDB_BUF_SIZE, + &hostEntry) != PR_SUCCESS) + goto loser; + + hostIndex = 0; + do { + hostIndex = PR_EnumerateHostEnt(hostIndex, &hostEntry, port, &addr); + if (hostIndex <= 0) + goto loser; + } while (PR_Connect(sock, &addr, timeout) != PR_SUCCESS); + + PORT_Free(netdbbuf); + } else { + /* + * First put the port into the address, then connect. + */ + if (PR_InitializeNetAddr(PR_IpAddrNull, port, &addr) != PR_SUCCESS) + goto loser; + if (PR_Connect(sock, &addr, timeout) != PR_SUCCESS) + goto loser; + } + + return sock; + +loser: + if (sock != NULL) + PR_Close(sock); + if (netdbbuf != NULL) + PORT_Free(netdbbuf); + return NULL; +} + +/* + * Sends an encoded OCSP request to the server identified by "location", + * and returns the socket on which it was sent (so can listen for the reply). + * "location" is expected to be a valid URL -- an error parsing it produces + * SEC_ERROR_CERT_BAD_ACCESS_LOCATION. Other errors are likely problems + * connecting to it, or writing to it, or allocating memory, and the low-level + * errors appropriate to the problem will be set. + * if (encodedRequest == NULL) + * then location MUST already include the full request, + * including base64 and urlencode, + * and the request will be sent with GET + * if (encodedRequest != NULL) + * then the request will be sent with POST + */ +static PRFileDesc * +ocsp_SendEncodedRequest(const char *location, const SECItem *encodedRequest) +{ + char *hostname = NULL; + char *path = NULL; + PRUint16 port; + SECStatus rv; + PRFileDesc *sock = NULL; + PRFileDesc *returnSock = NULL; + char *header = NULL; + char portstr[16]; + + /* + * Take apart the location, getting the hostname, port, and path. + */ + rv = ocsp_ParseURL(location, &hostname, &port, &path); + if (rv != SECSuccess) + goto loser; + + PORT_Assert(hostname != NULL); + PORT_Assert(path != NULL); + + sock = ocsp_ConnectToHost(hostname, port); + if (sock == NULL) + goto loser; + + portstr[0] = '\0'; + if (port != 80) { + PR_snprintf(portstr, sizeof(portstr), ":%d", port); + } + + if (!encodedRequest) { + header = PR_smprintf("GET %s HTTP/1.0\r\n" + "Host: %s%s\r\n\r\n", + path, hostname, portstr); + if (header == NULL) + goto loser; + + /* + * The NSPR documentation promises that if it can, it will write the full + * amount; this will not return a partial value expecting us to loop. + */ + if (PR_Write(sock, header, (PRInt32)PORT_Strlen(header)) < 0) + goto loser; + } else { + header = PR_smprintf("POST %s HTTP/1.0\r\n" + "Host: %s%s\r\n" + "Content-Type: application/ocsp-request\r\n" + "Content-Length: %u\r\n\r\n", + path, hostname, portstr, encodedRequest->len); + if (header == NULL) + goto loser; + + /* + * The NSPR documentation promises that if it can, it will write the full + * amount; this will not return a partial value expecting us to loop. + */ + if (PR_Write(sock, header, (PRInt32)PORT_Strlen(header)) < 0) + goto loser; + + if (PR_Write(sock, encodedRequest->data, + (PRInt32)encodedRequest->len) < 0) + goto loser; + } + + returnSock = sock; + sock = NULL; + +loser: + if (header != NULL) + PORT_Free(header); + if (sock != NULL) + PR_Close(sock); + if (path != NULL) + PORT_Free(path); + if (hostname != NULL) + PORT_Free(hostname); + + return returnSock; +} + +/* + * Read from "fd" into "buf" -- expect/attempt to read a given number of bytes + * Obviously, stop if hit end-of-stream. Timeout is passed in. + */ + +static int +ocsp_read(PRFileDesc *fd, char *buf, int toread, PRIntervalTime timeout) +{ + int total = 0; + + while (total < toread) { + PRInt32 got; + + got = PR_Recv(fd, buf + total, (PRInt32)(toread - total), 0, timeout); + if (got < 0) { + if (0 == total) { + total = -1; /* report the error if we didn't read anything yet */ + } + break; + } else if (got == 0) { /* EOS */ + break; + } + + total += got; + } + + return total; +} + +#define OCSP_BUFSIZE 1024 + +#define AbortHttpDecode(error) \ + { \ + if (inBuffer) \ + PORT_Free(inBuffer); \ + PORT_SetError(error); \ + return NULL; \ + } + +/* + * Reads on the given socket and returns an encoded response when received. + * Properly formatted HTTP/1.0 response headers are expected to be read + * from the socket, preceding a binary-encoded OCSP response. Problems + * with parsing cause the error SEC_ERROR_OCSP_BAD_HTTP_RESPONSE to be + * set; any other problems are likely low-level i/o or memory allocation + * errors. + */ +static SECItem * +ocsp_GetEncodedResponse(PLArenaPool *arena, PRFileDesc *sock) +{ + /* first read HTTP status line and headers */ + + char *inBuffer = NULL; + PRInt32 offset = 0; + PRInt32 inBufsize = 0; + const PRInt32 bufSizeIncrement = OCSP_BUFSIZE; /* 1 KB at a time */ + const PRInt32 maxBufSize = 8 * bufSizeIncrement; /* 8 KB max */ + const char *CRLF = "\r\n"; + const PRInt32 CRLFlen = strlen(CRLF); + const char *headerEndMark = "\r\n\r\n"; + const PRInt32 markLen = strlen(headerEndMark); + const PRIntervalTime ocsptimeout = + PR_SecondsToInterval(30); /* hardcoded to 30s for now */ + char *headerEnd = NULL; + PRBool EOS = PR_FALSE; + const char *httpprotocol = "HTTP/"; + const PRInt32 httplen = strlen(httpprotocol); + const char *httpcode = NULL; + const char *contenttype = NULL; + PRInt32 contentlength = 0; + PRInt32 bytesRead = 0; + char *statusLineEnd = NULL; + char *space = NULL; + char *nextHeader = NULL; + SECItem *result = NULL; + + /* read up to at least the end of the HTTP headers */ + do { + inBufsize += bufSizeIncrement; + inBuffer = PORT_Realloc(inBuffer, inBufsize + 1); + if (NULL == inBuffer) { + AbortHttpDecode(SEC_ERROR_NO_MEMORY); + } + bytesRead = ocsp_read(sock, inBuffer + offset, bufSizeIncrement, + ocsptimeout); + if (bytesRead > 0) { + PRInt32 searchOffset = (offset - markLen) > 0 ? offset - markLen : 0; + offset += bytesRead; + *(inBuffer + offset) = '\0'; /* NULL termination */ + headerEnd = strstr((const char *)inBuffer + searchOffset, headerEndMark); + if (bytesRead < bufSizeIncrement) { + /* we read less data than requested, therefore we are at + EOS or there was a read error */ + EOS = PR_TRUE; + } + } else { + /* recv error or EOS */ + EOS = PR_TRUE; + } + } while ((!headerEnd) && (PR_FALSE == EOS) && + (inBufsize < maxBufSize)); + + if (!headerEnd) { + AbortHttpDecode(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE); + } + + /* parse the HTTP status line */ + statusLineEnd = strstr((const char *)inBuffer, CRLF); + if (!statusLineEnd) { + AbortHttpDecode(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE); + } + *statusLineEnd = '\0'; + + /* check for HTTP/ response */ + space = strchr((const char *)inBuffer, ' '); + if (!space || PORT_Strncasecmp((const char *)inBuffer, httpprotocol, httplen) != 0) { + AbortHttpDecode(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE); + } + + /* check the HTTP status code of 200 */ + httpcode = space + 1; + space = strchr(httpcode, ' '); + if (!space) { + AbortHttpDecode(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE); + } + *space = 0; + if (0 != strcmp(httpcode, "200")) { + AbortHttpDecode(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE); + } + + /* parse the HTTP headers in the buffer . We only care about + content-type and content-length + */ + + nextHeader = statusLineEnd + CRLFlen; + *headerEnd = '\0'; /* terminate */ + do { + char *thisHeaderEnd = NULL; + char *value = NULL; + char *colon = strchr(nextHeader, ':'); + + if (!colon) { + AbortHttpDecode(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE); + } + + *colon = '\0'; + value = colon + 1; + + /* jpierre - note : the following code will only handle the basic form + of HTTP/1.0 response headers, of the form "name: value" . Headers + split among multiple lines are not supported. This is not common + and should not be an issue, but it could become one in the + future */ + + if (*value != ' ') { + AbortHttpDecode(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE); + } + + value++; + thisHeaderEnd = strstr(value, CRLF); + if (thisHeaderEnd) { + *thisHeaderEnd = '\0'; + } + + if (0 == PORT_Strcasecmp(nextHeader, "content-type")) { + contenttype = value; + } else if (0 == PORT_Strcasecmp(nextHeader, "content-length")) { + contentlength = atoi(value); + } + + if (thisHeaderEnd) { + nextHeader = thisHeaderEnd + CRLFlen; + } else { + nextHeader = NULL; + } + + } while (nextHeader && (nextHeader < (headerEnd + CRLFlen))); + + /* check content-type */ + if (!contenttype || + (0 != PORT_Strcasecmp(contenttype, "application/ocsp-response"))) { + AbortHttpDecode(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE); + } + + /* read the body of the OCSP response */ + offset = offset - (PRInt32)(headerEnd - (const char *)inBuffer) - markLen; + if (offset) { + /* move all data to the beginning of the buffer */ + PORT_Memmove(inBuffer, headerEnd + markLen, offset); + } + + /* resize buffer to only what's needed to hold the current response */ + inBufsize = (1 + (offset - 1) / bufSizeIncrement) * bufSizeIncrement; + + while ((PR_FALSE == EOS) && + ((contentlength == 0) || (offset < contentlength)) && + (inBufsize < maxBufSize)) { + /* we still need to receive more body data */ + inBufsize += bufSizeIncrement; + inBuffer = PORT_Realloc(inBuffer, inBufsize + 1); + if (NULL == inBuffer) { + AbortHttpDecode(SEC_ERROR_NO_MEMORY); + } + bytesRead = ocsp_read(sock, inBuffer + offset, bufSizeIncrement, + ocsptimeout); + if (bytesRead > 0) { + offset += bytesRead; + if (bytesRead < bufSizeIncrement) { + /* we read less data than requested, therefore we are at + EOS or there was a read error */ + EOS = PR_TRUE; + } + } else { + /* recv error or EOS */ + EOS = PR_TRUE; + } + } + + if (0 == offset) { + AbortHttpDecode(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE); + } + + /* + * Now allocate the item to hold the data. + */ + result = SECITEM_AllocItem(arena, NULL, offset); + if (NULL == result) { + AbortHttpDecode(SEC_ERROR_NO_MEMORY); + } + + /* + * And copy the data left in the buffer. + */ + PORT_Memcpy(result->data, inBuffer, offset); + + /* and free the temporary buffer */ + PORT_Free(inBuffer); + return result; +} + +SECStatus +CERT_ParseURL(const char *url, char **pHostname, PRUint16 *pPort, char **pPath) +{ + return ocsp_ParseURL(url, pHostname, pPort, pPath); +} + +/* + * Limit the size of http responses we are willing to accept. + */ +#define MAX_WANTED_OCSP_RESPONSE_LEN 64 * 1024 + +/* if (encodedRequest == NULL) + * then location MUST already include the full request, + * including base64 and urlencode, + * and the request will be sent with GET + * if (encodedRequest != NULL) + * then the request will be sent with POST + */ +static SECItem * +fetchOcspHttpClientV1(PLArenaPool *arena, + const SEC_HttpClientFcnV1 *hcv1, + const char *location, + const SECItem *encodedRequest) +{ + char *hostname = NULL; + char *path = NULL; + PRUint16 port; + SECItem *encodedResponse = NULL; + SEC_HTTP_SERVER_SESSION pServerSession = NULL; + SEC_HTTP_REQUEST_SESSION pRequestSession = NULL; + PRUint16 myHttpResponseCode; + const char *myHttpResponseData; + PRUint32 myHttpResponseDataLen; + + if (ocsp_ParseURL(location, &hostname, &port, &path) == SECFailure) { + PORT_SetError(SEC_ERROR_OCSP_MALFORMED_REQUEST); + goto loser; + } + + PORT_Assert(hostname != NULL); + PORT_Assert(path != NULL); + + if ((*hcv1->createSessionFcn)( + hostname, + port, + &pServerSession) != SECSuccess) { + PORT_SetError(SEC_ERROR_OCSP_SERVER_ERROR); + goto loser; + } + + /* We use a non-zero timeout, which means: + - the client will use blocking I/O + - TryFcn will not return WOULD_BLOCK nor a poll descriptor + - it's sufficient to call TryFcn once + No lock for accessing OCSP_Global.timeoutSeconds, bug 406120 + */ + + if ((*hcv1->createFcn)( + pServerSession, + "http", + path, + encodedRequest ? "POST" : "GET", + PR_TicksPerSecond() * OCSP_Global.timeoutSeconds, + &pRequestSession) != SECSuccess) { + PORT_SetError(SEC_ERROR_OCSP_SERVER_ERROR); + goto loser; + } + + if (encodedRequest && + (*hcv1->setPostDataFcn)( + pRequestSession, + (char *)encodedRequest->data, + encodedRequest->len, + "application/ocsp-request") != SECSuccess) { + PORT_SetError(SEC_ERROR_OCSP_SERVER_ERROR); + goto loser; + } + + /* we don't want result objects larger than this: */ + myHttpResponseDataLen = MAX_WANTED_OCSP_RESPONSE_LEN; + + OCSP_TRACE(("OCSP trySendAndReceive %s\n", location)); + + if ((*hcv1->trySendAndReceiveFcn)( + pRequestSession, + NULL, + &myHttpResponseCode, + NULL, + NULL, + &myHttpResponseData, + &myHttpResponseDataLen) != SECSuccess) { + PORT_SetError(SEC_ERROR_OCSP_SERVER_ERROR); + goto loser; + } + + OCSP_TRACE(("OCSP trySendAndReceive result http %d\n", myHttpResponseCode)); + + if (myHttpResponseCode != 200) { + PORT_SetError(SEC_ERROR_OCSP_BAD_HTTP_RESPONSE); + goto loser; + } + + encodedResponse = SECITEM_AllocItem(arena, NULL, myHttpResponseDataLen); + + if (!encodedResponse) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + PORT_Memcpy(encodedResponse->data, myHttpResponseData, myHttpResponseDataLen); + +loser: + if (pRequestSession != NULL) + (*hcv1->freeFcn)(pRequestSession); + if (pServerSession != NULL) + (*hcv1->freeSessionFcn)(pServerSession); + if (path != NULL) + PORT_Free(path); + if (hostname != NULL) + PORT_Free(hostname); + + return encodedResponse; +} + +/* + * FUNCTION: CERT_GetEncodedOCSPResponseByMethod + * Creates and sends a request to an OCSP responder, then reads and + * returns the (encoded) response. + * INPUTS: + * PLArenaPool *arena + * Pointer to arena from which return value will be allocated. + * If NULL, result will be allocated from the heap (and thus should + * be freed via SECITEM_FreeItem). + * CERTCertList *certList + * A list of certs for which status will be requested. + * Note that all of these certificates should have the same issuer, + * or it's expected the response will be signed by a trusted responder. + * If the certs need to be broken up into multiple requests, that + * must be handled by the caller (and thus by having multiple calls + * to this routine), who knows about where the request(s) are being + * sent and whether there are any trusted responders in place. + * const char *location + * The location of the OCSP responder (a URL). + * const char *method + * The protocol method used when retrieving the OCSP response. + * Currently support: "GET" (http GET) and "POST" (http POST). + * Additionals methods for http or other protocols might be added + * in the future. + * PRTime time + * Indicates the time for which the certificate status is to be + * determined -- this may be used in the search for the cert's issuer + * but has no other bearing on the operation. + * PRBool addServiceLocator + * If true, the Service Locator extension should be added to the + * single request(s) for each cert. + * CERTCertificate *signerCert + * If non-NULL, means sign the request using this cert. Otherwise, + * do not sign. + * void *pwArg + * Pointer to argument for password prompting, if needed. (Definitely + * not needed if not signing.) + * OUTPUTS: + * CERTOCSPRequest **pRequest + * Pointer in which to store the OCSP request created for the given + * list of certificates. It is only filled in if the entire operation + * is successful and the pointer is not null -- and in that case the + * caller is then reponsible for destroying it. + * RETURN: + * Returns a pointer to the SECItem holding the response. + * On error, returns null with error set describing the reason: + * SEC_ERROR_UNKNOWN_ISSUER + * SEC_ERROR_CERT_BAD_ACCESS_LOCATION + * SEC_ERROR_OCSP_BAD_HTTP_RESPONSE + * Other errors are low-level problems (no memory, bad database, etc.). + */ +SECItem * +CERT_GetEncodedOCSPResponseByMethod(PLArenaPool *arena, CERTCertList *certList, + const char *location, const char *method, + PRTime time, PRBool addServiceLocator, + CERTCertificate *signerCert, void *pwArg, + CERTOCSPRequest **pRequest) +{ + CERTOCSPRequest *request; + request = CERT_CreateOCSPRequest(certList, time, addServiceLocator, + signerCert); + if (!request) + return NULL; + return ocsp_GetEncodedOCSPResponseFromRequest(arena, request, location, + method, time, addServiceLocator, + pwArg, pRequest); +} + +/* + * FUNCTION: CERT_GetEncodedOCSPResponse + * Creates and sends a request to an OCSP responder, then reads and + * returns the (encoded) response. + * + * This is a legacy API that behaves identically to + * CERT_GetEncodedOCSPResponseByMethod using the "POST" method. + */ +SECItem * +CERT_GetEncodedOCSPResponse(PLArenaPool *arena, CERTCertList *certList, + const char *location, PRTime time, + PRBool addServiceLocator, + CERTCertificate *signerCert, void *pwArg, + CERTOCSPRequest **pRequest) +{ + return CERT_GetEncodedOCSPResponseByMethod(arena, certList, location, + "POST", time, addServiceLocator, + signerCert, pwArg, pRequest); +} + +/* URL encode a buffer that consists of base64-characters, only, + * which means we can use a simple encoding logic. + * + * No output buffer size checking is performed. + * You should call the function twice, to calculate the required buffer size. + * + * If the outpufBuf parameter is NULL, the function will calculate the + * required size, including the trailing zero termination char. + * + * The function returns the number of bytes calculated or produced. + */ +size_t +ocsp_UrlEncodeBase64Buf(const char *base64Buf, char *outputBuf) +{ + const char *walkInput = NULL; + char *walkOutput = outputBuf; + size_t count = 0; + + for (walkInput = base64Buf; *walkInput; ++walkInput) { + char c = *walkInput; + if (isspace(c)) + continue; + switch (c) { + case '+': + if (outputBuf) { + strcpy(walkOutput, "%2B"); + walkOutput += 3; + } + count += 3; + break; + case '/': + if (outputBuf) { + strcpy(walkOutput, "%2F"); + walkOutput += 3; + } + count += 3; + break; + case '=': + if (outputBuf) { + strcpy(walkOutput, "%3D"); + walkOutput += 3; + } + count += 3; + break; + default: + if (outputBuf) { + *walkOutput = *walkInput; + ++walkOutput; + } + ++count; + break; + } + } + if (outputBuf) { + *walkOutput = 0; + } + ++count; + return count; +} + +enum { max_get_request_size = 255 }; /* defined by RFC2560 */ + +static SECItem * +cert_GetOCSPResponse(PLArenaPool *arena, const char *location, + const SECItem *encodedRequest); + +static SECItem * +ocsp_GetEncodedOCSPResponseFromRequest(PLArenaPool *arena, + CERTOCSPRequest *request, + const char *location, + const char *method, + PRTime time, + PRBool addServiceLocator, + void *pwArg, + CERTOCSPRequest **pRequest) +{ + SECItem *encodedRequest = NULL; + SECItem *encodedResponse = NULL; + SECStatus rv; + + if (!location || !*location) /* location should be at least one byte */ + goto loser; + + rv = CERT_AddOCSPAcceptableResponses(request, + SEC_OID_PKIX_OCSP_BASIC_RESPONSE); + if (rv != SECSuccess) + goto loser; + + encodedRequest = CERT_EncodeOCSPRequest(NULL, request, pwArg); + if (encodedRequest == NULL) + goto loser; + + if (!strcmp(method, "GET")) { + encodedResponse = cert_GetOCSPResponse(arena, location, encodedRequest); + } else if (!strcmp(method, "POST")) { + encodedResponse = CERT_PostOCSPRequest(arena, location, encodedRequest); + } else { + goto loser; + } + + if (encodedResponse != NULL && pRequest != NULL) { + *pRequest = request; + request = NULL; /* avoid destroying below */ + } + +loser: + if (request != NULL) + CERT_DestroyOCSPRequest(request); + if (encodedRequest != NULL) + SECITEM_FreeItem(encodedRequest, PR_TRUE); + return encodedResponse; +} + +static SECItem * +cert_FetchOCSPResponse(PLArenaPool *arena, const char *location, + const SECItem *encodedRequest); + +/* using HTTP GET method */ +static SECItem * +cert_GetOCSPResponse(PLArenaPool *arena, const char *location, + const SECItem *encodedRequest) +{ + char *walkOutput = NULL; + char *fullGetPath = NULL; + size_t pathLength; + PRInt32 urlEncodedBufLength; + size_t base64size; + char b64ReqBuf[max_get_request_size + 1]; + size_t slashLengthIfNeeded = 0; + size_t getURLLength; + SECItem *item; + + if (!location || !*location) { + return NULL; + } + + pathLength = strlen(location); + if (location[pathLength - 1] != '/') { + slashLengthIfNeeded = 1; + } + + /* Calculation as documented by PL_Base64Encode function. + * Use integer conversion to avoid having to use function ceil(). + */ + base64size = (((encodedRequest->len + 2) / 3) * 4); + if (base64size > max_get_request_size) { + return NULL; + } + memset(b64ReqBuf, 0, sizeof(b64ReqBuf)); + PL_Base64Encode((const char *)encodedRequest->data, encodedRequest->len, + b64ReqBuf); + + urlEncodedBufLength = ocsp_UrlEncodeBase64Buf(b64ReqBuf, NULL); + getURLLength = pathLength + urlEncodedBufLength + slashLengthIfNeeded; + + /* urlEncodedBufLength already contains room for the zero terminator. + * Add another if we must add the '/' char. + */ + if (arena) { + fullGetPath = (char *)PORT_ArenaAlloc(arena, getURLLength); + } else { + fullGetPath = (char *)PORT_Alloc(getURLLength); + } + if (!fullGetPath) { + return NULL; + } + + strcpy(fullGetPath, location); + walkOutput = fullGetPath + pathLength; + + if (walkOutput > fullGetPath && slashLengthIfNeeded) { + strcpy(walkOutput, "/"); + ++walkOutput; + } + ocsp_UrlEncodeBase64Buf(b64ReqBuf, walkOutput); + + item = cert_FetchOCSPResponse(arena, fullGetPath, NULL); + if (!arena) { + PORT_Free(fullGetPath); + } + return item; +} + +SECItem * +CERT_PostOCSPRequest(PLArenaPool *arena, const char *location, + const SECItem *encodedRequest) +{ + return cert_FetchOCSPResponse(arena, location, encodedRequest); +} + +SECItem * +cert_FetchOCSPResponse(PLArenaPool *arena, const char *location, + const SECItem *encodedRequest) +{ + const SEC_HttpClientFcn *registeredHttpClient; + SECItem *encodedResponse = NULL; + + registeredHttpClient = SEC_GetRegisteredHttpClient(); + + if (registeredHttpClient && registeredHttpClient->version == 1) { + encodedResponse = fetchOcspHttpClientV1( + arena, + ®isteredHttpClient->fcnTable.ftable1, + location, + encodedRequest); + } else { + /* use internal http client */ + PRFileDesc *sock = ocsp_SendEncodedRequest(location, encodedRequest); + if (sock) { + encodedResponse = ocsp_GetEncodedResponse(arena, sock); + PR_Close(sock); + } + } + + return encodedResponse; +} + +static SECItem * +ocsp_GetEncodedOCSPResponseForSingleCert(PLArenaPool *arena, + CERTOCSPCertID *certID, + CERTCertificate *singleCert, + const char *location, + const char *method, + PRTime time, + PRBool addServiceLocator, + void *pwArg, + CERTOCSPRequest **pRequest) +{ + CERTOCSPRequest *request; + request = cert_CreateSingleCertOCSPRequest(certID, singleCert, time, + addServiceLocator, NULL); + if (!request) + return NULL; + return ocsp_GetEncodedOCSPResponseFromRequest(arena, request, location, + method, time, addServiceLocator, + pwArg, pRequest); +} + +/* Checks a certificate for the key usage extension of OCSP signer. */ +static PRBool +ocsp_CertIsOCSPDesignatedResponder(CERTCertificate *cert) +{ + SECStatus rv; + SECItem extItem; + SECItem **oids; + SECItem *oid; + SECOidTag oidTag; + PRBool retval; + CERTOidSequence *oidSeq = NULL; + + extItem.data = NULL; + rv = CERT_FindCertExtension(cert, SEC_OID_X509_EXT_KEY_USAGE, &extItem); + if (rv != SECSuccess) { + goto loser; + } + + oidSeq = CERT_DecodeOidSequence(&extItem); + if (oidSeq == NULL) { + goto loser; + } + + oids = oidSeq->oids; + while (*oids != NULL) { + oid = *oids; + + oidTag = SECOID_FindOIDTag(oid); + + if (oidTag == SEC_OID_OCSP_RESPONDER) { + goto success; + } + + oids++; + } + +loser: + retval = PR_FALSE; + PORT_SetError(SEC_ERROR_OCSP_INVALID_SIGNING_CERT); + goto done; +success: + retval = PR_TRUE; +done: + if (extItem.data != NULL) { + PORT_Free(extItem.data); + } + if (oidSeq != NULL) { + CERT_DestroyOidSequence(oidSeq); + } + + return (retval); +} + +#ifdef LATER /* \ + * XXX This function is not currently used, but will \ + * be needed later when we do revocation checking of \ + * the responder certificate. Of course, it may need \ + * revising then, if the cert extension interface has \ + * changed. (Hopefully it will!) \ + */ + +/* Checks a certificate to see if it has the OCSP no check extension. */ +static PRBool +ocsp_CertHasNoCheckExtension(CERTCertificate *cert) +{ + SECStatus rv; + + rv = CERT_FindCertExtension(cert, SEC_OID_PKIX_OCSP_NO_CHECK, + NULL); + if (rv == SECSuccess) { + return PR_TRUE; + } + return PR_FALSE; +} +#endif /* LATER */ + +static PRBool +ocsp_matchcert(SECItem *certIndex, CERTCertificate *testCert) +{ + SECItem item; + unsigned char buf[HASH_LENGTH_MAX]; + + item.data = buf; + item.len = SHA1_LENGTH; + + if (CERT_GetSubjectPublicKeyDigest(NULL, testCert, SEC_OID_SHA1, + &item) == NULL) { + return PR_FALSE; + } + if (SECITEM_ItemsAreEqual(certIndex, &item)) { + return PR_TRUE; + } + if (CERT_GetSubjectPublicKeyDigest(NULL, testCert, SEC_OID_MD5, + &item) == NULL) { + return PR_FALSE; + } + if (SECITEM_ItemsAreEqual(certIndex, &item)) { + return PR_TRUE; + } + if (CERT_GetSubjectPublicKeyDigest(NULL, testCert, SEC_OID_MD2, + &item) == NULL) { + return PR_FALSE; + } + if (SECITEM_ItemsAreEqual(certIndex, &item)) { + return PR_TRUE; + } + + return PR_FALSE; +} + +static CERTCertificate * +ocsp_CertGetDefaultResponder(CERTCertDBHandle *handle, CERTOCSPCertID *certID); + +CERTCertificate * +ocsp_GetSignerCertificate(CERTCertDBHandle *handle, ocspResponseData *tbsData, + ocspSignature *signature, CERTCertificate *issuer) +{ + CERTCertificate **certs = NULL; + CERTCertificate *signerCert = NULL; + SECStatus rv = SECFailure; + PRBool lookupByName = PR_TRUE; + void *certIndex = NULL; + int certCount = 0; + + PORT_Assert(tbsData->responderID != NULL); + switch (tbsData->responderID->responderIDType) { + case ocspResponderID_byName: + lookupByName = PR_TRUE; + certIndex = &tbsData->derResponderID; + break; + case ocspResponderID_byKey: + lookupByName = PR_FALSE; + certIndex = &tbsData->responderID->responderIDValue.keyHash; + break; + case ocspResponderID_other: + default: + PORT_Assert(0); + PORT_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE); + return NULL; + } + + /* + * If the signature contains some certificates as well, temporarily + * import them in case they are needed for verification. + * + * Note that the result of this is that each cert in "certs" needs + * to be destroyed. + */ + if (signature->derCerts != NULL) { + for (; signature->derCerts[certCount] != NULL; certCount++) { + /* just counting */ + } + rv = CERT_ImportCerts(handle, certUsageStatusResponder, certCount, + signature->derCerts, &certs, + PR_FALSE, PR_FALSE, NULL); + if (rv != SECSuccess) + goto finish; + } + + /* + * Now look up the certificate that did the signing. + * The signer can be specified either by name or by key hash. + */ + if (lookupByName) { + SECItem *crIndex = (SECItem *)certIndex; + SECItem encodedName; + PLArenaPool *arena; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena != NULL) { + + rv = SEC_QuickDERDecodeItem(arena, &encodedName, + ocsp_ResponderIDDerNameTemplate, + crIndex); + if (rv != SECSuccess) { + if (PORT_GetError() == SEC_ERROR_BAD_DER) + PORT_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE); + } else { + signerCert = CERT_FindCertByName(handle, &encodedName); + } + PORT_FreeArena(arena, PR_FALSE); + } + } else { + /* + * The signer is either 1) a known issuer CA we passed in, + * 2) the default OCSP responder, or 3) an intermediate CA + * passed in the cert list to use. Figure out which it is. + */ + int i; + CERTCertificate *responder = + ocsp_CertGetDefaultResponder(handle, NULL); + if (responder && ocsp_matchcert(certIndex, responder)) { + signerCert = CERT_DupCertificate(responder); + } else if (issuer && ocsp_matchcert(certIndex, issuer)) { + signerCert = CERT_DupCertificate(issuer); + } + for (i = 0; (signerCert == NULL) && (i < certCount); i++) { + if (ocsp_matchcert(certIndex, certs[i])) { + signerCert = CERT_DupCertificate(certs[i]); + } + } + if (signerCert == NULL) { + PORT_SetError(SEC_ERROR_UNKNOWN_CERT); + } + } + +finish: + if (certs != NULL) { + CERT_DestroyCertArray(certs, certCount); + } + + return signerCert; +} + +SECStatus +ocsp_VerifyResponseSignature(CERTCertificate *signerCert, + ocspSignature *signature, + SECItem *tbsResponseDataDER, + void *pwArg) +{ + SECKEYPublicKey *signerKey = NULL; + SECStatus rv = SECFailure; + CERTSignedData signedData; + + /* + * Now get the public key from the signer's certificate; we need + * it to perform the verification. + */ + signerKey = CERT_ExtractPublicKey(signerCert); + if (signerKey == NULL) { + return SECFailure; + } + + /* + * We copy the signature data *pointer* and length, so that we can + * modify the length without damaging the original copy. This is a + * simple copy, not a dup, so no destroy/free is necessary. + */ + signedData.signature = signature->signature; + signedData.signatureAlgorithm = signature->signatureAlgorithm; + signedData.data = *tbsResponseDataDER; + + rv = CERT_VerifySignedDataWithPublicKey(&signedData, signerKey, pwArg); + if (rv != SECSuccess && + (PORT_GetError() == SEC_ERROR_BAD_SIGNATURE || + PORT_GetError() == SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED)) { + PORT_SetError(SEC_ERROR_OCSP_BAD_SIGNATURE); + } + + if (signerKey != NULL) { + SECKEY_DestroyPublicKey(signerKey); + } + + return rv; +} + +/* + * FUNCTION: CERT_VerifyOCSPResponseSignature + * Check the signature on an OCSP Response. Will also perform a + * verification of the signer's certificate. Note, however, that a + * successful verification does not make any statement about the + * signer's *authority* to provide status for the certificate(s), + * that must be checked individually for each certificate. + * INPUTS: + * CERTOCSPResponse *response + * Pointer to response structure with signature to be checked. + * CERTCertDBHandle *handle + * Pointer to CERTCertDBHandle for certificate DB to use for verification. + * void *pwArg + * Pointer to argument for password prompting, if needed. + * OUTPUTS: + * CERTCertificate **pSignerCert + * Pointer in which to store signer's certificate; only filled-in if + * non-null. + * RETURN: + * Returns SECSuccess when signature is valid, anything else means invalid. + * Possible errors set: + * SEC_ERROR_OCSP_MALFORMED_RESPONSE - unknown type of ResponderID + * SEC_ERROR_INVALID_TIME - bad format of "ProducedAt" time + * SEC_ERROR_UNKNOWN_SIGNER - signer's cert could not be found + * SEC_ERROR_BAD_SIGNATURE - the signature did not verify + * Other errors are any of the many possible failures in cert verification + * (e.g. SEC_ERROR_REVOKED_CERTIFICATE, SEC_ERROR_UNTRUSTED_ISSUER) when + * verifying the signer's cert, or low-level problems (no memory, etc.) + */ +SECStatus +CERT_VerifyOCSPResponseSignature(CERTOCSPResponse *response, + CERTCertDBHandle *handle, void *pwArg, + CERTCertificate **pSignerCert, + CERTCertificate *issuer) +{ + SECItem *tbsResponseDataDER; + CERTCertificate *signerCert = NULL; + SECStatus rv = SECFailure; + PRTime producedAt; + + /* ocsp_DecodeBasicOCSPResponse will fail if asn1 decoder is unable + * to properly decode tbsData (see the function and + * ocsp_BasicOCSPResponseTemplate). Thus, tbsData can not be + * equal to null */ + ocspResponseData *tbsData = ocsp_GetResponseData(response, + &tbsResponseDataDER); + ocspSignature *signature = ocsp_GetResponseSignature(response); + + if (!signature) { + PORT_SetError(SEC_ERROR_OCSP_BAD_SIGNATURE); + return SECFailure; + } + + /* + * If this signature has already gone through verification, just + * return the cached result. + */ + if (signature->wasChecked) { + if (signature->status == SECSuccess) { + if (pSignerCert != NULL) + *pSignerCert = CERT_DupCertificate(signature->cert); + } else { + PORT_SetError(signature->failureReason); + } + return signature->status; + } + + signerCert = ocsp_GetSignerCertificate(handle, tbsData, + signature, issuer); + if (signerCert == NULL) { + rv = SECFailure; + if (PORT_GetError() == SEC_ERROR_UNKNOWN_CERT) { + /* Make the error a little more specific. */ + PORT_SetError(SEC_ERROR_OCSP_INVALID_SIGNING_CERT); + } + goto finish; + } + + /* + * We could mark this true at the top of this function, or always + * below at "finish", but if the problem was just that we could not + * find the signer's cert, leave that as if the signature hasn't + * been checked in case a subsequent call might have better luck. + */ + signature->wasChecked = PR_TRUE; + + /* + * The function will also verify the signer certificate; we + * need to tell it *when* that certificate must be valid -- for our + * purposes we expect it to be valid when the response was signed. + * The value of "producedAt" is the signing time. + */ + rv = DER_GeneralizedTimeToTime(&producedAt, &tbsData->producedAt); + if (rv != SECSuccess) + goto finish; + + /* + * Just because we have a cert does not mean it is any good; check + * it for validity, trust and usage. + */ + if (!ocsp_CertIsOCSPDefaultResponder(handle, signerCert)) { + SECCertUsage certUsage; + if (CERT_IsCACert(signerCert, NULL)) { + certUsage = certUsageAnyCA; + } else { + certUsage = certUsageStatusResponder; + } + rv = cert_VerifyCertWithFlags(handle, signerCert, PR_TRUE, certUsage, + producedAt, CERT_VERIFYCERT_SKIP_OCSP, + pwArg, NULL); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_OCSP_INVALID_SIGNING_CERT); + goto finish; + } + } + + rv = ocsp_VerifyResponseSignature(signerCert, signature, + tbsResponseDataDER, + pwArg); + +finish: + if (signature->wasChecked) + signature->status = rv; + + if (rv != SECSuccess) { + signature->failureReason = PORT_GetError(); + if (signerCert != NULL) + CERT_DestroyCertificate(signerCert); + } else { + /* + * Save signer's certificate in signature. + */ + signature->cert = signerCert; + if (pSignerCert != NULL) { + /* + * Pass pointer to signer's certificate back to our caller, + * who is also now responsible for destroying it. + */ + *pSignerCert = CERT_DupCertificate(signerCert); + } + } + + return rv; +} + +/* + * See if the request's certID and the single response's certID match. + * This can be easy or difficult, depending on whether the same hash + * algorithm was used. + */ +static PRBool +ocsp_CertIDsMatch(CERTOCSPCertID *requestCertID, + CERTOCSPCertID *responseCertID) +{ + PRBool match = PR_FALSE; + SECOidTag hashAlg; + SECItem *keyHash = NULL; + SECItem *nameHash = NULL; + + /* + * In order to match, they must have the same issuer and the same + * serial number. + * + * We just compare the easier things first. + */ + if (SECITEM_CompareItem(&requestCertID->serialNumber, + &responseCertID->serialNumber) != SECEqual) { + goto done; + } + + /* + * Make sure the "parameters" are not too bogus. Since we encoded + * requestCertID->hashAlgorithm, we don't need to check it. + */ + if (responseCertID->hashAlgorithm.parameters.len > 2) { + goto done; + } + if (SECITEM_CompareItem(&requestCertID->hashAlgorithm.algorithm, + &responseCertID->hashAlgorithm.algorithm) == + SECEqual) { + /* + * If the hash algorithms match then we can do a simple compare + * of the hash values themselves. + */ + if ((SECITEM_CompareItem(&requestCertID->issuerNameHash, + &responseCertID->issuerNameHash) == SECEqual) && + (SECITEM_CompareItem(&requestCertID->issuerKeyHash, + &responseCertID->issuerKeyHash) == SECEqual)) { + match = PR_TRUE; + } + goto done; + } + + hashAlg = SECOID_FindOIDTag(&responseCertID->hashAlgorithm.algorithm); + switch (hashAlg) { + case SEC_OID_SHA1: + keyHash = &requestCertID->issuerSHA1KeyHash; + nameHash = &requestCertID->issuerSHA1NameHash; + break; + case SEC_OID_MD5: + keyHash = &requestCertID->issuerMD5KeyHash; + nameHash = &requestCertID->issuerMD5NameHash; + break; + case SEC_OID_MD2: + keyHash = &requestCertID->issuerMD2KeyHash; + nameHash = &requestCertID->issuerMD2NameHash; + break; + default: + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + return PR_FALSE; + } + + if ((keyHash != NULL) && + (SECITEM_CompareItem(nameHash, + &responseCertID->issuerNameHash) == SECEqual) && + (SECITEM_CompareItem(keyHash, + &responseCertID->issuerKeyHash) == SECEqual)) { + match = PR_TRUE; + } + +done: + return match; +} + +/* + * Find the single response for the cert specified by certID. + * No copying is done; this just returns a pointer to the appropriate + * response within responses, if it is found (and null otherwise). + * This is fine, of course, since this function is internal-use only. + */ +static CERTOCSPSingleResponse * +ocsp_GetSingleResponseForCertID(CERTOCSPSingleResponse **responses, + CERTCertDBHandle *handle, + CERTOCSPCertID *certID) +{ + CERTOCSPSingleResponse *single; + int i; + + if (responses == NULL) + return NULL; + + for (i = 0; responses[i] != NULL; i++) { + single = responses[i]; + if (ocsp_CertIDsMatch(certID, single->certID)) { + return single; + } + } + + /* + * The OCSP server should have included a response even if it knew + * nothing about the certificate in question. Since it did not, + * this will make it look as if it had. + * + * XXX Should we make this a separate error to notice the server's + * bad behavior? + */ + PORT_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT); + return NULL; +} + +static ocspCheckingContext * +ocsp_GetCheckingContext(CERTCertDBHandle *handle) +{ + CERTStatusConfig *statusConfig; + ocspCheckingContext *ocspcx = NULL; + + statusConfig = CERT_GetStatusConfig(handle); + if (statusConfig != NULL) { + ocspcx = statusConfig->statusContext; + + /* + * This is actually an internal error, because we should never + * have a good statusConfig without a good statusContext, too. + * For lack of anything better, though, we just assert and use + * the same error as if there were no statusConfig (set below). + */ + PORT_Assert(ocspcx != NULL); + } + + if (ocspcx == NULL) + PORT_SetError(SEC_ERROR_OCSP_NOT_ENABLED); + + return ocspcx; +} + +/* + * Return cert reference if the given signerCert is the default responder for + * the given certID. If not, or if any error, return NULL. + */ +static CERTCertificate * +ocsp_CertGetDefaultResponder(CERTCertDBHandle *handle, CERTOCSPCertID *certID) +{ + ocspCheckingContext *ocspcx; + + ocspcx = ocsp_GetCheckingContext(handle); + if (ocspcx == NULL) + goto loser; + + /* + * Right now we have only one default responder. It applies to + * all certs when it is used, so the check is simple and certID + * has no bearing on the answer. Someday in the future we may + * allow configuration of different responders for different + * issuers, and then we would have to use the issuer specified + * in certID to determine if signerCert is the right one. + */ + if (ocspcx->useDefaultResponder) { + PORT_Assert(ocspcx->defaultResponderCert != NULL); + return ocspcx->defaultResponderCert; + } + +loser: + return NULL; +} + +/* + * Return true if the cert is one of the default responders configured for + * ocsp context. If not, or if any error, return false. + */ +PRBool +ocsp_CertIsOCSPDefaultResponder(CERTCertDBHandle *handle, CERTCertificate *cert) +{ + ocspCheckingContext *ocspcx; + + ocspcx = ocsp_GetCheckingContext(handle); + if (ocspcx == NULL) + return PR_FALSE; + + /* + * Right now we have only one default responder. It applies to + * all certs when it is used, so the check is simple and certID + * has no bearing on the answer. Someday in the future we may + * allow configuration of different responders for different + * issuers, and then we would have to use the issuer specified + * in certID to determine if signerCert is the right one. + */ + if (ocspcx->useDefaultResponder && + CERT_CompareCerts(ocspcx->defaultResponderCert, cert)) { + return PR_TRUE; + } + + return PR_FALSE; +} + +/* + * Check that the given signer certificate is authorized to sign status + * information for the given certID. Return true if it is, false if not + * (or if there is any error along the way). If false is returned because + * the signer is not authorized, the following error will be set: + * SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE + * Other errors are low-level problems (no memory, bad database, etc.). + * + * There are three ways to be authorized. In the order in which we check, + * using the terms used in the OCSP spec, the signer must be one of: + * 1. A "trusted responder" -- it matches a local configuration + * of OCSP signing authority for the certificate in question. + * 2. The CA who issued the certificate in question. + * 3. A "CA designated responder", aka an "authorized responder" -- it + * must be represented by a special cert issued by the CA who issued + * the certificate in question. + */ +static PRBool +ocsp_AuthorizedResponderForCertID(CERTCertDBHandle *handle, + CERTCertificate *signerCert, + CERTOCSPCertID *certID, + PRTime thisUpdate) +{ + CERTCertificate *issuerCert = NULL, *defRespCert; + SECItem *keyHash = NULL; + SECItem *nameHash = NULL; + SECOidTag hashAlg; + PRBool keyHashEQ = PR_FALSE, nameHashEQ = PR_FALSE; + + /* + * Check first for a trusted responder, which overrides everything else. + */ + if ((defRespCert = ocsp_CertGetDefaultResponder(handle, certID)) && + CERT_CompareCerts(defRespCert, signerCert)) { + return PR_TRUE; + } + + /* + * In the other two cases, we need to do an issuer comparison. + * How we do it depends on whether the signer certificate has the + * special extension (for a designated responder) or not. + * + * First, lets check if signer of the response is the actual issuer + * of the cert. For that we will use signer cert key hash and cert subj + * name hash and will compare them with already calculated issuer key + * hash and issuer name hash. The hash algorithm is picked from response + * certID hash to avoid second hash calculation. + */ + + hashAlg = SECOID_FindOIDTag(&certID->hashAlgorithm.algorithm); + + keyHash = CERT_GetSubjectPublicKeyDigest(NULL, signerCert, hashAlg, NULL); + if (keyHash != NULL) { + + keyHashEQ = + (SECITEM_CompareItem(keyHash, + &certID->issuerKeyHash) == SECEqual); + SECITEM_FreeItem(keyHash, PR_TRUE); + } + if (keyHashEQ && + (nameHash = CERT_GetSubjectNameDigest(NULL, signerCert, + hashAlg, NULL))) { + nameHashEQ = + (SECITEM_CompareItem(nameHash, + &certID->issuerNameHash) == SECEqual); + + SECITEM_FreeItem(nameHash, PR_TRUE); + if (nameHashEQ) { + /* The issuer of the cert is the the signer of the response */ + return PR_TRUE; + } + } + + keyHashEQ = PR_FALSE; + nameHashEQ = PR_FALSE; + + if (!ocsp_CertIsOCSPDesignatedResponder(signerCert)) { + PORT_SetError(SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE); + return PR_FALSE; + } + + /* + * The signer is a designated responder. Its issuer must match + * the issuer of the cert being checked. + */ + issuerCert = CERT_FindCertIssuer(signerCert, thisUpdate, + certUsageAnyCA); + if (issuerCert == NULL) { + /* + * We could leave the SEC_ERROR_UNKNOWN_ISSUER error alone, + * but the following will give slightly more information. + * Once we have an error stack, things will be much better. + */ + PORT_SetError(SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE); + return PR_FALSE; + } + + keyHash = CERT_GetSubjectPublicKeyDigest(NULL, issuerCert, hashAlg, NULL); + nameHash = CERT_GetSubjectNameDigest(NULL, issuerCert, hashAlg, NULL); + + CERT_DestroyCertificate(issuerCert); + + if (keyHash != NULL && nameHash != NULL) { + keyHashEQ = + (SECITEM_CompareItem(keyHash, + &certID->issuerKeyHash) == SECEqual); + + nameHashEQ = + (SECITEM_CompareItem(nameHash, + &certID->issuerNameHash) == SECEqual); + } + + if (keyHash) { + SECITEM_FreeItem(keyHash, PR_TRUE); + } + if (nameHash) { + SECITEM_FreeItem(nameHash, PR_TRUE); + } + + if (keyHashEQ && nameHashEQ) { + return PR_TRUE; + } + + PORT_SetError(SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE); + return PR_FALSE; +} + +/* + * We need to check that a responder gives us "recent" information. + * Since a responder can pre-package responses, we need to pick an amount + * of time that is acceptable to us, and reject any response that is + * older than that. + * + * XXX This *should* be based on some configuration parameter, so that + * different usages could specify exactly what constitutes "sufficiently + * recent". But that is not going to happen right away. For now, we + * want something from within the last 24 hours. This macro defines that + * number in seconds. + */ +#define OCSP_ALLOWABLE_LAPSE_SECONDS (24L * 60L * 60L) + +static PRBool +ocsp_TimeIsRecent(PRTime checkTime) +{ + PRTime now = PR_Now(); + PRTime lapse, tmp; + + LL_I2L(lapse, OCSP_ALLOWABLE_LAPSE_SECONDS); + LL_I2L(tmp, PR_USEC_PER_SEC); + LL_MUL(lapse, lapse, tmp); /* allowable lapse in microseconds */ + + LL_ADD(checkTime, checkTime, lapse); + if (LL_CMP(now, >, checkTime)) + return PR_FALSE; + + return PR_TRUE; +} + +#define OCSP_SLOP (5L * 60L) /* OCSP responses are allowed to be 5 minutes \ + in the future by default */ + +static PRUint32 ocspsloptime = OCSP_SLOP; /* seconds */ + +/* + * If an old response contains the revoked certificate status, we want + * to return SECSuccess so the response will be used. + */ +static SECStatus +ocsp_HandleOldSingleResponse(CERTOCSPSingleResponse *single, PRTime time) +{ + SECStatus rv; + ocspCertStatus *status = single->certStatus; + if (status->certStatusType == ocspCertStatus_revoked) { + rv = ocsp_CertRevokedAfter(status->certStatusInfo.revokedInfo, time); + if (rv != SECSuccess && + PORT_GetError() == SEC_ERROR_REVOKED_CERTIFICATE) { + /* + * Return SECSuccess now. The subsequent ocsp_CertRevokedAfter + * call in ocsp_CertHasGoodStatus will cause + * ocsp_CertHasGoodStatus to fail with + * SEC_ERROR_REVOKED_CERTIFICATE. + */ + return SECSuccess; + } + } + PORT_SetError(SEC_ERROR_OCSP_OLD_RESPONSE); + return SECFailure; +} + +/* + * Check that this single response is okay. A return of SECSuccess means: + * 1. The signer (represented by "signerCert") is authorized to give status + * for the cert represented by the individual response in "single". + * 2. The value of thisUpdate is earlier than now. + * 3. The value of producedAt is later than or the same as thisUpdate. + * 4. If nextUpdate is given: + * - The value of nextUpdate is later than now. + * - The value of producedAt is earlier than nextUpdate. + * Else if no nextUpdate: + * - The value of thisUpdate is fairly recent. + * - The value of producedAt is fairly recent. + * However we do not need to perform an explicit check for this last + * constraint because it is already guaranteed by checking that + * producedAt is later than thisUpdate and thisUpdate is recent. + * Oh, and any responder is "authorized" to say that a cert is unknown to it. + * + * If any of those checks fail, SECFailure is returned and an error is set: + * SEC_ERROR_OCSP_FUTURE_RESPONSE + * SEC_ERROR_OCSP_OLD_RESPONSE + * SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE + * Other errors are low-level problems (no memory, bad database, etc.). + */ +static SECStatus +ocsp_VerifySingleResponse(CERTOCSPSingleResponse *single, + CERTCertDBHandle *handle, + CERTCertificate *signerCert, + PRTime producedAt) +{ + CERTOCSPCertID *certID = single->certID; + PRTime now, thisUpdate, nextUpdate, tmstamp, tmp; + SECStatus rv; + + OCSP_TRACE(("OCSP ocsp_VerifySingleResponse, nextUpdate: %d\n", + ((single->nextUpdate) != 0))); + /* + * If all the responder said was that the given cert was unknown to it, + * that is a valid response. Not very interesting to us, of course, + * but all this function is concerned with is validity of the response, + * not the status of the cert. + */ + PORT_Assert(single->certStatus != NULL); + if (single->certStatus->certStatusType == ocspCertStatus_unknown) + return SECSuccess; + + /* + * We need to extract "thisUpdate" for use below and to pass along + * to AuthorizedResponderForCertID in case it needs it for doing an + * issuer look-up. + */ + rv = DER_GeneralizedTimeToTime(&thisUpdate, &single->thisUpdate); + if (rv != SECSuccess) + return rv; + + /* + * First confirm that signerCert is authorized to give this status. + */ + if (ocsp_AuthorizedResponderForCertID(handle, signerCert, certID, + thisUpdate) != PR_TRUE) + return SECFailure; + + /* + * Now check the time stuff, as described above. + */ + now = PR_Now(); + /* allow slop time for future response */ + LL_UI2L(tmstamp, ocspsloptime); /* get slop time in seconds */ + LL_UI2L(tmp, PR_USEC_PER_SEC); + LL_MUL(tmp, tmstamp, tmp); /* convert the slop time to PRTime */ + LL_ADD(tmstamp, tmp, now); /* add current time to it */ + + if (LL_CMP(thisUpdate, >, tmstamp) || LL_CMP(producedAt, <, thisUpdate)) { + PORT_SetError(SEC_ERROR_OCSP_FUTURE_RESPONSE); + return SECFailure; + } + if (single->nextUpdate != NULL) { + rv = DER_GeneralizedTimeToTime(&nextUpdate, single->nextUpdate); + if (rv != SECSuccess) + return rv; + + LL_ADD(tmp, tmp, nextUpdate); + if (LL_CMP(tmp, <, now) || LL_CMP(producedAt, >, nextUpdate)) + return ocsp_HandleOldSingleResponse(single, now); + } else if (ocsp_TimeIsRecent(thisUpdate) != PR_TRUE) { + return ocsp_HandleOldSingleResponse(single, now); + } + + return SECSuccess; +} + +/* + * FUNCTION: CERT_GetOCSPAuthorityInfoAccessLocation + * Get the value of the URI of the OCSP responder for the given cert. + * This is found in the (optional) Authority Information Access extension + * in the cert. + * INPUTS: + * CERTCertificate *cert + * The certificate being examined. + * RETURN: + * char * + * A copy of the URI for the OCSP method, if found. If either the + * extension is not present or it does not contain an entry for OCSP, + * SEC_ERROR_CERT_BAD_ACCESS_LOCATION will be set and a NULL returned. + * Any other error will also result in a NULL being returned. + * + * This result should be freed (via PORT_Free) when no longer in use. + */ +char * +CERT_GetOCSPAuthorityInfoAccessLocation(const CERTCertificate *cert) +{ + CERTGeneralName *locname = NULL; + SECItem *location = NULL; + SECItem *encodedAuthInfoAccess = NULL; + CERTAuthInfoAccess **authInfoAccess = NULL; + char *locURI = NULL; + PLArenaPool *arena = NULL; + SECStatus rv; + int i; + + /* + * Allocate this one from the heap because it will get filled in + * by CERT_FindCertExtension which will also allocate from the heap, + * and we can free the entire thing on our way out. + */ + encodedAuthInfoAccess = SECITEM_AllocItem(NULL, NULL, 0); + if (encodedAuthInfoAccess == NULL) + goto loser; + + rv = CERT_FindCertExtension(cert, SEC_OID_X509_AUTH_INFO_ACCESS, + encodedAuthInfoAccess); + if (rv == SECFailure) { + PORT_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION); + goto loser; + } + + /* + * The rest of the things allocated in the routine will come out of + * this arena, which is temporary just for us to decode and get at the + * AIA extension. The whole thing will be destroyed on our way out, + * after we have copied the location string (url) itself (if found). + */ + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) + goto loser; + + authInfoAccess = CERT_DecodeAuthInfoAccessExtension(arena, + encodedAuthInfoAccess); + if (authInfoAccess == NULL) + goto loser; + + for (i = 0; authInfoAccess[i] != NULL; i++) { + if (SECOID_FindOIDTag(&authInfoAccess[i]->method) == SEC_OID_PKIX_OCSP) + locname = authInfoAccess[i]->location; + } + + /* + * If we found an AIA extension, but it did not include an OCSP method, + * that should look to our caller as if we did not find the extension + * at all, because it is only an OCSP method that we care about. + * So set the same error that would be set if the AIA extension was + * not there at all. + */ + if (locname == NULL) { + PORT_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION); + goto loser; + } + + /* + * The following is just a pointer back into locname (i.e. not a copy); + * thus it should not be freed. + */ + location = CERT_GetGeneralNameByType(locname, certURI, PR_FALSE); + if (location == NULL) { + /* + * XXX Appears that CERT_GetGeneralNameByType does not set an + * error if there is no name by that type. For lack of anything + * better, act as if the extension was not found. In the future + * this should probably be something more like the extension was + * badly formed. + */ + PORT_SetError(SEC_ERROR_CERT_BAD_ACCESS_LOCATION); + goto loser; + } + + /* + * That location is really a string, but it has a specified length + * without a null-terminator. We need a real string that does have + * a null-terminator, and we need a copy of it anyway to return to + * our caller -- so allocate and copy. + */ + locURI = PORT_Alloc(location->len + 1); + if (locURI == NULL) { + goto loser; + } + PORT_Memcpy(locURI, location->data, location->len); + locURI[location->len] = '\0'; + +loser: + if (arena != NULL) + PORT_FreeArena(arena, PR_FALSE); + + if (encodedAuthInfoAccess != NULL) + SECITEM_FreeItem(encodedAuthInfoAccess, PR_TRUE); + + return locURI; +} + +/* + * Figure out where we should go to find out the status of the given cert + * via OCSP. If allowed to use a default responder uri and a default + * responder is set up, then that is our answer. + * If not, see if the certificate has an Authority Information Access (AIA) + * extension for OCSP, and return the value of that. Otherwise return NULL. + * We also let our caller know whether or not the responder chosen was + * a default responder or not through the output variable isDefault; + * its value has no meaning unless a good (non-null) value is returned + * for the location. + * + * The result needs to be freed (PORT_Free) when no longer in use. + */ +char * +ocsp_GetResponderLocation(CERTCertDBHandle *handle, CERTCertificate *cert, + PRBool canUseDefault, PRBool *isDefault) +{ + ocspCheckingContext *ocspcx = NULL; + char *ocspUrl = NULL; + + if (canUseDefault) { + ocspcx = ocsp_GetCheckingContext(handle); + } + if (ocspcx != NULL && ocspcx->useDefaultResponder) { + /* + * A default responder wins out, if specified. + * XXX Someday this may be a more complicated determination based + * on the cert's issuer. (That is, we could have different default + * responders configured for different issuers.) + */ + PORT_Assert(ocspcx->defaultResponderURI != NULL); + *isDefault = PR_TRUE; + return (PORT_Strdup(ocspcx->defaultResponderURI)); + } + + /* + * No default responder set up, so go see if we can find an AIA + * extension that has a value for OCSP, and get the url from that. + */ + *isDefault = PR_FALSE; + ocspUrl = CERT_GetOCSPAuthorityInfoAccessLocation(cert); + if (!ocspUrl) { + CERT_StringFromCertFcn altFcn; + + PR_EnterMonitor(OCSP_Global.monitor); + altFcn = OCSP_Global.alternateOCSPAIAFcn; + PR_ExitMonitor(OCSP_Global.monitor); + if (altFcn) { + ocspUrl = (*altFcn)(cert); + if (ocspUrl) + *isDefault = PR_TRUE; + } + } + return ocspUrl; +} + +/* + * Return SECSuccess if the cert was revoked *after* "time", + * SECFailure otherwise. + */ +static SECStatus +ocsp_CertRevokedAfter(ocspRevokedInfo *revokedInfo, PRTime time) +{ + PRTime revokedTime; + SECStatus rv; + + rv = DER_GeneralizedTimeToTime(&revokedTime, &revokedInfo->revocationTime); + if (rv != SECSuccess) + return rv; + + /* + * Set the error even if we will return success; someone might care. + */ + PORT_SetError(SEC_ERROR_REVOKED_CERTIFICATE); + + if (LL_CMP(revokedTime, >, time)) + return SECSuccess; + + return SECFailure; +} + +/* + * See if the cert represented in the single response had a good status + * at the specified time. + */ +SECStatus +ocsp_CertHasGoodStatus(ocspCertStatus *status, PRTime time) +{ + SECStatus rv; + switch (status->certStatusType) { + case ocspCertStatus_good: + rv = SECSuccess; + break; + case ocspCertStatus_revoked: + rv = ocsp_CertRevokedAfter(status->certStatusInfo.revokedInfo, time); + break; + case ocspCertStatus_unknown: + PORT_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT); + rv = SECFailure; + break; + case ocspCertStatus_other: + default: + PORT_Assert(0); + PORT_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE); + rv = SECFailure; + break; + } + return rv; +} + +static SECStatus +ocsp_SingleResponseCertHasGoodStatus(CERTOCSPSingleResponse *single, + PRTime time) +{ + return ocsp_CertHasGoodStatus(single->certStatus, time); +} + +/* SECFailure means the arguments were invalid. + * On SECSuccess, the out parameters contain the OCSP status. + * rvOcsp contains the overall result of the OCSP operation. + * Depending on input parameter ignoreGlobalOcspFailureSetting, + * a soft failure might be converted into *rvOcsp=SECSuccess. + * If the cached attempt to obtain OCSP information had resulted + * in a failure, missingResponseError shows the error code of + * that failure. + * cacheFreshness is ocspMissing if no entry was found, + * ocspFresh if a fresh entry was found, or + * ocspStale if a stale entry was found. + */ +SECStatus +ocsp_GetCachedOCSPResponseStatus(CERTOCSPCertID *certID, + PRTime time, + PRBool ignoreGlobalOcspFailureSetting, + SECStatus *rvOcsp, + SECErrorCodes *missingResponseError, + OCSPFreshness *cacheFreshness) +{ + OCSPCacheItem *cacheItem = NULL; + + if (!certID || !missingResponseError || !rvOcsp || !cacheFreshness) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + *rvOcsp = SECFailure; + *missingResponseError = 0; + *cacheFreshness = ocspMissing; + + PR_EnterMonitor(OCSP_Global.monitor); + cacheItem = ocsp_FindCacheEntry(&OCSP_Global.cache, certID); + if (cacheItem) { + *cacheFreshness = ocsp_IsCacheItemFresh(cacheItem) ? ocspFresh + : ocspStale; + /* having an arena means, we have a cached certStatus */ + if (cacheItem->certStatusArena) { + *rvOcsp = ocsp_CertHasGoodStatus(&cacheItem->certStatus, time); + if (*rvOcsp != SECSuccess) { + *missingResponseError = PORT_GetError(); + } + } else { + /* + * No status cached, the previous attempt failed. + * If OCSP is required, we never decide based on a failed attempt + * However, if OCSP is optional, a recent OCSP failure is + * an allowed good state. + */ + if (*cacheFreshness == ocspFresh && + !ignoreGlobalOcspFailureSetting && + OCSP_Global.ocspFailureMode == + ocspMode_FailureIsNotAVerificationFailure) { + *rvOcsp = SECSuccess; + } + *missingResponseError = cacheItem->missingResponseError; + } + } + PR_ExitMonitor(OCSP_Global.monitor); + return SECSuccess; +} + +PRBool +ocsp_FetchingFailureIsVerificationFailure(void) +{ + PRBool isFailure; + + PR_EnterMonitor(OCSP_Global.monitor); + isFailure = + OCSP_Global.ocspFailureMode == ocspMode_FailureIsVerificationFailure; + PR_ExitMonitor(OCSP_Global.monitor); + return isFailure; +} + +/* + * FUNCTION: CERT_CheckOCSPStatus + * Checks the status of a certificate via OCSP. Will only check status for + * a certificate that has an AIA (Authority Information Access) extension + * for OCSP *or* when a "default responder" is specified and enabled. + * (If no AIA extension for OCSP and no default responder in place, the + * cert is considered to have a good status and SECSuccess is returned.) + * INPUTS: + * CERTCertDBHandle *handle + * certificate DB of the cert that is being checked + * CERTCertificate *cert + * the certificate being checked + * XXX in the long term also need a boolean parameter that specifies + * whether to check the cert chain, as well; for now we check only + * the leaf (the specified certificate) + * PRTime time + * time for which status is to be determined + * void *pwArg + * argument for password prompting, if needed + * RETURN: + * Returns SECSuccess if an approved OCSP responder "knows" the cert + * *and* returns a non-revoked status for it; SECFailure otherwise, + * with an error set describing the reason: + * + * SEC_ERROR_OCSP_BAD_HTTP_RESPONSE + * SEC_ERROR_OCSP_FUTURE_RESPONSE + * SEC_ERROR_OCSP_MALFORMED_REQUEST + * SEC_ERROR_OCSP_MALFORMED_RESPONSE + * SEC_ERROR_OCSP_OLD_RESPONSE + * SEC_ERROR_OCSP_REQUEST_NEEDS_SIG + * SEC_ERROR_OCSP_SERVER_ERROR + * SEC_ERROR_OCSP_TRY_SERVER_LATER + * SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST + * SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE + * SEC_ERROR_OCSP_UNKNOWN_CERT + * SEC_ERROR_OCSP_UNKNOWN_RESPONSE_STATUS + * SEC_ERROR_OCSP_UNKNOWN_RESPONSE_TYPE + * + * SEC_ERROR_BAD_SIGNATURE + * SEC_ERROR_CERT_BAD_ACCESS_LOCATION + * SEC_ERROR_INVALID_TIME + * SEC_ERROR_REVOKED_CERTIFICATE + * SEC_ERROR_UNKNOWN_ISSUER + * SEC_ERROR_UNKNOWN_SIGNER + * + * Other errors are any of the many possible failures in cert verification + * (e.g. SEC_ERROR_REVOKED_CERTIFICATE, SEC_ERROR_UNTRUSTED_ISSUER) when + * verifying the signer's cert, or low-level problems (error allocating + * memory, error performing ASN.1 decoding, etc.). + */ +SECStatus +CERT_CheckOCSPStatus(CERTCertDBHandle *handle, CERTCertificate *cert, + PRTime time, void *pwArg) +{ + CERTOCSPCertID *certID; + PRBool certIDWasConsumed = PR_FALSE; + SECStatus rv; + SECStatus rvOcsp; + SECErrorCodes cachedErrorCode; + OCSPFreshness cachedResponseFreshness; + + OCSP_TRACE_CERT(cert); + OCSP_TRACE_TIME("## requested validity time:", time); + + certID = CERT_CreateOCSPCertID(cert, time); + if (!certID) + return SECFailure; + rv = ocsp_GetCachedOCSPResponseStatus( + certID, time, PR_FALSE, /* ignoreGlobalOcspFailureSetting */ + &rvOcsp, &cachedErrorCode, &cachedResponseFreshness); + if (rv != SECSuccess) { + CERT_DestroyOCSPCertID(certID); + return SECFailure; + } + if (cachedResponseFreshness == ocspFresh) { + CERT_DestroyOCSPCertID(certID); + if (rvOcsp != SECSuccess) { + PORT_SetError(cachedErrorCode); + } + return rvOcsp; + } + + rv = ocsp_GetOCSPStatusFromNetwork(handle, certID, cert, time, pwArg, + &certIDWasConsumed, + &rvOcsp); + if (rv != SECSuccess) { + PRErrorCode err = PORT_GetError(); + if (ocsp_FetchingFailureIsVerificationFailure()) { + PORT_SetError(err); + rvOcsp = SECFailure; + } else if (cachedResponseFreshness == ocspStale && + (cachedErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT || + cachedErrorCode == SEC_ERROR_REVOKED_CERTIFICATE)) { + /* If we couldn't get a response for a certificate that the OCSP + * responder previously told us was bad, then assume it is still + * bad until we hear otherwise, as it is very unlikely that the + * certificate status has changed from "revoked" to "good" and it + * is also unlikely that the certificate status has changed from + * "unknown" to "good", except for some buggy OCSP responders. + */ + PORT_SetError(cachedErrorCode); + rvOcsp = SECFailure; + } else { + rvOcsp = SECSuccess; + } + } + if (!certIDWasConsumed) { + CERT_DestroyOCSPCertID(certID); + } + return rvOcsp; +} + +/* + * FUNCTION: CERT_CacheOCSPResponseFromSideChannel + * First, this function checks the OCSP cache to see if a good response + * for the given certificate already exists. If it does, then the function + * returns successfully. + * + * If not, then it validates that the given OCSP response is a valid, + * good response for the given certificate and inserts it into the + * cache. + * + * This function is intended for use when OCSP responses are provided via a + * side-channel, i.e. TLS OCSP stapling (a.k.a. the status_request extension). + * + * INPUTS: + * CERTCertDBHandle *handle + * certificate DB of the cert that is being checked + * CERTCertificate *cert + * the certificate being checked + * PRTime time + * time for which status is to be determined + * SECItem *encodedResponse + * the DER encoded bytes of the OCSP response + * void *pwArg + * argument for password prompting, if needed + * RETURN: + * SECSuccess if the cert was found in the cache, or if the OCSP response was + * found to be valid and inserted into the cache. SECFailure otherwise. + */ +SECStatus +CERT_CacheOCSPResponseFromSideChannel(CERTCertDBHandle *handle, + CERTCertificate *cert, + PRTime time, + const SECItem *encodedResponse, + void *pwArg) +{ + CERTOCSPCertID *certID = NULL; + PRBool certIDWasConsumed = PR_FALSE; + SECStatus rv = SECFailure; + SECStatus rvOcsp = SECFailure; + SECErrorCodes dummy_error_code; /* we ignore this */ + CERTOCSPResponse *decodedResponse = NULL; + CERTOCSPSingleResponse *singleResponse = NULL; + OCSPFreshness freshness; + + /* The OCSP cache can be in three states regarding this certificate: + * + Good (cached, timely, 'good' response, or revoked in the future) + * + Revoked (cached, timely, but doesn't fit in the last category) + * + Miss (no knowledge) + * + * Likewise, the side-channel information can be + * + Good (timely, 'good' response, or revoked in the future) + * + Revoked (timely, but doesn't fit in the last category) + * + Invalid (bad syntax, bad signature, not timely etc) + * + * The common case is that the cache result is Good and so is the + * side-channel information. We want to save processing time in this case + * so we say that any time we see a Good result from the cache we return + * early. + * + * Cache result + * | Good Revoked Miss + * ---+-------------------------------------------- + * G | noop Cache more Cache it + * S | recent result + * i | + * d | + * e | + * R | noop Cache more Cache it + * C | recent result + * h | + * a | + * n | + * n I | noop Noop Noop + * e | + * l | + * + * When we fetch from the network we might choose to cache a negative + * result when the response is invalid. This saves us hammering, uselessly, + * at a broken responder. However, side channels are commonly attacker + * controlled and so we must not cache a negative result for an Invalid + * side channel. + */ + + if (!cert || !encodedResponse) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + certID = CERT_CreateOCSPCertID(cert, time); + if (!certID) + return SECFailure; + + /* We pass PR_TRUE for ignoreGlobalOcspFailureSetting so that a cached + * error entry is not interpreted as being a 'Good' entry here. + */ + rv = ocsp_GetCachedOCSPResponseStatus( + certID, time, PR_TRUE, /* ignoreGlobalOcspFailureSetting */ + &rvOcsp, &dummy_error_code, &freshness); + if (rv == SECSuccess && rvOcsp == SECSuccess && freshness == ocspFresh) { + /* The cached value is good. We don't want to waste time validating + * this OCSP response. This is the first column in the table above. */ + CERT_DestroyOCSPCertID(certID); + return rv; + } + + /* The logic for caching the more recent response is handled in + * ocsp_CacheSingleResponse. */ + + rv = ocsp_GetDecodedVerifiedSingleResponseForID(handle, certID, cert, + time, pwArg, + encodedResponse, + &decodedResponse, + &singleResponse); + if (rv == SECSuccess) { + rvOcsp = ocsp_SingleResponseCertHasGoodStatus(singleResponse, time); + /* Cache any valid singleResponse, regardless of status. */ + ocsp_CacheSingleResponse(certID, singleResponse, &certIDWasConsumed); + } + if (decodedResponse) { + CERT_DestroyOCSPResponse(decodedResponse); + } + if (!certIDWasConsumed) { + CERT_DestroyOCSPCertID(certID); + } + return rv == SECSuccess ? rvOcsp : rv; +} + +/* + * Status in *certIDWasConsumed will always be correct, regardless of + * return value. + */ +static SECStatus +ocsp_GetOCSPStatusFromNetwork(CERTCertDBHandle *handle, + CERTOCSPCertID *certID, + CERTCertificate *cert, + PRTime time, + void *pwArg, + PRBool *certIDWasConsumed, + SECStatus *rv_ocsp) +{ + char *location = NULL; + PRBool locationIsDefault; + SECItem *encodedResponse = NULL; + CERTOCSPRequest *request = NULL; + SECStatus rv = SECFailure; + + CERTOCSPResponse *decodedResponse = NULL; + CERTOCSPSingleResponse *singleResponse = NULL; + enum { stageGET, + stagePOST } currentStage; + PRBool retry = PR_FALSE; + + if (!certIDWasConsumed || !rv_ocsp) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + *certIDWasConsumed = PR_FALSE; + *rv_ocsp = SECFailure; + + if (!OCSP_Global.monitor) { + PORT_SetError(SEC_ERROR_NOT_INITIALIZED); + return SECFailure; + } + PR_EnterMonitor(OCSP_Global.monitor); + if (OCSP_Global.forcePost) { + currentStage = stagePOST; + } else { + currentStage = stageGET; + } + PR_ExitMonitor(OCSP_Global.monitor); + + /* + * The first thing we need to do is find the location of the responder. + * This will be the value of the default responder (if enabled), else + * it will come out of the AIA extension in the cert (if present). + * If we have no such location, then this cert does not "deserve" to + * be checked -- that is, we consider it a success and just return. + * The way we tell that is by looking at the error number to see if + * the problem was no AIA extension was found; any other error was + * a true failure that we unfortunately have to treat as an overall + * failure here. + */ + location = ocsp_GetResponderLocation(handle, cert, PR_TRUE, + &locationIsDefault); + if (location == NULL) { + int err = PORT_GetError(); + if (err == SEC_ERROR_EXTENSION_NOT_FOUND || + err == SEC_ERROR_CERT_BAD_ACCESS_LOCATION) { + PORT_SetError(0); + *rv_ocsp = SECSuccess; + return SECSuccess; + } + return SECFailure; + } + + /* + * XXX In the fullness of time, we will want/need to handle a + * certificate chain. This will be done either when a new parameter + * tells us to, or some configuration variable tells us to. In any + * case, handling it is complicated because we may need to send as + * many requests (and receive as many responses) as we have certs + * in the chain. If we are going to talk to a default responder, + * and we only support one default responder, we can put all of the + * certs together into one request. Otherwise, we must break them up + * into multiple requests. (Even if all of the requests will go to + * the same location, the signature on each response will be different, + * because each issuer is different. Carefully read the OCSP spec + * if you do not understand this.) + */ + + /* + * XXX If/when signing of requests is supported, that second NULL + * should be changed to be the signer certificate. Not sure if that + * should be passed into this function or retrieved via some operation + * on the handle/context. + */ + + do { + const char *method; + PRBool validResponseWithAccurateInfo = PR_FALSE; + retry = PR_FALSE; + *rv_ocsp = SECFailure; + + if (currentStage == stageGET) { + method = "GET"; + } else { + PORT_Assert(currentStage == stagePOST); + method = "POST"; + } + + encodedResponse = + ocsp_GetEncodedOCSPResponseForSingleCert(NULL, certID, cert, + location, method, + time, locationIsDefault, + pwArg, &request); + + if (encodedResponse) { + rv = ocsp_GetDecodedVerifiedSingleResponseForID(handle, certID, cert, + time, pwArg, + encodedResponse, + &decodedResponse, + &singleResponse); + if (rv == SECSuccess) { + switch (singleResponse->certStatus->certStatusType) { + case ocspCertStatus_good: + case ocspCertStatus_revoked: + validResponseWithAccurateInfo = PR_TRUE; + break; + default: + break; + } + *rv_ocsp = ocsp_SingleResponseCertHasGoodStatus(singleResponse, time); + } + } + + if (currentStage == stageGET) { + /* only accept GET response if good or revoked */ + if (validResponseWithAccurateInfo) { + ocsp_CacheSingleResponse(certID, singleResponse, + certIDWasConsumed); + } else { + retry = PR_TRUE; + currentStage = stagePOST; + } + } else { + /* cache the POST respone, regardless of status */ + if (!singleResponse) { + cert_RememberOCSPProcessingFailure(certID, certIDWasConsumed); + } else { + ocsp_CacheSingleResponse(certID, singleResponse, + certIDWasConsumed); + } + } + + if (encodedResponse) { + SECITEM_FreeItem(encodedResponse, PR_TRUE); + encodedResponse = NULL; + } + if (request) { + CERT_DestroyOCSPRequest(request); + request = NULL; + } + if (decodedResponse) { + CERT_DestroyOCSPResponse(decodedResponse); + decodedResponse = NULL; + } + singleResponse = NULL; + + } while (retry); + + PORT_Free(location); + return rv; +} + +/* + * FUNCTION: ocsp_GetDecodedVerifiedSingleResponseForID + * This function decodes an OCSP response and checks for a valid response + * concerning the given certificate. + * + * Note: a 'valid' response is one that parses successfully, is not an OCSP + * exception (see RFC 2560 Section 2.3), is correctly signed and is current. + * A 'good' response is a valid response that attests that the certificate + * is not currently revoked (see RFC 2560 Section 2.2). + * + * INPUTS: + * CERTCertDBHandle *handle + * certificate DB of the cert that is being checked + * CERTOCSPCertID *certID + * the cert ID corresponding to |cert| + * CERTCertificate *cert + * the certificate being checked + * PRTime time + * time for which status is to be determined + * void *pwArg + * the opaque argument to the password prompting function. + * SECItem *encodedResponse + * the DER encoded bytes of the OCSP response + * CERTOCSPResponse **pDecodedResponse + * (output) The caller must ALWAYS check for this output parameter, + * and if it's non-null, must destroy it using CERT_DestroyOCSPResponse. + * CERTOCSPSingleResponse **pSingle + * (output) on success, this points to the single response that corresponds + * to the certID parameter. Points to the inside of pDecodedResponse. + * It isn't a copy, don't free it. + * RETURN: + * SECSuccess iff the response is valid. + */ +static SECStatus +ocsp_GetDecodedVerifiedSingleResponseForID(CERTCertDBHandle *handle, + CERTOCSPCertID *certID, + CERTCertificate *cert, + PRTime time, + void *pwArg, + const SECItem *encodedResponse, + CERTOCSPResponse **pDecodedResponse, + CERTOCSPSingleResponse **pSingle) +{ + CERTCertificate *signerCert = NULL; + CERTCertificate *issuerCert = NULL; + SECStatus rv = SECFailure; + + if (!pSingle || !pDecodedResponse) { + return SECFailure; + } + *pSingle = NULL; + *pDecodedResponse = CERT_DecodeOCSPResponse(encodedResponse); + if (!*pDecodedResponse) { + return SECFailure; + } + + /* + * Okay, we at least have a response that *looks* like a response! + * Now see if the overall response status value is good or not. + * If not, we set an error and give up. (It means that either the + * server had a problem, or it didn't like something about our + * request. Either way there is nothing to do but give up.) + * Otherwise, we continue to find the actual per-cert status + * in the response. + */ + if (CERT_GetOCSPResponseStatus(*pDecodedResponse) != SECSuccess) { + goto loser; + } + + /* + * If we've made it this far, we expect a response with a good signature. + * So, check for that. + */ + issuerCert = CERT_FindCertIssuer(cert, time, certUsageAnyCA); + rv = CERT_VerifyOCSPResponseSignature(*pDecodedResponse, handle, pwArg, + &signerCert, issuerCert); + if (rv != SECSuccess) { + goto loser; + } + + PORT_Assert(signerCert != NULL); /* internal consistency check */ + /* XXX probably should set error, return failure if signerCert is null */ + + /* + * Again, we are only doing one request for one cert. + * XXX When we handle cert chains, the following code will obviously + * have to be modified, in coordation with the code above that will + * have to determine how to make multiple requests, etc. + */ + rv = ocsp_GetVerifiedSingleResponseForCertID(handle, *pDecodedResponse, certID, + signerCert, time, pSingle); +loser: + if (issuerCert != NULL) + CERT_DestroyCertificate(issuerCert); + if (signerCert != NULL) + CERT_DestroyCertificate(signerCert); + return rv; +} + +/* + * FUNCTION: ocsp_CacheSingleResponse + * This function requires that the caller has checked that the response + * is valid and verified. + * The (positive or negative) valid response will be used to update the cache. + * INPUTS: + * CERTOCSPCertID *certID + * the cert ID corresponding to |cert| + * PRBool *certIDWasConsumed + * (output) on return, this is true iff |certID| was consumed by this + * function. + */ +void +ocsp_CacheSingleResponse(CERTOCSPCertID *certID, + CERTOCSPSingleResponse *single, + PRBool *certIDWasConsumed) +{ + if (single != NULL) { + PR_EnterMonitor(OCSP_Global.monitor); + if (OCSP_Global.maxCacheEntries >= 0) { + ocsp_CreateOrUpdateCacheEntry(&OCSP_Global.cache, certID, single, + certIDWasConsumed); + /* ignore cache update failures */ + } + PR_ExitMonitor(OCSP_Global.monitor); + } +} + +SECStatus +ocsp_GetVerifiedSingleResponseForCertID(CERTCertDBHandle *handle, + CERTOCSPResponse *response, + CERTOCSPCertID *certID, + CERTCertificate *signerCert, + PRTime time, + CERTOCSPSingleResponse + **pSingleResponse) +{ + SECStatus rv; + ocspResponseData *responseData; + PRTime producedAt; + CERTOCSPSingleResponse *single; + + /* + * The ResponseData part is the real guts of the response. + */ + responseData = ocsp_GetResponseData(response, NULL); + if (responseData == NULL) { + rv = SECFailure; + goto loser; + } + + /* + * There is one producedAt time for the entire response (and a separate + * thisUpdate time for each individual single response). We need to + * compare them, so get the overall time to pass into the check of each + * single response. + */ + rv = DER_GeneralizedTimeToTime(&producedAt, &responseData->producedAt); + if (rv != SECSuccess) + goto loser; + + single = ocsp_GetSingleResponseForCertID(responseData->responses, + handle, certID); + if (single == NULL) { + rv = SECFailure; + goto loser; + } + + rv = ocsp_VerifySingleResponse(single, handle, signerCert, producedAt); + if (rv != SECSuccess) + goto loser; + *pSingleResponse = single; + +loser: + return rv; +} + +SECStatus +CERT_GetOCSPStatusForCertID(CERTCertDBHandle *handle, + CERTOCSPResponse *response, + CERTOCSPCertID *certID, + CERTCertificate *signerCert, + PRTime time) +{ + /* + * We do not update the cache, because: + * + * CERT_GetOCSPStatusForCertID is an old exported API that was introduced + * before the OCSP cache got implemented. + * + * The implementation of helper function cert_ProcessOCSPResponse + * requires the ability to transfer ownership of the the given certID to + * the cache. The external API doesn't allow us to prevent the caller from + * destroying the certID. We don't have the original certificate available, + * therefore we are unable to produce another certID object (that could + * be stored in the cache). + * + * Should we ever implement code to produce a deep copy of certID, + * then this could be changed to allow updating the cache. + * The duplication would have to be done in + * cert_ProcessOCSPResponse, if the out parameter to indicate + * a transfer of ownership is NULL. + */ + return cert_ProcessOCSPResponse(handle, response, certID, + signerCert, time, + NULL, NULL); +} + +/* + * The first 5 parameters match the definition of CERT_GetOCSPStatusForCertID. + */ +SECStatus +cert_ProcessOCSPResponse(CERTCertDBHandle *handle, + CERTOCSPResponse *response, + CERTOCSPCertID *certID, + CERTCertificate *signerCert, + PRTime time, + PRBool *certIDWasConsumed, + SECStatus *cacheUpdateStatus) +{ + SECStatus rv; + SECStatus rv_cache = SECSuccess; + CERTOCSPSingleResponse *single = NULL; + + rv = ocsp_GetVerifiedSingleResponseForCertID(handle, response, certID, + signerCert, time, &single); + if (rv == SECSuccess) { + /* + * Check whether the status says revoked, and if so + * how that compares to the time value passed into this routine. + */ + rv = ocsp_SingleResponseCertHasGoodStatus(single, time); + } + + if (certIDWasConsumed) { + /* + * We don't have copy-of-certid implemented. In order to update + * the cache, the caller must supply an out variable + * certIDWasConsumed, allowing us to return ownership status. + */ + + PR_EnterMonitor(OCSP_Global.monitor); + if (OCSP_Global.maxCacheEntries >= 0) { + /* single == NULL means: remember response failure */ + rv_cache = + ocsp_CreateOrUpdateCacheEntry(&OCSP_Global.cache, certID, + single, certIDWasConsumed); + } + PR_ExitMonitor(OCSP_Global.monitor); + if (cacheUpdateStatus) { + *cacheUpdateStatus = rv_cache; + } + } + + return rv; +} + +SECStatus +cert_RememberOCSPProcessingFailure(CERTOCSPCertID *certID, + PRBool *certIDWasConsumed) +{ + SECStatus rv = SECSuccess; + PR_EnterMonitor(OCSP_Global.monitor); + if (OCSP_Global.maxCacheEntries >= 0) { + rv = ocsp_CreateOrUpdateCacheEntry(&OCSP_Global.cache, certID, NULL, + certIDWasConsumed); + } + PR_ExitMonitor(OCSP_Global.monitor); + return rv; +} + +/* + * Disable status checking and destroy related structures/data. + */ +static SECStatus +ocsp_DestroyStatusChecking(CERTStatusConfig *statusConfig) +{ + ocspCheckingContext *statusContext; + + /* + * Disable OCSP checking + */ + statusConfig->statusChecker = NULL; + + statusContext = statusConfig->statusContext; + PORT_Assert(statusContext != NULL); + if (statusContext == NULL) + return SECFailure; + + if (statusContext->defaultResponderURI != NULL) + PORT_Free(statusContext->defaultResponderURI); + if (statusContext->defaultResponderNickname != NULL) + PORT_Free(statusContext->defaultResponderNickname); + + PORT_Free(statusContext); + statusConfig->statusContext = NULL; + + PORT_Free(statusConfig); + + return SECSuccess; +} + +/* + * FUNCTION: CERT_DisableOCSPChecking + * Turns off OCSP checking for the given certificate database. + * This routine disables OCSP checking. Though it will return + * SECFailure if OCSP checking is not enabled, it is "safe" to + * call it that way and just ignore the return value, if it is + * easier to just call it than to "remember" whether it is enabled. + * INPUTS: + * CERTCertDBHandle *handle + * Certificate database for which OCSP checking will be disabled. + * RETURN: + * Returns SECFailure if an error occurred (usually means that OCSP + * checking was not enabled or status contexts were not initialized -- + * error set will be SEC_ERROR_OCSP_NOT_ENABLED); SECSuccess otherwise. + */ +SECStatus +CERT_DisableOCSPChecking(CERTCertDBHandle *handle) +{ + CERTStatusConfig *statusConfig; + ocspCheckingContext *statusContext; + + if (handle == NULL) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + statusConfig = CERT_GetStatusConfig(handle); + statusContext = ocsp_GetCheckingContext(handle); + if (statusContext == NULL) + return SECFailure; + + if (statusConfig->statusChecker != CERT_CheckOCSPStatus) { + /* + * Status configuration is present, but either not currently + * enabled or not for OCSP. + */ + PORT_SetError(SEC_ERROR_OCSP_NOT_ENABLED); + return SECFailure; + } + + /* cache no longer necessary */ + CERT_ClearOCSPCache(); + + /* + * This is how we disable status checking. Everything else remains + * in place in case we are enabled again. + */ + statusConfig->statusChecker = NULL; + + return SECSuccess; +} + +/* + * Allocate and initialize the informational structures for status checking. + * This is done when some configuration of OCSP is being done or when OCSP + * checking is being turned on, whichever comes first. + */ +static SECStatus +ocsp_InitStatusChecking(CERTCertDBHandle *handle) +{ + CERTStatusConfig *statusConfig = NULL; + ocspCheckingContext *statusContext = NULL; + + PORT_Assert(CERT_GetStatusConfig(handle) == NULL); + if (CERT_GetStatusConfig(handle) != NULL) { + /* XXX or call statusConfig->statusDestroy and continue? */ + return SECFailure; + } + + statusConfig = PORT_ZNew(CERTStatusConfig); + if (statusConfig == NULL) + goto loser; + + statusContext = PORT_ZNew(ocspCheckingContext); + if (statusContext == NULL) + goto loser; + + statusConfig->statusDestroy = ocsp_DestroyStatusChecking; + statusConfig->statusContext = statusContext; + + CERT_SetStatusConfig(handle, statusConfig); + + return SECSuccess; + +loser: + if (statusConfig != NULL) + PORT_Free(statusConfig); + return SECFailure; +} + +/* + * FUNCTION: CERT_EnableOCSPChecking + * Turns on OCSP checking for the given certificate database. + * INPUTS: + * CERTCertDBHandle *handle + * Certificate database for which OCSP checking will be enabled. + * RETURN: + * Returns SECFailure if an error occurred (likely only problem + * allocating memory); SECSuccess otherwise. + */ +SECStatus +CERT_EnableOCSPChecking(CERTCertDBHandle *handle) +{ + CERTStatusConfig *statusConfig; + + SECStatus rv; + + if (handle == NULL) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + statusConfig = CERT_GetStatusConfig(handle); + if (statusConfig == NULL) { + rv = ocsp_InitStatusChecking(handle); + if (rv != SECSuccess) + return rv; + + /* Get newly established value */ + statusConfig = CERT_GetStatusConfig(handle); + PORT_Assert(statusConfig != NULL); + } + + /* + * Setting the checker function is what really enables the checking + * when each cert verification is done. + */ + statusConfig->statusChecker = CERT_CheckOCSPStatus; + + return SECSuccess; +} + +/* + * FUNCTION: CERT_SetOCSPDefaultResponder + * Specify the location and cert of the default responder. + * If OCSP checking is already enabled *and* use of a default responder + * is also already enabled, all OCSP checking from now on will go directly + * to the specified responder. If OCSP checking is not enabled, or if + * it is but use of a default responder is not enabled, the information + * will be recorded and take effect whenever both are enabled. + * INPUTS: + * CERTCertDBHandle *handle + * Cert database on which OCSP checking should use the default responder. + * char *url + * The location of the default responder (e.g. "http://foo.com:80/ocsp") + * Note that the location will not be tested until the first attempt + * to send a request there. + * char *name + * The nickname of the cert to trust (expected) to sign the OCSP responses. + * If the corresponding cert cannot be found, SECFailure is returned. + * RETURN: + * Returns SECFailure if an error occurred; SECSuccess otherwise. + * The most likely error is that the cert for "name" could not be found + * (probably SEC_ERROR_UNKNOWN_CERT). Other errors are low-level (no memory, + * bad database, etc.). + */ +SECStatus +CERT_SetOCSPDefaultResponder(CERTCertDBHandle *handle, + const char *url, const char *name) +{ + CERTCertificate *cert; + ocspCheckingContext *statusContext; + char *url_copy = NULL; + char *name_copy = NULL; + SECStatus rv; + + if (handle == NULL || url == NULL || name == NULL) { + /* + * XXX When interface is exported, probably want better errors; + * perhaps different one for each parameter. + */ + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + /* + * Find the certificate for the specified nickname. Do this first + * because it seems the most likely to fail. + * + * XXX Shouldn't need that cast if the FindCertByNickname interface + * used const to convey that it does not modify the name. Maybe someday. + */ + cert = CERT_FindCertByNickname(handle, (char *)name); + if (cert == NULL) { + /* + * look for the cert on an external token. + */ + cert = PK11_FindCertFromNickname((char *)name, NULL); + } + if (cert == NULL) + return SECFailure; + + /* + * Make a copy of the url and nickname. + */ + url_copy = PORT_Strdup(url); + name_copy = PORT_Strdup(name); + if (url_copy == NULL || name_copy == NULL) { + rv = SECFailure; + goto loser; + } + + statusContext = ocsp_GetCheckingContext(handle); + + /* + * Allocate and init the context if it doesn't already exist. + */ + if (statusContext == NULL) { + rv = ocsp_InitStatusChecking(handle); + if (rv != SECSuccess) + goto loser; + + statusContext = ocsp_GetCheckingContext(handle); + PORT_Assert(statusContext != NULL); /* extreme paranoia */ + } + + /* + * Note -- we do not touch the status context until after all of + * the steps which could cause errors. If something goes wrong, + * we want to leave things as they were. + */ + + /* + * Get rid of old url and name if there. + */ + if (statusContext->defaultResponderNickname != NULL) + PORT_Free(statusContext->defaultResponderNickname); + if (statusContext->defaultResponderURI != NULL) + PORT_Free(statusContext->defaultResponderURI); + + /* + * And replace them with the new ones. + */ + statusContext->defaultResponderURI = url_copy; + statusContext->defaultResponderNickname = name_copy; + + /* + * If there was already a cert in place, get rid of it and replace it. + * Otherwise, we are not currently enabled, so we don't want to save it; + * it will get re-found and set whenever use of a default responder is + * enabled. + */ + if (statusContext->defaultResponderCert != NULL) { + CERT_DestroyCertificate(statusContext->defaultResponderCert); + statusContext->defaultResponderCert = cert; + /*OCSP enabled, switching responder: clear cache*/ + CERT_ClearOCSPCache(); + } else { + PORT_Assert(statusContext->useDefaultResponder == PR_FALSE); + CERT_DestroyCertificate(cert); + /*OCSP currently not enabled, no need to clear cache*/ + } + + return SECSuccess; + +loser: + CERT_DestroyCertificate(cert); + if (url_copy != NULL) + PORT_Free(url_copy); + if (name_copy != NULL) + PORT_Free(name_copy); + return rv; +} + +/* + * FUNCTION: CERT_EnableOCSPDefaultResponder + * Turns on use of a default responder when OCSP checking. + * If OCSP checking is already enabled, this will make subsequent checks + * go directly to the default responder. (The location of the responder + * and the nickname of the responder cert must already be specified.) + * If OCSP checking is not enabled, this will be recorded and take effect + * whenever it is enabled. + * INPUTS: + * CERTCertDBHandle *handle + * Cert database on which OCSP checking should use the default responder. + * RETURN: + * Returns SECFailure if an error occurred; SECSuccess otherwise. + * No errors are especially likely unless the caller did not previously + * perform a successful call to SetOCSPDefaultResponder (in which case + * the error set will be SEC_ERROR_OCSP_NO_DEFAULT_RESPONDER). + */ +SECStatus +CERT_EnableOCSPDefaultResponder(CERTCertDBHandle *handle) +{ + ocspCheckingContext *statusContext; + CERTCertificate *cert; + SECStatus rv; + SECCertificateUsage usage; + + if (handle == NULL) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + statusContext = ocsp_GetCheckingContext(handle); + + if (statusContext == NULL) { + /* + * Strictly speaking, the error already set is "correct", + * but cover over it with one more helpful in this context. + */ + PORT_SetError(SEC_ERROR_OCSP_NO_DEFAULT_RESPONDER); + return SECFailure; + } + + if (statusContext->defaultResponderURI == NULL) { + PORT_SetError(SEC_ERROR_OCSP_NO_DEFAULT_RESPONDER); + return SECFailure; + } + + if (statusContext->defaultResponderNickname == NULL) { + PORT_SetError(SEC_ERROR_OCSP_NO_DEFAULT_RESPONDER); + return SECFailure; + } + + /* + * Find the cert for the nickname. + */ + cert = CERT_FindCertByNickname(handle, + statusContext->defaultResponderNickname); + if (cert == NULL) { + cert = PK11_FindCertFromNickname(statusContext->defaultResponderNickname, + NULL); + } + /* + * We should never have trouble finding the cert, because its + * existence should have been proven by SetOCSPDefaultResponder. + */ + PORT_Assert(cert != NULL); + if (cert == NULL) + return SECFailure; + + /* + * Supplied cert should at least have a signing capability in order for us + * to use it as a trusted responder cert. Ability to sign is guaranteed if + * cert is validated to have any set of the usages below. + */ + rv = CERT_VerifyCertificateNow(handle, cert, PR_TRUE, + certificateUsageCheckAllUsages, + NULL, &usage); + if (rv != SECSuccess || (usage & (certificateUsageSSLClient | certificateUsageSSLServer | certificateUsageSSLServerWithStepUp | certificateUsageEmailSigner | certificateUsageObjectSigner | certificateUsageStatusResponder | certificateUsageSSLCA)) == 0) { + PORT_SetError(SEC_ERROR_OCSP_RESPONDER_CERT_INVALID); + return SECFailure; + } + + /* + * And hang onto it. + */ + statusContext->defaultResponderCert = cert; + + /* we don't allow a mix of cache entries from different responders */ + CERT_ClearOCSPCache(); + + /* + * Finally, record the fact that we now have a default responder enabled. + */ + statusContext->useDefaultResponder = PR_TRUE; + return SECSuccess; +} + +/* + * FUNCTION: CERT_DisableOCSPDefaultResponder + * Turns off use of a default responder when OCSP checking. + * (Does nothing if use of a default responder is not enabled.) + * INPUTS: + * CERTCertDBHandle *handle + * Cert database on which OCSP checking should stop using a default + * responder. + * RETURN: + * Returns SECFailure if an error occurred; SECSuccess otherwise. + * Errors very unlikely (like random memory corruption...). + */ +SECStatus +CERT_DisableOCSPDefaultResponder(CERTCertDBHandle *handle) +{ + CERTStatusConfig *statusConfig; + ocspCheckingContext *statusContext; + CERTCertificate *tmpCert; + + if (handle == NULL) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + statusConfig = CERT_GetStatusConfig(handle); + if (statusConfig == NULL) + return SECSuccess; + + statusContext = ocsp_GetCheckingContext(handle); + PORT_Assert(statusContext != NULL); + if (statusContext == NULL) + return SECFailure; + + tmpCert = statusContext->defaultResponderCert; + if (tmpCert) { + statusContext->defaultResponderCert = NULL; + CERT_DestroyCertificate(tmpCert); + /* we don't allow a mix of cache entries from different responders */ + CERT_ClearOCSPCache(); + } + + /* + * Finally, record the fact. + */ + statusContext->useDefaultResponder = PR_FALSE; + return SECSuccess; +} + +SECStatus +CERT_ForcePostMethodForOCSP(PRBool forcePost) +{ + if (!OCSP_Global.monitor) { + PORT_SetError(SEC_ERROR_NOT_INITIALIZED); + return SECFailure; + } + + PR_EnterMonitor(OCSP_Global.monitor); + OCSP_Global.forcePost = forcePost; + PR_ExitMonitor(OCSP_Global.monitor); + + return SECSuccess; +} + +SECStatus +CERT_GetOCSPResponseStatus(CERTOCSPResponse *response) +{ + PORT_Assert(response); + if (response->statusValue == ocspResponse_successful) + return SECSuccess; + + switch (response->statusValue) { + case ocspResponse_malformedRequest: + PORT_SetError(SEC_ERROR_OCSP_MALFORMED_REQUEST); + break; + case ocspResponse_internalError: + PORT_SetError(SEC_ERROR_OCSP_SERVER_ERROR); + break; + case ocspResponse_tryLater: + PORT_SetError(SEC_ERROR_OCSP_TRY_SERVER_LATER); + break; + case ocspResponse_sigRequired: + /* XXX We *should* retry with a signature, if possible. */ + PORT_SetError(SEC_ERROR_OCSP_REQUEST_NEEDS_SIG); + break; + case ocspResponse_unauthorized: + PORT_SetError(SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST); + break; + case ocspResponse_unused: + default: + PORT_SetError(SEC_ERROR_OCSP_UNKNOWN_RESPONSE_STATUS); + break; + } + return SECFailure; +} diff --git a/security/nss/lib/certhigh/ocsp.h b/security/nss/lib/certhigh/ocsp.h new file mode 100644 index 0000000000..1b94aec2e3 --- /dev/null +++ b/security/nss/lib/certhigh/ocsp.h @@ -0,0 +1,723 @@ +/* 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/. */ + +/* + * Interface to the OCSP implementation. + */ + +#ifndef _OCSP_H_ +#define _OCSP_H_ + +#include "plarena.h" +#include "seccomon.h" +#include "secoidt.h" +#include "keythi.h" +#include "certt.h" +#include "ocspt.h" + +/************************************************************************/ +SEC_BEGIN_PROTOS + +/* + * This function registers the HttpClient with whose functions the + * HttpClientFcn structure has been populated as the default Http + * client. + * + * The function table must be a global object. + * The caller must ensure that NSS will be able to call + * the registered functions for the lifetime of the process. + */ +extern SECStatus +SEC_RegisterDefaultHttpClient(const SEC_HttpClientFcn *fcnTable); + +/* + * This function obtains the HttpClient which has been registered + * by an earlier call to SEC_RegisterDefaultHttpClient. + */ +extern const SEC_HttpClientFcn * +SEC_GetRegisteredHttpClient(void); + +/* + * Sets parameters that control NSS' internal OCSP cache. + * maxCacheEntries, special varlues are: + * -1 disable cache + * 0 unlimited cache entries + * minimumSecondsToNextFetchAttempt: + * whenever an OCSP request was attempted or completed over the network, + * wait at least this number of seconds before trying to fetch again. + * maximumSecondsToNextFetchAttempt: + * this is the maximum age of a cached response we allow, until we try + * to fetch an updated response, even if the OCSP responder expects + * that newer information update will not be available yet. + */ +extern SECStatus +CERT_OCSPCacheSettings(PRInt32 maxCacheEntries, + PRUint32 minimumSecondsToNextFetchAttempt, + PRUint32 maximumSecondsToNextFetchAttempt); + +/* + * Set the desired behaviour on OCSP failures. + * See definition of ocspFailureMode for allowed choices. + */ +extern SECStatus +CERT_SetOCSPFailureMode(SEC_OcspFailureMode ocspFailureMode); + +/* + * Configure the maximum time NSS will wait for an OCSP response. + */ +extern SECStatus +CERT_SetOCSPTimeout(PRUint32 seconds); + +/* + * Removes all items currently stored in the OCSP cache. + */ +extern SECStatus +CERT_ClearOCSPCache(void); + +/* + * FUNCTION: CERT_EnableOCSPChecking + * Turns on OCSP checking for the given certificate database. + * INPUTS: + * CERTCertDBHandle *handle + * Certificate database for which OCSP checking will be enabled. + * RETURN: + * Returns SECFailure if an error occurred (likely only problem + * allocating memory); SECSuccess otherwise. + */ +extern SECStatus +CERT_EnableOCSPChecking(CERTCertDBHandle *handle); + +/* + * FUNCTION: CERT_DisableOCSPChecking + * Turns off OCSP checking for the given certificate database. + * This routine disables OCSP checking. Though it will return + * SECFailure if OCSP checking is not enabled, it is "safe" to + * call it that way and just ignore the return value, if it is + * easier to just call it than to "remember" whether it is enabled. + * INPUTS: + * CERTCertDBHandle *handle + * Certificate database for which OCSP checking will be disabled. + * RETURN: + * Returns SECFailure if an error occurred (usually means that OCSP + * checking was not enabled or status contexts were not initialized -- + * error set will be SEC_ERROR_OCSP_NOT_ENABLED); SECSuccess otherwise. + */ +extern SECStatus +CERT_DisableOCSPChecking(CERTCertDBHandle *handle); + +/* + * FUNCTION: CERT_SetOCSPDefaultResponder + * Specify the location and cert of the default responder. + * If OCSP checking is already enabled *and* use of a default responder + * is also already enabled, all OCSP checking from now on will go directly + * to the specified responder. If OCSP checking is not enabled, or if + * it is but use of a default responder is not enabled, the information + * will be recorded and take effect whenever both are enabled. + * INPUTS: + * CERTCertDBHandle *handle + * Cert database on which OCSP checking should use the default responder. + * const char *url + * The location of the default responder (e.g. "http://foo.com:80/ocsp") + * Note that the location will not be tested until the first attempt + * to send a request there. + * const char *name + * The nickname of the cert to trust (expected) to sign the OCSP responses. + * If the corresponding cert cannot be found, SECFailure is returned. + * RETURN: + * Returns SECFailure if an error occurred; SECSuccess otherwise. + * The most likely error is that the cert for "name" could not be found + * (probably SEC_ERROR_UNKNOWN_CERT). Other errors are low-level (no memory, + * bad database, etc.). + */ +extern SECStatus +CERT_SetOCSPDefaultResponder(CERTCertDBHandle *handle, + const char *url, const char *name); + +/* + * FUNCTION: CERT_EnableOCSPDefaultResponder + * Turns on use of a default responder when OCSP checking. + * If OCSP checking is already enabled, this will make subsequent checks + * go directly to the default responder. (The location of the responder + * and the nickname of the responder cert must already be specified.) + * If OCSP checking is not enabled, this will be recorded and take effect + * whenever it is enabled. + * INPUTS: + * CERTCertDBHandle *handle + * Cert database on which OCSP checking should use the default responder. + * RETURN: + * Returns SECFailure if an error occurred; SECSuccess otherwise. + * No errors are especially likely unless the caller did not previously + * perform a successful call to SetOCSPDefaultResponder (in which case + * the error set will be SEC_ERROR_OCSP_NO_DEFAULT_RESPONDER). + */ +extern SECStatus +CERT_EnableOCSPDefaultResponder(CERTCertDBHandle *handle); + +/* + * FUNCTION: CERT_DisableOCSPDefaultResponder + * Turns off use of a default responder when OCSP checking. + * (Does nothing if use of a default responder is not enabled.) + * INPUTS: + * CERTCertDBHandle *handle + * Cert database on which OCSP checking should stop using a default + * responder. + * RETURN: + * Returns SECFailure if an error occurred; SECSuccess otherwise. + * Errors very unlikely (like random memory corruption...). + */ +extern SECStatus +CERT_DisableOCSPDefaultResponder(CERTCertDBHandle *handle); + +/* If forcePost is set, OCSP requests will only be sent using the HTTP POST + * method. When forcePost is not set, OCSP requests will be sent using the + * HTTP GET method, with a fallback to POST when we fail to receive a response + * and/or when we receive an uncacheable response like "Unknown." + * + * The default is to use GET and fallback to POST. + */ +extern SECStatus CERT_ForcePostMethodForOCSP(PRBool forcePost); + +/* + * ------------------------------------------------------- + * The Functions above are those expected to be used by a client + * providing OCSP status checking along with every cert verification. + * The functions below are for OCSP testing, debugging, or clients + * or servers performing more specialized OCSP tasks. + * ------------------------------------------------------- + */ + +/* + * FUNCTION: CERT_CreateOCSPRequest + * Creates a CERTOCSPRequest, requesting the status of the certs in + * the given list. + * INPUTS: + * CERTCertList *certList + * A list of certs for which status will be requested. + * Note that all of these certificates should have the same issuer, + * or it's expected the response will be signed by a trusted responder. + * If the certs need to be broken up into multiple requests, that + * must be handled by the caller (and thus by having multiple calls + * to this routine), who knows about where the request(s) are being + * sent and whether there are any trusted responders in place. + * PRTime time + * Indicates the time for which the certificate status is to be + * determined -- this may be used in the search for the cert's issuer + * but has no effect on the request itself. + * PRBool addServiceLocator + * If true, the Service Locator extension should be added to the + * single request(s) for each cert. + * CERTCertificate *signerCert + * If non-NULL, means sign the request using this cert. Otherwise, + * do not sign. + * XXX note that request signing is not yet supported; see comment in code + * RETURN: + * A pointer to a CERTOCSPRequest structure containing an OCSP request + * for the cert list. On error, null is returned, with an error set + * indicating the reason. This is likely SEC_ERROR_UNKNOWN_ISSUER. + * (The issuer is needed to create a request for the certificate.) + * Other errors are low-level problems (no memory, bad database, etc.). + */ +extern CERTOCSPRequest * +CERT_CreateOCSPRequest(CERTCertList *certList, PRTime time, + PRBool addServiceLocator, + CERTCertificate *signerCert); + +/* + * FUNCTION: CERT_AddOCSPAcceptableResponses + * Add the AcceptableResponses extension to an OCSP Request. + * INPUTS: + * CERTOCSPRequest *request + * The request to which the extension should be added. + * SECOidTag responseType0, ... + * A list (of one or more) of SECOidTag -- each of the response types + * to be added. The last OID *must* be SEC_OID_PKIX_OCSP_BASIC_RESPONSE. + * (This marks the end of the list, and it must be specified because a + * client conforming to the OCSP standard is required to handle the basic + * response type.) The OIDs are not checked in any way. + * RETURN: + * SECSuccess if the extension is added; SECFailure if anything goes wrong. + * All errors are internal or low-level problems (e.g. no memory). + */ +extern SECStatus +CERT_AddOCSPAcceptableResponses(CERTOCSPRequest *request, + SECOidTag responseType0, ...); + +/* + * FUNCTION: CERT_EncodeOCSPRequest + * DER encodes an OCSP Request, possibly adding a signature as well. + * XXX Signing is not yet supported, however; see comments in code. + * INPUTS: + * PLArenaPool *arena + * The return value is allocated from here. + * If a NULL is passed in, allocation is done from the heap instead. + * CERTOCSPRequest *request + * The request to be encoded. + * void *pwArg + * Pointer to argument for password prompting, if needed. (Definitely + * not needed if not signing.) + * RETURN: + * Returns a NULL on error and a pointer to the SECItem with the + * encoded value otherwise. Any error is likely to be low-level + * (e.g. no memory). + */ +extern SECItem * +CERT_EncodeOCSPRequest(PLArenaPool *arena, CERTOCSPRequest *request, + void *pwArg); + +/* + * FUNCTION: CERT_DecodeOCSPRequest + * Decode a DER encoded OCSP Request. + * INPUTS: + * SECItem *src + * Pointer to a SECItem holding DER encoded OCSP Request. + * RETURN: + * Returns a pointer to a CERTOCSPRequest containing the decoded request. + * On error, returns NULL. Most likely error is trouble decoding + * (SEC_ERROR_OCSP_MALFORMED_REQUEST), or low-level problem (no memory). + */ +extern CERTOCSPRequest * +CERT_DecodeOCSPRequest(const SECItem *src); + +/* + * FUNCTION: CERT_DestroyOCSPRequest + * Frees an OCSP Request structure. + * INPUTS: + * CERTOCSPRequest *request + * Pointer to CERTOCSPRequest to be freed. + * RETURN: + * No return value; no errors. + */ +extern void +CERT_DestroyOCSPRequest(CERTOCSPRequest *request); + +/* + * FUNCTION: CERT_DecodeOCSPResponse + * Decode a DER encoded OCSP Response. + * INPUTS: + * SECItem *src + * Pointer to a SECItem holding DER encoded OCSP Response. + * RETURN: + * Returns a pointer to a CERTOCSPResponse (the decoded OCSP Response); + * the caller is responsible for destroying it. Or NULL if error (either + * response could not be decoded (SEC_ERROR_OCSP_MALFORMED_RESPONSE), + * it was of an unexpected type (SEC_ERROR_OCSP_UNKNOWN_RESPONSE_TYPE), + * or a low-level or internal error occurred). + */ +extern CERTOCSPResponse * +CERT_DecodeOCSPResponse(const SECItem *src); + +/* + * FUNCTION: CERT_DestroyOCSPResponse + * Frees an OCSP Response structure. + * INPUTS: + * CERTOCSPResponse *request + * Pointer to CERTOCSPResponse to be freed. + * RETURN: + * No return value; no errors. + */ +extern void +CERT_DestroyOCSPResponse(CERTOCSPResponse *response); + +/* + * FUNCTION: CERT_GetEncodedOCSPResponse + * Creates and sends a request to an OCSP responder, then reads and + * returns the (encoded) response. + * INPUTS: + * PLArenaPool *arena + * Pointer to arena from which return value will be allocated. + * If NULL, result will be allocated from the heap (and thus should + * be freed via SECITEM_FreeItem). + * CERTCertList *certList + * A list of certs for which status will be requested. + * Note that all of these certificates should have the same issuer, + * or it's expected the response will be signed by a trusted responder. + * If the certs need to be broken up into multiple requests, that + * must be handled by the caller (and thus by having multiple calls + * to this routine), who knows about where the request(s) are being + * sent and whether there are any trusted responders in place. + * const char *location + * The location of the OCSP responder (a URL). + * PRTime time + * Indicates the time for which the certificate status is to be + * determined -- this may be used in the search for the cert's issuer + * but has no other bearing on the operation. + * PRBool addServiceLocator + * If true, the Service Locator extension should be added to the + * single request(s) for each cert. + * CERTCertificate *signerCert + * If non-NULL, means sign the request using this cert. Otherwise, + * do not sign. + * void *pwArg + * Pointer to argument for password prompting, if needed. (Definitely + * not needed if not signing.) + * OUTPUTS: + * CERTOCSPRequest **pRequest + * Pointer in which to store the OCSP request created for the given + * list of certificates. It is only filled in if the entire operation + * is successful and the pointer is not null -- and in that case the + * caller is then reponsible for destroying it. + * RETURN: + * Returns a pointer to the SECItem holding the response. + * On error, returns null with error set describing the reason: + * SEC_ERROR_UNKNOWN_ISSUER + * SEC_ERROR_CERT_BAD_ACCESS_LOCATION + * SEC_ERROR_OCSP_BAD_HTTP_RESPONSE + * Other errors are low-level problems (no memory, bad database, etc.). + */ +extern SECItem * +CERT_GetEncodedOCSPResponse(PLArenaPool *arena, CERTCertList *certList, + const char *location, PRTime time, + PRBool addServiceLocator, + CERTCertificate *signerCert, void *pwArg, + CERTOCSPRequest **pRequest); + +/* + * FUNCTION: CERT_VerifyOCSPResponseSignature + * Check the signature on an OCSP Response. Will also perform a + * verification of the signer's certificate. Note, however, that a + * successful verification does not make any statement about the + * signer's *authority* to provide status for the certificate(s), + * that must be checked individually for each certificate. + * INPUTS: + * CERTOCSPResponse *response + * Pointer to response structure with signature to be checked. + * CERTCertDBHandle *handle + * Pointer to CERTCertDBHandle for certificate DB to use for verification. + * void *pwArg + * Pointer to argument for password prompting, if needed. + * CERTCertificate *issuerCert + * Issuer of the certificate that generated the OCSP request. + * OUTPUTS: + * CERTCertificate **pSignerCert + * Pointer in which to store signer's certificate; only filled-in if + * non-null. + * RETURN: + * Returns SECSuccess when signature is valid, anything else means invalid. + * Possible errors set: + * SEC_ERROR_OCSP_MALFORMED_RESPONSE - unknown type of ResponderID + * SEC_ERROR_INVALID_TIME - bad format of "ProducedAt" time + * SEC_ERROR_UNKNOWN_SIGNER - signer's cert could not be found + * SEC_ERROR_BAD_SIGNATURE - the signature did not verify + * Other errors are any of the many possible failures in cert verification + * (e.g. SEC_ERROR_REVOKED_CERTIFICATE, SEC_ERROR_UNTRUSTED_ISSUER) when + * verifying the signer's cert, or low-level problems (no memory, etc.) + */ +extern SECStatus +CERT_VerifyOCSPResponseSignature(CERTOCSPResponse *response, + CERTCertDBHandle *handle, void *pwArg, + CERTCertificate **pSignerCert, + CERTCertificate *issuerCert); + +/* + * FUNCTION: CERT_GetOCSPAuthorityInfoAccessLocation + * Get the value of the URI of the OCSP responder for the given cert. + * This is found in the (optional) Authority Information Access extension + * in the cert. + * INPUTS: + * CERTCertificate *cert + * The certificate being examined. + * RETURN: + * char * + * A copy of the URI for the OCSP method, if found. If either the + * extension is not present or it does not contain an entry for OCSP, + * SEC_ERROR_EXTENSION_NOT_FOUND will be set and a NULL returned. + * Any other error will also result in a NULL being returned. + * + * This result should be freed (via PORT_Free) when no longer in use. + */ +extern char * +CERT_GetOCSPAuthorityInfoAccessLocation(const CERTCertificate *cert); + +/* + * FUNCTION: CERT_RegisterAlternateOCSPAIAInfoCallBack + * This function serves two purposes. + * 1) It registers the address of a callback function that will be + * called for certs that have no OCSP AIA extension, to see if the + * callback wishes to supply an alternative URL for such an OCSP inquiry. + * 2) It outputs the previously registered function's address to the + * address supplied by the caller, unless that is NULL. + * The registered callback function returns NULL, or an allocated string + * that may be subsequently freed by calling PORT_Free(). + * RETURN: + * SECSuccess or SECFailure (if the library is not yet intialized) + */ +extern SECStatus +CERT_RegisterAlternateOCSPAIAInfoCallBack( + CERT_StringFromCertFcn newCallback, + CERT_StringFromCertFcn *oldCallback); + +/* + * FUNCTION: CERT_ParseURL + * Parse a URI into hostname, port, and path. The scheme in the URI must + * be "http". + * INPUTS: + * const char *url + * The URI to be parsed + * OUTPUTS: + * char **pHostname + * Pointer to store the hostname obtained from the URI. + * This result should be freed (via PORT_Free) when no longer in use. + * PRUint16 *pPort + * Pointer to store the port number obtained from the URI. + * char **pPath + * Pointer to store the path obtained from the URI. + * This result should be freed (via PORT_Free) when no longer in use. + * RETURN: + * Returns SECSuccess when parsing was successful. Returns SECFailure when + * problems were encountered. + */ +extern SECStatus +CERT_ParseURL(const char *url, char **pHostname, PRUint16 *pPort, char **pPath); + +/* + * FUNCTION: CERT_CheckOCSPStatus + * Checks the status of a certificate via OCSP. Will only check status for + * a certificate that has an AIA (Authority Information Access) extension + * for OCSP *or* when a "default responder" is specified and enabled. + * (If no AIA extension for OCSP and no default responder in place, the + * cert is considered to have a good status and SECSuccess is returned.) + * INPUTS: + * CERTCertDBHandle *handle + * certificate DB of the cert that is being checked + * CERTCertificate *cert + * the certificate being checked + * XXX in the long term also need a boolean parameter that specifies + * whether to check the cert chain, as well; for now we check only + * the leaf (the specified certificate) + * PRTime time + * time for which status is to be determined + * void *pwArg + * argument for password prompting, if needed + * RETURN: + * Returns SECSuccess if an approved OCSP responder "knows" the cert + * *and* returns a non-revoked status for it; SECFailure otherwise, + * with an error set describing the reason: + * + * SEC_ERROR_OCSP_BAD_HTTP_RESPONSE + * SEC_ERROR_OCSP_FUTURE_RESPONSE + * SEC_ERROR_OCSP_MALFORMED_REQUEST + * SEC_ERROR_OCSP_MALFORMED_RESPONSE + * SEC_ERROR_OCSP_OLD_RESPONSE + * SEC_ERROR_OCSP_REQUEST_NEEDS_SIG + * SEC_ERROR_OCSP_SERVER_ERROR + * SEC_ERROR_OCSP_TRY_SERVER_LATER + * SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST + * SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE + * SEC_ERROR_OCSP_UNKNOWN_CERT + * SEC_ERROR_OCSP_UNKNOWN_RESPONSE_STATUS + * SEC_ERROR_OCSP_UNKNOWN_RESPONSE_TYPE + * + * SEC_ERROR_BAD_SIGNATURE + * SEC_ERROR_CERT_BAD_ACCESS_LOCATION + * SEC_ERROR_INVALID_TIME + * SEC_ERROR_REVOKED_CERTIFICATE + * SEC_ERROR_UNKNOWN_ISSUER + * SEC_ERROR_UNKNOWN_SIGNER + * + * Other errors are any of the many possible failures in cert verification + * (e.g. SEC_ERROR_REVOKED_CERTIFICATE, SEC_ERROR_UNTRUSTED_ISSUER) when + * verifying the signer's cert, or low-level problems (error allocating + * memory, error performing ASN.1 decoding, etc.). + */ +extern SECStatus +CERT_CheckOCSPStatus(CERTCertDBHandle *handle, CERTCertificate *cert, + PRTime time, void *pwArg); + +/* + * FUNCTION: CERT_CacheOCSPResponseFromSideChannel + * First, this function checks the OCSP cache to see if a good response + * for the given certificate already exists. If it does, then the function + * returns successfully. + * + * If not, then it validates that the given OCSP response is a valid, + * good response for the given certificate and inserts it into the + * cache. + * + * This function is intended for use when OCSP responses are provided via a + * side-channel, i.e. TLS OCSP stapling (a.k.a. the status_request extension). + * + * INPUTS: + * CERTCertDBHandle *handle + * certificate DB of the cert that is being checked + * CERTCertificate *cert + * the certificate being checked + * PRTime time + * time for which status is to be determined + * SECItem *encodedResponse + * the DER encoded bytes of the OCSP response + * void *pwArg + * argument for password prompting, if needed + * RETURN: + * SECSuccess if the cert was found in the cache, or if the OCSP response was + * found to be valid and inserted into the cache. SECFailure otherwise. + */ +extern SECStatus +CERT_CacheOCSPResponseFromSideChannel(CERTCertDBHandle *handle, + CERTCertificate *cert, + PRTime time, + const SECItem *encodedResponse, + void *pwArg); + +/* + * FUNCTION: CERT_GetOCSPStatusForCertID + * Returns the OCSP status contained in the passed in parameter response + * that corresponds to the certID passed in. + * INPUTS: + * CERTCertDBHandle *handle + * certificate DB of the cert that is being checked + * CERTOCSPResponse *response + * the OCSP response we want to retrieve status from. + * CERTOCSPCertID *certID + * the ID we want to look for from the response. + * CERTCertificate *signerCert + * the certificate that was used to sign the OCSP response. + * must be obtained via a call to CERT_VerifyOCSPResponseSignature. + * PRTime time + * The time at which we're checking the status for. + * RETURN: + * Return values are the same as those for CERT_CheckOCSPStatus + */ +extern SECStatus +CERT_GetOCSPStatusForCertID(CERTCertDBHandle *handle, + CERTOCSPResponse *response, + CERTOCSPCertID *certID, + CERTCertificate *signerCert, + PRTime time); + +/* + * FUNCTION CERT_GetOCSPResponseStatus + * Returns the response status for the response passed. + * INPUTS: + * CERTOCSPResponse *response + * The response to query for status + * RETURN: + * Returns SECSuccess if the response has a successful status value. + * Otherwise it returns SECFailure and sets one of the following error + * codes via PORT_SetError + * SEC_ERROR_OCSP_MALFORMED_REQUEST + * SEC_ERROR_OCSP_SERVER_ERROR + * SEC_ERROR_OCSP_TRY_SERVER_LATER + * SEC_ERROR_OCSP_REQUEST_NEEDS_SIG + * SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST + * SEC_ERROR_OCSP_UNKNOWN_RESPONSE_STATUS + */ +extern SECStatus +CERT_GetOCSPResponseStatus(CERTOCSPResponse *response); + +/* + * FUNCTION CERT_CreateOCSPCertID + * Returns the OCSP certID for the certificate passed in. + * INPUTS: + * CERTCertificate *cert + * The certificate for which to create the certID for. + * PRTime time + * The time at which the id is requested for. This is used + * to determine the appropriate issuer for the cert since + * the issuing CA may be an older expired certificate. + * RETURN: + * A new copy of a CERTOCSPCertID*. The memory for this certID + * should be freed by calling CERT_DestroyOCSPCertID when the + * certID is no longer necessary. + */ +extern CERTOCSPCertID * +CERT_CreateOCSPCertID(CERTCertificate *cert, PRTime time); + +/* + * FUNCTION: CERT_DestroyOCSPCertID + * Frees the memory associated with the certID passed in. + * INPUTS: + * CERTOCSPCertID* certID + * The certID that the caller no longer needs and wants to + * free the associated memory. + * RETURN: + * SECSuccess if freeing the memory was successful. Returns + * SECFailure if the memory passed in was not allocated with + * a call to CERT_CreateOCSPCertID. + */ +extern SECStatus +CERT_DestroyOCSPCertID(CERTOCSPCertID *certID); + +extern CERTOCSPSingleResponse * +CERT_CreateOCSPSingleResponseGood(PLArenaPool *arena, + CERTOCSPCertID *id, + PRTime thisUpdate, + const PRTime *nextUpdate); + +extern CERTOCSPSingleResponse * +CERT_CreateOCSPSingleResponseUnknown(PLArenaPool *arena, + CERTOCSPCertID *id, + PRTime thisUpdate, + const PRTime *nextUpdate); + +extern CERTOCSPSingleResponse * +CERT_CreateOCSPSingleResponseRevoked( + PLArenaPool *arena, + CERTOCSPCertID *id, + PRTime thisUpdate, + const PRTime *nextUpdate, + PRTime revocationTime, + const CERTCRLEntryReasonCode *revocationReason); + +extern SECItem * +CERT_CreateEncodedOCSPSuccessResponse( + PLArenaPool *arena, + CERTCertificate *responderCert, + CERTOCSPResponderIDType responderIDType, + PRTime producedAt, + CERTOCSPSingleResponse **responses, + void *wincx); + +/* + * FUNCTION: CERT_CreateEncodedOCSPErrorResponse + * Creates an encoded OCSP response with an error response status. + * INPUTS: + * PLArenaPool *arena + * The return value is allocated from here. + * If a NULL is passed in, allocation is done from the heap instead. + * int error + * An NSS error code indicating an error response status. The error + * code is mapped to an OCSP response status as follows: + * SEC_ERROR_OCSP_MALFORMED_REQUEST -> malformedRequest + * SEC_ERROR_OCSP_SERVER_ERROR -> internalError + * SEC_ERROR_OCSP_TRY_SERVER_LATER -> tryLater + * SEC_ERROR_OCSP_REQUEST_NEEDS_SIG -> sigRequired + * SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST -> unauthorized + * where the OCSP response status is an enumerated type defined in + * RFC 2560: + * OCSPResponseStatus ::= ENUMERATED { + * successful (0), --Response has valid confirmations + * malformedRequest (1), --Illegal confirmation request + * internalError (2), --Internal error in issuer + * tryLater (3), --Try again later + * --(4) is not used + * sigRequired (5), --Must sign the request + * unauthorized (6) --Request unauthorized + * } + * RETURN: + * Returns a pointer to the SECItem holding the response. + * On error, returns null with error set describing the reason: + * SEC_ERROR_INVALID_ARGS + * Other errors are low-level problems (no memory, bad database, etc.). + */ +extern SECItem * +CERT_CreateEncodedOCSPErrorResponse(PLArenaPool *arena, int error); + +/* Sends an OCSP request using the HTTP POST method to the location addressed + * by the URL in |location| parameter. The request body will be + * |encodedRequest|, which must be a valid encoded OCSP request. On success, + * the server's response is returned and the caller must free it using + * SECITEM_FreeItem. On failure, NULL is returned. No parsing or validation of + * the HTTP response is done. + * + * If a default HTTP client has been registered with + * SEC_RegisterDefaultHttpClient then that client is used. Otherwise, an + * internal HTTP client is used. + */ +SECItem *CERT_PostOCSPRequest(PLArenaPool *arena, const char *location, + const SECItem *encodedRequest); + +/************************************************************************/ +SEC_END_PROTOS + +#endif /* _OCSP_H_ */ diff --git a/security/nss/lib/certhigh/ocspi.h b/security/nss/lib/certhigh/ocspi.h new file mode 100644 index 0000000000..c946d9f51c --- /dev/null +++ b/security/nss/lib/certhigh/ocspi.h @@ -0,0 +1,166 @@ +/* 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/. */ +/* + * ocspi.h - NSS internal interfaces to OCSP code + */ + +#ifndef _OCSPI_H_ +#define _OCSPI_H_ + +SECStatus OCSP_InitGlobal(void); +SECStatus OCSP_ShutdownGlobal(void); + +ocspResponseData * +ocsp_GetResponseData(CERTOCSPResponse *response, SECItem **tbsResponseDataDER); + +ocspSignature * +ocsp_GetResponseSignature(CERTOCSPResponse *response); + +SECItem * +ocsp_DigestValue(PLArenaPool *arena, SECOidTag digestAlg, + SECItem *fill, const SECItem *src); + +PRBool +ocsp_CertIsOCSPDefaultResponder(CERTCertDBHandle *handle, CERTCertificate *cert); + +CERTCertificate * +ocsp_GetSignerCertificate(CERTCertDBHandle *handle, ocspResponseData *tbsData, + ocspSignature *signature, CERTCertificate *issuer); + +SECStatus +ocsp_VerifyResponseSignature(CERTCertificate *signerCert, + ocspSignature *signature, + SECItem *tbsResponseDataDER, + void *pwArg); + +CERTOCSPRequest * +cert_CreateSingleCertOCSPRequest(CERTOCSPCertID *certID, + CERTCertificate *singleCert, + PRTime time, + PRBool addServiceLocator, + CERTCertificate *signerCert); + +typedef enum { ocspMissing, + ocspFresh, + ocspStale } OCSPFreshness; + +SECStatus +ocsp_GetCachedOCSPResponseStatus(CERTOCSPCertID *certID, + PRTime time, + PRBool ignoreOcspFailureMode, + SECStatus *rvOcsp, + SECErrorCodes *missingResponseError, + OCSPFreshness *freshness); + +/* + * FUNCTION: cert_ProcessOCSPResponse + * Same behavior and basic parameters as CERT_GetOCSPStatusForCertID. + * In addition it can update the OCSP cache (using information + * available internally to this function). + * INPUTS: + * CERTCertDBHandle *handle + * certificate DB of the cert that is being checked + * CERTOCSPResponse *response + * the OCSP response we want to retrieve status from. + * CERTOCSPCertID *certID + * the ID we want to look for from the response. + * CERTCertificate *signerCert + * the certificate that was used to sign the OCSP response. + * must be obtained via a call to CERT_VerifyOCSPResponseSignature. + * PRTime time + * The time at which we're checking the status for. + * PRBool *certIDWasConsumed + * In and Out parameter. + * If certIDWasConsumed is NULL on input, + * this function might produce a deep copy of cert ID + * for storing it in the cache. + * If out value is true, ownership of parameter certID was + * transferred to the OCSP cache. + * SECStatus *cacheUpdateStatus + * This optional out parameter will contain the result + * of the cache update operation (if requested). + * RETURN: + * The return value is not influenced by the cache operation, + * it matches the documentation for CERT_CheckOCSPStatus + */ + +SECStatus +cert_ProcessOCSPResponse(CERTCertDBHandle *handle, + CERTOCSPResponse *response, + CERTOCSPCertID *certID, + CERTCertificate *signerCert, + PRTime time, + PRBool *certIDWasConsumed, + SECStatus *cacheUpdateStatus); + +/* + * FUNCTION: cert_RememberOCSPProcessingFailure + * If an application notices a failure during OCSP processing, + * it should finally call this function. The failure will be recorded + * in the OCSP cache in order to avoid repetitive failures. + * INPUTS: + * CERTOCSPCertID *certID + * the ID that was used for the failed OCSP processing + * PRBool *certIDWasConsumed + * Out parameter, if set to true, ownership of parameter certID was + * transferred to the OCSP cache. + * RETURN: + * Status of the cache update operation. + */ + +SECStatus +cert_RememberOCSPProcessingFailure(CERTOCSPCertID *certID, + PRBool *certIDWasConsumed); + +/* + * FUNCTION: ocsp_GetResponderLocation + * Check ocspx context for user-designated responder URI first. If not + * found, checks cert AIA extension. + * INPUTS: + * CERTCertDBHandle *handle + * certificate DB of the cert that is being checked + * CERTCertificate *cert + * The certificate being examined. + * PRBool *certIDWasConsumed + * Out parameter, if set to true, URI of default responder is + * returned. + * RETURN: + * Responder URI. + */ +char * +ocsp_GetResponderLocation(CERTCertDBHandle *handle, + CERTCertificate *cert, + PRBool canUseDefaultLocation, + PRBool *isDefault); + +/* FUNCTION: ocsp_FetchingFailureIsVerificationFailure + * The function checks the global ocsp settings and + * tells how to treat an ocsp response fetching failure. + * RETURNS: + * if PR_TRUE is returned, then treat fetching as a + * revoked cert status. + */ +PRBool +ocsp_FetchingFailureIsVerificationFailure(void); + +size_t +ocsp_UrlEncodeBase64Buf(const char *base64Buf, char *outputBuf); + +SECStatus +ocsp_GetVerifiedSingleResponseForCertID(CERTCertDBHandle *handle, + CERTOCSPResponse *response, + CERTOCSPCertID *certID, + CERTCertificate *signerCert, + PRTime time, + CERTOCSPSingleResponse **pSingleResponse); + +SECStatus +ocsp_CertHasGoodStatus(ocspCertStatus *status, PRTime time); + +void +ocsp_CacheSingleResponse(CERTOCSPCertID *certID, + CERTOCSPSingleResponse *single, + PRBool *certIDWasConsumed); + +#endif /* _OCSPI_H_ */ diff --git a/security/nss/lib/certhigh/ocspsig.c b/security/nss/lib/certhigh/ocspsig.c new file mode 100644 index 0000000000..94606baf56 --- /dev/null +++ b/security/nss/lib/certhigh/ocspsig.c @@ -0,0 +1,597 @@ +/* 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 "secasn1.h" +#include "secder.h" +#include "cert.h" +#include "secerr.h" +#include "secoid.h" +#include "sechash.h" +#include "keyhi.h" +#include "cryptohi.h" +#include "ocsp.h" +#include "ocspti.h" +#include "ocspi.h" +#include "pk11pub.h" + +extern const SEC_ASN1Template ocsp_ResponderIDByNameTemplate[]; +extern const SEC_ASN1Template ocsp_ResponderIDByKeyTemplate[]; +extern const SEC_ASN1Template ocsp_OCSPResponseTemplate[]; + +ocspCertStatus * +ocsp_CreateCertStatus(PLArenaPool *arena, + ocspCertStatusType status, + PRTime revocationTime) +{ + ocspCertStatus *cs; + + if (!arena) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + switch (status) { + case ocspCertStatus_good: + case ocspCertStatus_unknown: + case ocspCertStatus_revoked: + break; + default: + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + cs = PORT_ArenaZNew(arena, ocspCertStatus); + if (!cs) + return NULL; + cs->certStatusType = status; + switch (status) { + case ocspCertStatus_good: + cs->certStatusInfo.goodInfo = SECITEM_AllocItem(arena, NULL, 0); + if (!cs->certStatusInfo.goodInfo) + return NULL; + break; + case ocspCertStatus_unknown: + cs->certStatusInfo.unknownInfo = SECITEM_AllocItem(arena, NULL, 0); + if (!cs->certStatusInfo.unknownInfo) + return NULL; + break; + case ocspCertStatus_revoked: + cs->certStatusInfo.revokedInfo = + PORT_ArenaZNew(arena, ocspRevokedInfo); + if (!cs->certStatusInfo.revokedInfo) + return NULL; + cs->certStatusInfo.revokedInfo->revocationReason = + SECITEM_AllocItem(arena, NULL, 0); + if (!cs->certStatusInfo.revokedInfo->revocationReason) + return NULL; + if (DER_TimeToGeneralizedTimeArena(arena, + &cs->certStatusInfo.revokedInfo->revocationTime, + revocationTime) != + SECSuccess) + return NULL; + break; + default: + PORT_Assert(PR_FALSE); + } + return cs; +} + +static const SEC_ASN1Template mySEC_EnumeratedTemplate[] = { + { SEC_ASN1_ENUMERATED, 0, NULL, sizeof(SECItem) } +}; + +static const SEC_ASN1Template mySEC_PointerToEnumeratedTemplate[] = { + { SEC_ASN1_POINTER, 0, mySEC_EnumeratedTemplate } +}; + +static const SEC_ASN1Template ocsp_EncodeRevokedInfoTemplate[] = { + { SEC_ASN1_GENERALIZED_TIME, + offsetof(ocspRevokedInfo, revocationTime) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(ocspRevokedInfo, revocationReason), + mySEC_PointerToEnumeratedTemplate }, + { 0 } +}; + +static const SEC_ASN1Template ocsp_PointerToEncodeRevokedInfoTemplate[] = { + { SEC_ASN1_POINTER, 0, + ocsp_EncodeRevokedInfoTemplate } +}; + +static const SEC_ASN1Template mySEC_NullTemplate[] = { + { SEC_ASN1_NULL, 0, NULL, sizeof(SECItem) } +}; + +static const SEC_ASN1Template ocsp_CertStatusTemplate[] = { + { SEC_ASN1_CHOICE, offsetof(ocspCertStatus, certStatusType), + 0, sizeof(ocspCertStatus) }, + { SEC_ASN1_CONTEXT_SPECIFIC | 0, + 0, mySEC_NullTemplate, ocspCertStatus_good }, + { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | + SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(ocspCertStatus, certStatusInfo.revokedInfo), + ocsp_PointerToEncodeRevokedInfoTemplate, ocspCertStatus_revoked }, + { SEC_ASN1_CONTEXT_SPECIFIC | 2, + 0, mySEC_NullTemplate, ocspCertStatus_unknown }, + { 0 } +}; + +static const SEC_ASN1Template mySECOID_AlgorithmIDTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(SECAlgorithmID) }, + { SEC_ASN1_OBJECT_ID, + offsetof(SECAlgorithmID, algorithm) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_ANY, + offsetof(SECAlgorithmID, parameters) }, + { 0 } +}; + +static const SEC_ASN1Template mySEC_AnyTemplate[] = { + { SEC_ASN1_ANY | SEC_ASN1_MAY_STREAM, 0, NULL, sizeof(SECItem) } +}; + +static const SEC_ASN1Template mySEC_SequenceOfAnyTemplate[] = { + { SEC_ASN1_SEQUENCE_OF, 0, mySEC_AnyTemplate } +}; + +static const SEC_ASN1Template mySEC_PointerToSequenceOfAnyTemplate[] = { + { SEC_ASN1_POINTER, 0, mySEC_SequenceOfAnyTemplate } +}; + +static const SEC_ASN1Template mySEC_IntegerTemplate[] = { + { SEC_ASN1_INTEGER, 0, NULL, sizeof(SECItem) } +}; + +static const SEC_ASN1Template mySEC_PointerToIntegerTemplate[] = { + { SEC_ASN1_POINTER, 0, mySEC_IntegerTemplate } +}; + +static const SEC_ASN1Template mySEC_GeneralizedTimeTemplate[] = { + { SEC_ASN1_GENERALIZED_TIME | SEC_ASN1_MAY_STREAM, 0, NULL, sizeof(SECItem) } +}; + +static const SEC_ASN1Template mySEC_PointerToGeneralizedTimeTemplate[] = { + { SEC_ASN1_POINTER, 0, mySEC_GeneralizedTimeTemplate } +}; + +static const SEC_ASN1Template ocsp_myCertIDTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(CERTOCSPCertID) }, + { SEC_ASN1_INLINE, + offsetof(CERTOCSPCertID, hashAlgorithm), + mySECOID_AlgorithmIDTemplate }, + { SEC_ASN1_OCTET_STRING, + offsetof(CERTOCSPCertID, issuerNameHash) }, + { SEC_ASN1_OCTET_STRING, + offsetof(CERTOCSPCertID, issuerKeyHash) }, + { SEC_ASN1_INTEGER, + offsetof(CERTOCSPCertID, serialNumber) }, + { 0 } +}; + +static const SEC_ASN1Template myCERT_CertExtensionTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(CERTCertExtension) }, + { SEC_ASN1_OBJECT_ID, + offsetof(CERTCertExtension, id) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_BOOLEAN, /* XXX DER_DEFAULT */ + offsetof(CERTCertExtension, critical) }, + { SEC_ASN1_OCTET_STRING, + offsetof(CERTCertExtension, value) }, + { 0 } +}; + +static const SEC_ASN1Template myCERT_SequenceOfCertExtensionTemplate[] = { + { SEC_ASN1_SEQUENCE_OF, 0, myCERT_CertExtensionTemplate } +}; + +static const SEC_ASN1Template myCERT_PointerToSequenceOfCertExtensionTemplate[] = { + { SEC_ASN1_POINTER, 0, myCERT_SequenceOfCertExtensionTemplate } +}; + +static const SEC_ASN1Template ocsp_mySingleResponseTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(CERTOCSPSingleResponse) }, + { SEC_ASN1_POINTER, + offsetof(CERTOCSPSingleResponse, certID), + ocsp_myCertIDTemplate }, + { SEC_ASN1_ANY, + offsetof(CERTOCSPSingleResponse, derCertStatus) }, + { SEC_ASN1_GENERALIZED_TIME, + offsetof(CERTOCSPSingleResponse, thisUpdate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(CERTOCSPSingleResponse, nextUpdate), + mySEC_PointerToGeneralizedTimeTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(CERTOCSPSingleResponse, singleExtensions), + myCERT_PointerToSequenceOfCertExtensionTemplate }, + { 0 } +}; + +static const SEC_ASN1Template ocsp_myResponseDataTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(ocspResponseData) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | /* XXX DER_DEFAULT */ + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(ocspResponseData, version), + mySEC_PointerToIntegerTemplate }, + { SEC_ASN1_ANY, + offsetof(ocspResponseData, derResponderID) }, + { SEC_ASN1_GENERALIZED_TIME, + offsetof(ocspResponseData, producedAt) }, + { SEC_ASN1_SEQUENCE_OF, + offsetof(ocspResponseData, responses), + ocsp_mySingleResponseTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(ocspResponseData, responseExtensions), + myCERT_PointerToSequenceOfCertExtensionTemplate }, + { 0 } +}; + +static const SEC_ASN1Template ocsp_EncodeBasicOCSPResponseTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(ocspBasicOCSPResponse) }, + { SEC_ASN1_POINTER, + offsetof(ocspBasicOCSPResponse, tbsResponseData), + ocsp_myResponseDataTemplate }, + { SEC_ASN1_INLINE, + offsetof(ocspBasicOCSPResponse, responseSignature.signatureAlgorithm), + mySECOID_AlgorithmIDTemplate }, + { SEC_ASN1_BIT_STRING, + offsetof(ocspBasicOCSPResponse, responseSignature.signature) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(ocspBasicOCSPResponse, responseSignature.derCerts), + mySEC_PointerToSequenceOfAnyTemplate }, + { 0 } +}; + +static CERTOCSPSingleResponse * +ocsp_CreateSingleResponse(PLArenaPool *arena, + CERTOCSPCertID *id, ocspCertStatus *status, + PRTime thisUpdate, const PRTime *nextUpdate) +{ + CERTOCSPSingleResponse *sr; + + if (!arena || !id || !status) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + sr = PORT_ArenaZNew(arena, CERTOCSPSingleResponse); + if (!sr) + return NULL; + sr->arena = arena; + sr->certID = id; + sr->certStatus = status; + if (DER_TimeToGeneralizedTimeArena(arena, &sr->thisUpdate, thisUpdate) != + SECSuccess) + return NULL; + sr->nextUpdate = NULL; + if (nextUpdate) { + sr->nextUpdate = SECITEM_AllocItem(arena, NULL, 0); + if (!sr->nextUpdate) + return NULL; + if (DER_TimeToGeneralizedTimeArena(arena, sr->nextUpdate, *nextUpdate) != + SECSuccess) + return NULL; + } + + sr->singleExtensions = PORT_ArenaNewArray(arena, CERTCertExtension *, 1); + if (!sr->singleExtensions) + return NULL; + + sr->singleExtensions[0] = NULL; + + if (!SEC_ASN1EncodeItem(arena, &sr->derCertStatus, + status, ocsp_CertStatusTemplate)) + return NULL; + + return sr; +} + +CERTOCSPSingleResponse * +CERT_CreateOCSPSingleResponseGood(PLArenaPool *arena, + CERTOCSPCertID *id, + PRTime thisUpdate, + const PRTime *nextUpdate) +{ + ocspCertStatus *cs; + if (!arena) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + cs = ocsp_CreateCertStatus(arena, ocspCertStatus_good, 0); + if (!cs) + return NULL; + return ocsp_CreateSingleResponse(arena, id, cs, thisUpdate, nextUpdate); +} + +CERTOCSPSingleResponse * +CERT_CreateOCSPSingleResponseUnknown(PLArenaPool *arena, + CERTOCSPCertID *id, + PRTime thisUpdate, + const PRTime *nextUpdate) +{ + ocspCertStatus *cs; + if (!arena) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + cs = ocsp_CreateCertStatus(arena, ocspCertStatus_unknown, 0); + if (!cs) + return NULL; + return ocsp_CreateSingleResponse(arena, id, cs, thisUpdate, nextUpdate); +} + +CERTOCSPSingleResponse * +CERT_CreateOCSPSingleResponseRevoked( + PLArenaPool *arena, + CERTOCSPCertID *id, + PRTime thisUpdate, + const PRTime *nextUpdate, + PRTime revocationTime, + const CERTCRLEntryReasonCode *revocationReason) +{ + ocspCertStatus *cs; + /* revocationReason is not yet supported, so it must be NULL. */ + if (!arena || revocationReason) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + cs = ocsp_CreateCertStatus(arena, ocspCertStatus_revoked, revocationTime); + if (!cs) + return NULL; + return ocsp_CreateSingleResponse(arena, id, cs, thisUpdate, nextUpdate); +} + +/* responderCert == 0 means: + * create a response with an invalid signature (for testing purposes) */ +SECItem * +CERT_CreateEncodedOCSPSuccessResponse( + PLArenaPool *arena, + CERTCertificate *responderCert, + CERTOCSPResponderIDType responderIDType, + PRTime producedAt, + CERTOCSPSingleResponse **responses, + void *wincx) +{ + PLArenaPool *tmpArena; + ocspResponseData *rd = NULL; + ocspResponderID *rid = NULL; + const SEC_ASN1Template *responderIDTemplate = NULL; + ocspBasicOCSPResponse *br = NULL; + ocspResponseBytes *rb = NULL; + CERTOCSPResponse *response = NULL; + + SECOidTag algID; + SECOidData *od = NULL; + SECKEYPrivateKey *privKey = NULL; + SECItem *result = NULL; + + if (!arena || !responses) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + if (responderIDType != ocspResponderID_byName && + responderIDType != ocspResponderID_byKey) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + tmpArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!tmpArena) + return NULL; + + rd = PORT_ArenaZNew(tmpArena, ocspResponseData); + if (!rd) + goto done; + rid = PORT_ArenaZNew(tmpArena, ocspResponderID); + if (!rid) + goto done; + br = PORT_ArenaZNew(tmpArena, ocspBasicOCSPResponse); + if (!br) + goto done; + rb = PORT_ArenaZNew(tmpArena, ocspResponseBytes); + if (!rb) + goto done; + response = PORT_ArenaZNew(tmpArena, CERTOCSPResponse); + if (!response) + goto done; + + rd->version.data = NULL; + rd->version.len = 0; + rd->responseExtensions = NULL; + rd->responses = responses; + if (DER_TimeToGeneralizedTimeArena(tmpArena, &rd->producedAt, producedAt) != + SECSuccess) + goto done; + + if (!responderCert) { + /* use invalid signature for testing purposes */ + unsigned char dummyChar = 'd'; + SECItem dummy; + + dummy.len = 1; + dummy.data = &dummyChar; + + /* it's easier to produdce a keyHash out of nowhere, + * than to produce an encoded subject, + * so for our dummy response we always use byKey + */ + + rid->responderIDType = ocspResponderID_byKey; + if (!ocsp_DigestValue(tmpArena, SEC_OID_SHA1, &rid->responderIDValue.keyHash, + &dummy)) + goto done; + + if (!SEC_ASN1EncodeItem(tmpArena, &rd->derResponderID, rid, + ocsp_ResponderIDByKeyTemplate)) + goto done; + + br->tbsResponseData = rd; + + if (!SEC_ASN1EncodeItem(tmpArena, &br->tbsResponseDataDER, br->tbsResponseData, + ocsp_myResponseDataTemplate)) + goto done; + + br->responseSignature.derCerts = PORT_ArenaNewArray(tmpArena, SECItem *, 1); + if (!br->responseSignature.derCerts) + goto done; + br->responseSignature.derCerts[0] = NULL; + + algID = SEC_GetSignatureAlgorithmOidTag(rsaKey, SEC_OID_SHA1); + if (algID == SEC_OID_UNKNOWN) + goto done; + + /* match the regular signature code, which doesn't use the arena */ + if (!SECITEM_AllocItem(NULL, &br->responseSignature.signature, 1)) + goto done; + PORT_Memcpy(br->responseSignature.signature.data, &dummyChar, 1); + + /* convert len-in-bytes to len-in-bits */ + br->responseSignature.signature.len = br->responseSignature.signature.len << 3; + } else { + rid->responderIDType = responderIDType; + if (responderIDType == ocspResponderID_byName) { + responderIDTemplate = ocsp_ResponderIDByNameTemplate; + if (CERT_CopyName(tmpArena, &rid->responderIDValue.name, + &responderCert->subject) != SECSuccess) + goto done; + } else { + responderIDTemplate = ocsp_ResponderIDByKeyTemplate; + if (!CERT_GetSubjectPublicKeyDigest(tmpArena, responderCert, + SEC_OID_SHA1, &rid->responderIDValue.keyHash)) + goto done; + } + + if (!SEC_ASN1EncodeItem(tmpArena, &rd->derResponderID, rid, + responderIDTemplate)) + goto done; + + br->tbsResponseData = rd; + + if (!SEC_ASN1EncodeItem(tmpArena, &br->tbsResponseDataDER, br->tbsResponseData, + ocsp_myResponseDataTemplate)) + goto done; + + br->responseSignature.derCerts = PORT_ArenaNewArray(tmpArena, SECItem *, 1); + if (!br->responseSignature.derCerts) + goto done; + br->responseSignature.derCerts[0] = NULL; + + privKey = PK11_FindKeyByAnyCert(responderCert, wincx); + if (!privKey) + goto done; + + algID = SEC_GetSignatureAlgorithmOidTag(privKey->keyType, SEC_OID_SHA1); + if (algID == SEC_OID_UNKNOWN) + goto done; + + if (SEC_SignData(&br->responseSignature.signature, + br->tbsResponseDataDER.data, br->tbsResponseDataDER.len, + privKey, algID) != + SECSuccess) + goto done; + + /* convert len-in-bytes to len-in-bits */ + br->responseSignature.signature.len = br->responseSignature.signature.len << 3; + + /* br->responseSignature.signature wasn't allocated from arena, + * we must free it when done. */ + } + + if (SECOID_SetAlgorithmID(tmpArena, &br->responseSignature.signatureAlgorithm, algID, 0) != + SECSuccess) + goto done; + + if (!SEC_ASN1EncodeItem(tmpArena, &rb->response, br, + ocsp_EncodeBasicOCSPResponseTemplate)) + goto done; + + rb->responseTypeTag = SEC_OID_PKIX_OCSP_BASIC_RESPONSE; + + od = SECOID_FindOIDByTag(rb->responseTypeTag); + if (!od) + goto done; + + rb->responseType = od->oid; + rb->decodedResponse.basic = br; + + response->arena = tmpArena; + response->responseBytes = rb; + response->statusValue = ocspResponse_successful; + + if (!SEC_ASN1EncodeInteger(tmpArena, &response->responseStatus, + response->statusValue)) + goto done; + + result = SEC_ASN1EncodeItem(arena, NULL, response, ocsp_OCSPResponseTemplate); + +done: + if (privKey) + SECKEY_DestroyPrivateKey(privKey); + if (br && br->responseSignature.signature.data) + SECITEM_FreeItem(&br->responseSignature.signature, PR_FALSE); + PORT_FreeArena(tmpArena, PR_FALSE); + + return result; +} + +static const SEC_ASN1Template ocsp_OCSPErrorResponseTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(CERTOCSPResponse) }, + { SEC_ASN1_ENUMERATED, + offsetof(CERTOCSPResponse, responseStatus) }, + { 0, 0, + mySEC_NullTemplate }, + { 0 } +}; + +SECItem * +CERT_CreateEncodedOCSPErrorResponse(PLArenaPool *arena, int error) +{ + CERTOCSPResponse response; + SECItem *result = NULL; + + switch (error) { + case SEC_ERROR_OCSP_MALFORMED_REQUEST: + response.statusValue = ocspResponse_malformedRequest; + break; + case SEC_ERROR_OCSP_SERVER_ERROR: + response.statusValue = ocspResponse_internalError; + break; + case SEC_ERROR_OCSP_TRY_SERVER_LATER: + response.statusValue = ocspResponse_tryLater; + break; + case SEC_ERROR_OCSP_REQUEST_NEEDS_SIG: + response.statusValue = ocspResponse_sigRequired; + break; + case SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST: + response.statusValue = ocspResponse_unauthorized; + break; + default: + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return NULL; + } + + if (!SEC_ASN1EncodeInteger(NULL, &response.responseStatus, + response.statusValue)) + return NULL; + + result = SEC_ASN1EncodeItem(arena, NULL, &response, + ocsp_OCSPErrorResponseTemplate); + + SECITEM_FreeItem(&response.responseStatus, PR_FALSE); + + return result; +} diff --git a/security/nss/lib/certhigh/ocspt.h b/security/nss/lib/certhigh/ocspt.h new file mode 100644 index 0000000000..db429ff058 --- /dev/null +++ b/security/nss/lib/certhigh/ocspt.h @@ -0,0 +1,301 @@ +/* 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/. */ + +/* + * Public header for exported OCSP types. + */ + +#ifndef _OCSPT_H_ +#define _OCSPT_H_ + +/* + * The following are all opaque types. If someone needs to get at + * a field within, then we need to fix the API. Try very hard not + * make the type available to them. + */ +typedef struct CERTOCSPRequestStr CERTOCSPRequest; +typedef struct CERTOCSPResponseStr CERTOCSPResponse; + +/* + * XXX I think only those first two above should need to be exported, + * but until I know for certain I am leaving the rest of these here, too. + */ +typedef struct CERTOCSPCertIDStr CERTOCSPCertID; +typedef struct CERTOCSPSingleResponseStr CERTOCSPSingleResponse; + +/* + * This interface is described in terms of an HttpClient which + * supports at least a specified set of functions. (An implementer may + * provide HttpClients with additional functionality accessible only to + * users with a particular implementation in mind.) The basic behavior + * is provided by defining a set of functions, listed in an + * SEC_HttpServerFcnStruct. If the implementor of a SpecificHttpClient + * registers his SpecificHttpClient as the default HttpClient, then his + * functions will be called by the user of an HttpClient, such as an + * OCSPChecker. + * + * The implementer of a specific HttpClient (e.g., the NSS-provided + * DefaultHttpClient), populates an SEC_HttpClientFcnStruct, uses it to + * register his client, and waits for his functions to be called. + * + * For future expandability, the SEC_HttpClientFcnStruct is defined as a + * union, with the version field acting as a selector. The proposed + * initial version of the structure is given following the definition + * of the union. The HttpClientState structure is implementation- + * dependent, and should be opaque to the user. + */ + +typedef void *SEC_HTTP_SERVER_SESSION; +typedef void *SEC_HTTP_REQUEST_SESSION; + +/* + * This function creates a SEC_HTTP_SERVER_SESSION object. The implementer of a + * specific HttpClient will allocate the necessary space, when this + * function is called, and will free it when the corresponding FreeFcn + * is called. The SEC_HTTP_SERVER_SESSION object is passed, as an opaque object, + * to subsequent calls. + * + * If the function returns SECSuccess, the returned SEC_HTTP_SERVER_SESSION + * must be cleaned up with a call to SEC_HttpServer_FreeSession, + * after processing is finished. + */ +typedef SECStatus (*SEC_HttpServer_CreateSessionFcn)( + const char *host, + PRUint16 portnum, + SEC_HTTP_SERVER_SESSION *pSession); + +/* + * This function is called to allow the implementation to attempt to keep + * the connection alive. Depending on the underlying platform, it might + * immediately return SECSuccess without having performed any operations. + * (If a connection has not been kept alive, a subsequent call to + * SEC_HttpRequest_TrySendAndReceiveFcn should reopen the connection + * automatically.) + * + * If the connection uses nonblocking I/O, this function may return + * SECWouldBlock and store a nonzero value at "pPollDesc". In that case + * the caller may wait on the poll descriptor, and should call this function + * again until SECSuccess (and a zero value at "pPollDesc") is obtained. + */ +typedef SECStatus (*SEC_HttpServer_KeepAliveSessionFcn)( + SEC_HTTP_SERVER_SESSION session, + PRPollDesc **pPollDesc); + +/* + * This function frees the client SEC_HTTP_SERVER_SESSION object, closes all + * SEC_HTTP_REQUEST_SESSIONs created for that server, discards all partial results, + * frees any memory that was allocated by the client, and invalidates any + * response pointers that might have been returned by prior server or request + * functions. + */ +typedef SECStatus (*SEC_HttpServer_FreeSessionFcn)( + SEC_HTTP_SERVER_SESSION session); + +/* + * This function creates a SEC_HTTP_REQUEST_SESSION object. The implementer of a + * specific HttpClient will allocate the necessary space, when this + * function is called, and will free it when the corresponding FreeFcn + * is called. The SEC_HTTP_REQUEST_SESSION object is passed, as an opaque object, + * to subsequent calls. + * + * An implementation that does not support the requested protocol variant + * (usually "http", but could eventually allow "https") or request method + * should return SECFailure. + * + * Timeout values may include the constants PR_INTERVAL_NO_TIMEOUT (wait + * forever) or PR_INTERVAL_NO_WAIT (nonblocking I/O). + * + * If the function returns SECSuccess, the returned SEC_HTTP_REQUEST_SESSION + * must be cleaned up with a call to SEC_HttpRequest_FreeSession, + * after processing is finished. + */ +typedef SECStatus (*SEC_HttpRequest_CreateFcn)( + SEC_HTTP_SERVER_SESSION session, + const char *http_protocol_variant, /* usually "http" */ + const char *path_and_query_string, + const char *http_request_method, + const PRIntervalTime timeout, + SEC_HTTP_REQUEST_SESSION *pRequest); + +/* + * This function sets data to be sent to the server for an HTTP request + * of http_request_method == POST. If a particular implementation + * supports it, the details for the POST request can be set by calling + * this function, prior to activating the request with TrySendAndReceiveFcn. + * + * An implementation that does not support the POST method should + * implement a SetPostDataFcn function that returns immediately. + * + * Setting http_content_type is optional, the parameter may + * by NULL or the empty string. + */ +typedef SECStatus (*SEC_HttpRequest_SetPostDataFcn)( + SEC_HTTP_REQUEST_SESSION request, + const char *http_data, + const PRUint32 http_data_len, + const char *http_content_type); + +/* + * This function sets an additional HTTP protocol request header. + * If a particular implementation supports it, one or multiple headers + * can be added to the request by calling this function once or multiple + * times, prior to activating the request with TryFcn. + * + * An implementation that does not support setting additional headers + * should implement an AddRequestHeaderFcn function that returns immediately. + */ +typedef SECStatus (*SEC_HttpRequest_AddHeaderFcn)( + SEC_HTTP_REQUEST_SESSION request, + const char *http_header_name, + const char *http_header_value); + +/* + * This function initiates or continues an HTTP request. After + * parameters have been set with the Create function and, optionally, + * modified or enhanced with the AddParams function, this call creates + * the socket connection and initiates the communication. + * + * If a timeout value of zero is specified, indicating non-blocking + * I/O, the client creates a non-blocking socket, and returns a status + * of SECWouldBlock and a non-NULL PRPollDesc if the operation is not + * complete. In that case all other return parameters are undefined. + * The caller is expected to repeat the call, possibly after using + * PRPoll to determine that a completion has occurred, until a return + * value of SECSuccess (and a NULL value for pPollDesc) or a return + * value of SECFailure (indicating failure on the network level) + * is obtained. + * + * http_response_data_len is both input and output parameter. + * If a pointer to a PRUint32 is supplied, the http client is + * expected to check the given integer value and always set an out + * value, even on failure. + * An input value of zero means, the caller will accept any response len. + * A different input value indicates the maximum response value acceptable + * to the caller. + * If data is successfully read and the size is acceptable to the caller, + * the function will return SECSuccess and set http_response_data_len to + * the size of the block returned in http_response_data. + * If the data read from the http server is larger than the acceptable + * size, the function will return SECFailure. + * http_response_data_len will be set to a value different from zero to + * indicate the reason of the failure. + * An out value of "0" means, the failure was unrelated to the + * acceptable size. + * An out value of "1" means, the result data is larger than the + * accpeptable size, but the real size is not yet known to the http client + * implementation and it stopped retrieving it, + * Any other out value combined with a return value of SECFailure + * will indicate the actual size of the server data. + * + * The caller is permitted to provide NULL values for any of the + * http_response arguments, indicating the caller is not interested in + * those values. If the caller does provide an address, the HttpClient + * stores at that address a pointer to the corresponding argument, at + * the completion of the operation. + * + * All returned pointers will be owned by the the HttpClient + * implementation and will remain valid until the call to + * SEC_HttpRequest_FreeFcn. + */ +typedef SECStatus (*SEC_HttpRequest_TrySendAndReceiveFcn)( + SEC_HTTP_REQUEST_SESSION request, + PRPollDesc **pPollDesc, + PRUint16 *http_response_code, + const char **http_response_content_type, + const char **http_response_headers, + const char **http_response_data, + PRUint32 *http_response_data_len); + +/* + * Calling CancelFcn asks for premature termination of the request. + * + * Future calls to SEC_HttpRequest_TrySendAndReceive should + * by avoided, but in this case the HttpClient implementation + * is expected to return immediately with SECFailure. + * + * After calling CancelFcn, a separate call to SEC_HttpRequest_FreeFcn + * is still necessary to free resources. + */ +typedef SECStatus (*SEC_HttpRequest_CancelFcn)( + SEC_HTTP_REQUEST_SESSION request); + +/* + * Before calling this function, it must be assured the request + * has been completed, i.e. either SEC_HttpRequest_TrySendAndReceiveFcn has + * returned SECSuccess, or the request has been canceled with + * a call to SEC_HttpRequest_CancelFcn. + * + * This function frees the client state object, closes all sockets, + * discards all partial results, frees any memory that was allocated + * by the client, and invalidates all response pointers that might + * have been returned by SEC_HttpRequest_TrySendAndReceiveFcn + */ +typedef SECStatus (*SEC_HttpRequest_FreeFcn)( + SEC_HTTP_REQUEST_SESSION request); + +typedef struct SEC_HttpClientFcnV1Struct { + SEC_HttpServer_CreateSessionFcn createSessionFcn; + SEC_HttpServer_KeepAliveSessionFcn keepAliveSessionFcn; + SEC_HttpServer_FreeSessionFcn freeSessionFcn; + SEC_HttpRequest_CreateFcn createFcn; + SEC_HttpRequest_SetPostDataFcn setPostDataFcn; + SEC_HttpRequest_AddHeaderFcn addHeaderFcn; + SEC_HttpRequest_TrySendAndReceiveFcn trySendAndReceiveFcn; + SEC_HttpRequest_CancelFcn cancelFcn; + SEC_HttpRequest_FreeFcn freeFcn; +} SEC_HttpClientFcnV1; + +typedef struct SEC_HttpClientFcnStruct { + PRInt16 version; + union { + SEC_HttpClientFcnV1 ftable1; + /* SEC_HttpClientFcnV2 ftable2; */ + /* ... */ + } fcnTable; +} SEC_HttpClientFcn; + +/* + * ocspMode_FailureIsVerificationFailure: + * This is the classic behaviour of NSS. + * Any OCSP failure is a verification failure (classic mode, default). + * Without a good response, OCSP networking will be retried each time + * it is required for verifying a cert. + * + * ocspMode_FailureIsNotAVerificationFailure: + * If we fail to obtain a valid OCSP response, consider the + * cert as good. + * Failed OCSP attempts might get cached and not retried until + * minimumSecondsToNextFetchAttempt. + * If we are able to obtain a valid response, the cert + * will be considered good, if either status is "good" + * or the cert was not yet revoked at verification time. + * + * Additional failure modes might be added in the future. + */ +typedef enum { + ocspMode_FailureIsVerificationFailure = 0, + ocspMode_FailureIsNotAVerificationFailure = 1 +} SEC_OcspFailureMode; + +/* + * A ResponderID identifies the responder -- or more correctly, the + * signer of the response. The ASN.1 definition of a ResponderID is: + * + * ResponderID ::= CHOICE { + * byName [1] EXPLICIT Name, + * byKey [2] EXPLICIT KeyHash } + * + * Because it is CHOICE, the type of identification used and the + * identification itself are actually encoded together. To represent + * this same information internally, we explicitly define a type and + * save it, along with the value, into a data structure. + */ + +typedef enum { + ocspResponderID_other = -1, /* unknown kind of responderID */ + ocspResponderID_byName = 1, + ocspResponderID_byKey = 2 +} CERTOCSPResponderIDType; + +#endif /* _OCSPT_H_ */ diff --git a/security/nss/lib/certhigh/ocspti.h b/security/nss/lib/certhigh/ocspti.h new file mode 100644 index 0000000000..d9297dba6a --- /dev/null +++ b/security/nss/lib/certhigh/ocspti.h @@ -0,0 +1,356 @@ +/* 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/. */ + +/* + * Private header defining OCSP types. + */ + +#ifndef _OCSPTI_H_ +#define _OCSPTI_H_ + +#include "ocspt.h" + +#include "certt.h" +#include "plarena.h" +#include "seccomon.h" +#include "secoidt.h" + +/* + * Some notes about naming conventions... + * + * The public data types all start with "CERTOCSP" (e.g. CERTOCSPRequest). + * (Even the public types are opaque, however. Only their names are + * "exported".) + * + * Internal-only data types drop the "CERT" prefix and use only the + * lower-case "ocsp" (e.g. ocspTBSRequest), for brevity sake. + * + * In either case, the base/suffix of the type name usually matches the + * name as defined in the OCSP specification. The exceptions to this are: + * - When there is overlap between the "OCSP" or "ocsp" prefix and + * the name used in the standard. That is, you cannot strip off the + * "CERTOCSP" or "ocsp" prefix and necessarily get the name of the + * type as it is defined in the standard; the "real" name will be + * *either* "OCSPSuffix" or just "Suffix". + * - When the name in the standard was a little too generic. (e.g. The + * standard defines "Request" but we call it a "SingleRequest".) + * In this case a comment above the type definition calls attention + * to the difference. + * + * The definitions laid out in this header file are intended to follow + * the same order as the definitions in the OCSP specification itself. + * With the OCSP standard in hand, you should be able to move through + * this file and follow along. To future modifiers of this file: please + * try to keep it that way. The only exceptions are the few cases where + * we need to define a type before it is referenced (e.g. enumerations), + * whereas in the OCSP specification these are usually defined the other + * way around (reference before definition). + */ + +/* + * Forward-declarations of internal-only data structures. + * + * These are in alphabetical order (case-insensitive); please keep it that way! + */ +typedef struct ocspBasicOCSPResponseStr ocspBasicOCSPResponse; +typedef struct ocspCertStatusStr ocspCertStatus; +typedef struct ocspResponderIDStr ocspResponderID; +typedef struct ocspResponseBytesStr ocspResponseBytes; +typedef struct ocspResponseDataStr ocspResponseData; +typedef struct ocspRevokedInfoStr ocspRevokedInfo; +typedef struct ocspServiceLocatorStr ocspServiceLocator; +typedef struct ocspSignatureStr ocspSignature; +typedef struct ocspSingleRequestStr ocspSingleRequest; +typedef struct ocspSingleResponseStr ocspSingleResponse; +typedef struct ocspTBSRequestStr ocspTBSRequest; + +/* + * An OCSPRequest; this is what is sent (encoded) to an OCSP responder. + */ +struct CERTOCSPRequestStr { + PLArenaPool *arena; /* local; not part of encoding */ + ocspTBSRequest *tbsRequest; + ocspSignature *optionalSignature; +}; + +/* + * A TBSRequest; when an OCSPRequest is signed, the encoding of this + * is what the signature is actually applied to. ("TBS" == To Be Signed) + * Whether signed or not, however, this structure will be present, and + * is the "meat" of the OCSPRequest. + * + * Note that the "requestorName" field cannot be encoded/decoded in the + * same pass as the entire request -- it needs to be handled with a special + * call to convert to/from our internal form of a GeneralName. Thus the + * "derRequestorName" field, which is the actual DER-encoded bytes. + * + * The "extensionHandle" field is used on creation only; it holds + * in-progress extensions as they are optionally added to the request. + */ +struct ocspTBSRequestStr { + SECItem version; /* an INTEGER */ + SECItem *derRequestorName; /* encoded GeneralName; see above */ + CERTGeneralNameList *requestorName; /* local; not part of encoding */ + ocspSingleRequest **requestList; + CERTCertExtension **requestExtensions; + void *extensionHandle; /* local; not part of encoding */ +}; + +/* + * This is the actual signature information for an OCSPRequest (applied to + * the TBSRequest structure) or for a BasicOCSPResponse (applied to a + * ResponseData structure). + * + * Note that the "signature" field itself is a BIT STRING; operations on + * it need to keep that in mind, converting the length to bytes as needed + * and back again afterward (so that the length is usually expressing bits). + * + * The "cert" field is the signer's certificate. In the case of a received + * signature, it will be filled in when the signature is verified. In the + * case of a created signature, it is filled in on creation and will be the + * cert used to create the signature when the signing-and-encoding occurs, + * as well as the cert (and its chain) to fill in derCerts if requested. + * + * The extra fields cache information about the signature after we have + * attempted a verification. "wasChecked", if true, means the signature + * has been checked against the appropriate data and thus that "status" + * contains the result of that verification. If "status" is not SECSuccess, + * "failureReason" is a copy of the error code that was set at the time; + * presumably it tells why the signature verification failed. + */ +struct ocspSignatureStr { + SECAlgorithmID signatureAlgorithm; + SECItem signature; /* a BIT STRING */ + SECItem **derCerts; /* a SEQUENCE OF Certificate */ + CERTCertificate *cert; /* local; not part of encoding */ + PRBool wasChecked; /* local; not part of encoding */ + SECStatus status; /* local; not part of encoding */ + int failureReason; /* local; not part of encoding */ +}; + +/* + * An OCSPRequest contains a SEQUENCE OF these, one for each certificate + * whose status is being checked. + * + * Note that in the OCSP specification this is just called "Request", + * but since that seemed confusing (vs. an OCSPRequest) and to be more + * consistent with the parallel type "SingleResponse", I called it a + * "SingleRequest". + * + * XXX figure out how to get rid of that arena -- there must be a way + */ +struct ocspSingleRequestStr { + PLArenaPool *arena; /* just a copy of the response arena, + * needed here for extension handling + * routines, on creation only */ + CERTOCSPCertID *reqCert; + CERTCertExtension **singleRequestExtensions; +}; + +/* + * A CertID is the means of identifying a certificate, used both in requests + * and in responses. + * + * When in a SingleRequest it specifies the certificate to be checked. + * When in a SingleResponse it is the cert whose status is being given. + */ +struct CERTOCSPCertIDStr { + SECAlgorithmID hashAlgorithm; + SECItem issuerNameHash; /* an OCTET STRING */ + SECItem issuerKeyHash; /* an OCTET STRING */ + SECItem serialNumber; /* an INTEGER */ + SECItem issuerSHA1NameHash; /* keep other hashes around when */ + SECItem issuerMD5NameHash; /* we have them */ + SECItem issuerMD2NameHash; + SECItem issuerSHA1KeyHash; /* keep other hashes around when */ + SECItem issuerMD5KeyHash; /* we have them */ + SECItem issuerMD2KeyHash; + PLArenaPool *poolp; +}; + +/* + * This describes the value of the responseStatus field in an OCSPResponse. + * The corresponding ASN.1 definition is: + * + * OCSPResponseStatus ::= ENUMERATED { + * successful (0), --Response has valid confirmations + * malformedRequest (1), --Illegal confirmation request + * internalError (2), --Internal error in issuer + * tryLater (3), --Try again later + * --(4) is not used + * sigRequired (5), --Must sign the request + * unauthorized (6), --Request unauthorized + * } + */ +typedef enum { + ocspResponse_min = 0, + ocspResponse_successful = 0, + ocspResponse_malformedRequest = 1, + ocspResponse_internalError = 2, + ocspResponse_tryLater = 3, + ocspResponse_unused = 4, + ocspResponse_sigRequired = 5, + ocspResponse_unauthorized = 6, + ocspResponse_max = 6 /* Please update max when adding values. + * Remember to also update arrays, e.g. + * "responseStatusNames" in ocspclnt.c + * and potentially other places. */ +} ocspResponseStatus; + +/* + * An OCSPResponse is what is sent (encoded) by an OCSP responder. + * + * The field "responseStatus" is the ASN.1 encoded value; the field + * "statusValue" is simply that same value translated into our local + * type ocspResponseStatus. + */ +struct CERTOCSPResponseStr { + PLArenaPool *arena; /* local; not part of encoding */ + SECItem responseStatus; /* an ENUMERATED, see above */ + ocspResponseStatus statusValue; /* local; not part of encoding */ + ocspResponseBytes *responseBytes; /* only when status is successful */ +}; + +/* + * A ResponseBytes (despite appearances) is what contains the meat + * of a successful response -- but still in encoded form. The type + * given as "responseType" tells you how to decode the string. + * + * We look at the OID and translate it into our local OID representation + * "responseTypeTag", and use that value to tell us how to decode the + * actual response itself. For now the only kind of OCSP response we + * know about is a BasicOCSPResponse. However, the intention in the + * OCSP specification is to allow for other response types, so we are + * building in that flexibility from the start and thus put a pointer + * to that data structure inside of a union. Whenever OCSP adds more + * response types, just add them to the union. + */ +struct ocspResponseBytesStr { + SECItem responseType; /* an OBJECT IDENTIFIER */ + SECOidTag responseTypeTag; /* local; not part of encoding */ + SECItem response; /* an OCTET STRING */ + union { + ocspBasicOCSPResponse *basic; /* when type is id-pkix-ocsp-basic */ + } decodedResponse; /* local; not part of encoding */ +}; + +/* + * A BasicOCSPResponse -- when the responseType in a ResponseBytes is + * id-pkix-ocsp-basic, the "response" OCTET STRING above is the DER + * encoding of one of these. + * + * Note that in the OCSP specification, the signature fields are not + * part of a separate sub-structure. But since they are the same fields + * as we define for the signature in a request, it made sense to share + * the C data structure here and in some shared code to operate on them. + */ +struct ocspBasicOCSPResponseStr { + SECItem tbsResponseDataDER; + ocspResponseData *tbsResponseData; /* "tbs" == To Be Signed */ + ocspSignature responseSignature; +}; + +/* + * A ResponseData is the part of a BasicOCSPResponse that is signed + * (after it is DER encoded). It contains the real details of the response + * (a per-certificate status). + */ +struct ocspResponseDataStr { + SECItem version; /* an INTEGER */ + SECItem derResponderID; + ocspResponderID *responderID; /* local; not part of encoding */ + SECItem producedAt; /* a GeneralizedTime */ + CERTOCSPSingleResponse **responses; + CERTCertExtension **responseExtensions; +}; + +struct ocspResponderIDStr { + CERTOCSPResponderIDType responderIDType; /* local; not part of encoding */ + union { + CERTName name; /* when ocspResponderID_byName */ + SECItem keyHash; /* when ocspResponderID_byKey */ + SECItem other; /* when ocspResponderID_other */ + } responderIDValue; +}; + +/* + * The ResponseData in a BasicOCSPResponse contains a SEQUENCE OF + * SingleResponse -- one for each certificate whose status is being supplied. + * + * XXX figure out how to get rid of that arena -- there must be a way + */ +struct CERTOCSPSingleResponseStr { + PLArenaPool *arena; /* just a copy of the response arena, + * needed here for extension handling + * routines, on creation only */ + CERTOCSPCertID *certID; + SECItem derCertStatus; + ocspCertStatus *certStatus; /* local; not part of encoding */ + SECItem thisUpdate; /* a GeneralizedTime */ + SECItem *nextUpdate; /* a GeneralizedTime */ + CERTCertExtension **singleExtensions; +}; + +/* + * A CertStatus is the actual per-certificate status. Its ASN.1 definition: + * + * CertStatus ::= CHOICE { + * good [0] IMPLICIT NULL, + * revoked [1] IMPLICIT RevokedInfo, + * unknown [2] IMPLICIT UnknownInfo } + * + * (where for now UnknownInfo is defined to be NULL but in the + * future may be replaced with an enumeration). + * + * Because it is CHOICE, the status value and its associated information + * (if any) are actually encoded together. To represent this same + * information internally, we explicitly define a type and save it, + * along with the value, into a data structure. + */ + +typedef enum { + ocspCertStatus_good, /* cert is not revoked */ + ocspCertStatus_revoked, /* cert is revoked */ + ocspCertStatus_unknown, /* cert was unknown to the responder */ + ocspCertStatus_other /* status was not an expected value */ +} ocspCertStatusType; + +/* + * This is the actual per-certificate status. + * + * The "goodInfo" and "unknownInfo" items are only place-holders for a NULL. + * (Though someday OCSP may replace UnknownInfo with an enumeration that + * gives more detailed information.) + */ +struct ocspCertStatusStr { + ocspCertStatusType certStatusType; /* local; not part of encoding */ + union { + SECItem *goodInfo; /* when ocspCertStatus_good */ + ocspRevokedInfo *revokedInfo; /* when ocspCertStatus_revoked */ + SECItem *unknownInfo; /* when ocspCertStatus_unknown */ + SECItem *otherInfo; /* when ocspCertStatus_other */ + } certStatusInfo; +}; + +/* + * A RevokedInfo gives information about a revoked certificate -- when it + * was revoked and why. + */ +struct ocspRevokedInfoStr { + SECItem revocationTime; /* a GeneralizedTime */ + SECItem *revocationReason; /* a CRLReason; ignored for now */ +}; + +/* + * ServiceLocator can be included as one of the singleRequestExtensions. + * When added, it specifies the (name of the) issuer of the cert being + * checked, and optionally the value of the AuthorityInfoAccess extension + * if the cert has one. + */ +struct ocspServiceLocatorStr { + CERTName *issuer; + SECItem locator; /* DER encoded authInfoAccess extension from cert */ +}; + +#endif /* _OCSPTI_H_ */ diff --git a/security/nss/lib/certhigh/xcrldist.c b/security/nss/lib/certhigh/xcrldist.c new file mode 100644 index 0000000000..4f74cdb259 --- /dev/null +++ b/security/nss/lib/certhigh/xcrldist.c @@ -0,0 +1,212 @@ +/* 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/. */ + +/* + * Code for dealing with x.509 v3 CRL Distribution Point extension. + */ +#include "genname.h" +#include "certt.h" +#include "secerr.h" + +SEC_ASN1_MKSUB(SEC_AnyTemplate) +SEC_ASN1_MKSUB(SEC_BitStringTemplate) + +extern void PrepareBitStringForEncoding(SECItem *bitMap, SECItem *value); + +static const SEC_ASN1Template FullNameTemplate[] = { + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | 0, + offsetof(CRLDistributionPoint, derFullName), + CERT_GeneralNamesTemplate } +}; + +static const SEC_ASN1Template RelativeNameTemplate[] = { + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | 1, + offsetof(CRLDistributionPoint, distPoint.relativeName), + CERT_RDNTemplate } +}; + +static const SEC_ASN1Template DistributionPointNameTemplate[] = { + { SEC_ASN1_CHOICE, + offsetof(CRLDistributionPoint, distPointType), NULL, + sizeof(CRLDistributionPoint) }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | 0, + offsetof(CRLDistributionPoint, derFullName), + CERT_GeneralNamesTemplate, generalName }, + { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | 1, + offsetof(CRLDistributionPoint, distPoint.relativeName), + CERT_RDNTemplate, relativeDistinguishedName }, + { 0 } +}; + +static const SEC_ASN1Template CRLDistributionPointTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CRLDistributionPoint) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | SEC_ASN1_XTRN | 0, + offsetof(CRLDistributionPoint, derDistPoint), + SEC_ASN1_SUB(SEC_AnyTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_XTRN | 1, + offsetof(CRLDistributionPoint, bitsmap), + SEC_ASN1_SUB(SEC_BitStringTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | + SEC_ASN1_CONSTRUCTED | 2, + offsetof(CRLDistributionPoint, derCrlIssuer), + CERT_GeneralNamesTemplate }, + { 0 } +}; + +const SEC_ASN1Template CERTCRLDistributionPointsTemplate[] = { + { SEC_ASN1_SEQUENCE_OF, 0, CRLDistributionPointTemplate } +}; + +SECStatus +CERT_EncodeCRLDistributionPoints(PLArenaPool *arena, + CERTCrlDistributionPoints *value, + SECItem *derValue) +{ + CRLDistributionPoint **pointList, *point; + PLArenaPool *ourPool = NULL; + SECStatus rv = SECSuccess; + + PORT_Assert(derValue); + PORT_Assert(value && value->distPoints); + + do { + ourPool = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE); + if (ourPool == NULL) { + rv = SECFailure; + break; + } + + pointList = value->distPoints; + while (*pointList) { + point = *pointList; + point->derFullName = NULL; + point->derDistPoint.data = NULL; + + switch (point->distPointType) { + case generalName: + point->derFullName = cert_EncodeGeneralNames(ourPool, point->distPoint.fullName); + + if (!point->derFullName || + !SEC_ASN1EncodeItem(ourPool, &point->derDistPoint, + point, FullNameTemplate)) + rv = SECFailure; + break; + + case relativeDistinguishedName: + if (!SEC_ASN1EncodeItem(ourPool, &point->derDistPoint, + point, RelativeNameTemplate)) + rv = SECFailure; + break; + + default: + PORT_SetError(SEC_ERROR_EXTENSION_VALUE_INVALID); + rv = SECFailure; + break; + } + + if (rv != SECSuccess) + break; + + if (point->reasons.data) + PrepareBitStringForEncoding(&point->bitsmap, &point->reasons); + + if (point->crlIssuer) { + point->derCrlIssuer = cert_EncodeGeneralNames(ourPool, point->crlIssuer); + if (!point->derCrlIssuer) { + rv = SECFailure; + break; + } + } + ++pointList; + } + if (rv != SECSuccess) + break; + if (!SEC_ASN1EncodeItem(arena, derValue, value, + CERTCRLDistributionPointsTemplate)) { + rv = SECFailure; + break; + } + } while (0); + PORT_FreeArena(ourPool, PR_FALSE); + return rv; +} + +CERTCrlDistributionPoints * +CERT_DecodeCRLDistributionPoints(PLArenaPool *arena, SECItem *encodedValue) +{ + CERTCrlDistributionPoints *value = NULL; + CRLDistributionPoint **pointList, *point; + SECStatus rv = SECSuccess; + SECItem newEncodedValue; + + PORT_Assert(arena); + do { + value = PORT_ArenaZNew(arena, CERTCrlDistributionPoints); + if (value == NULL) { + rv = SECFailure; + break; + } + + /* copy the DER into the arena, since Quick DER returns data that points + into the DER input, which may get freed by the caller */ + rv = SECITEM_CopyItem(arena, &newEncodedValue, encodedValue); + if (rv != SECSuccess) + break; + + rv = SEC_QuickDERDecodeItem(arena, &value->distPoints, + CERTCRLDistributionPointsTemplate, &newEncodedValue); + if (rv != SECSuccess) + break; + + pointList = value->distPoints; + while (NULL != (point = *pointList)) { + + /* get the data if the distributionPointName is not omitted */ + if (point->derDistPoint.data != NULL) { + rv = SEC_QuickDERDecodeItem(arena, point, + DistributionPointNameTemplate, &(point->derDistPoint)); + if (rv != SECSuccess) + break; + + switch (point->distPointType) { + case generalName: + point->distPoint.fullName = + cert_DecodeGeneralNames(arena, point->derFullName); + rv = point->distPoint.fullName ? SECSuccess : SECFailure; + break; + + case relativeDistinguishedName: + break; + + default: + PORT_SetError(SEC_ERROR_EXTENSION_VALUE_INVALID); + rv = SECFailure; + break; + } /* end switch */ + if (rv != SECSuccess) + break; + } /* end if */ + + /* Get the reason code if it's not omitted in the encoding */ + if (point->bitsmap.data != NULL) { + SECItem bitsmap = point->bitsmap; + DER_ConvertBitString(&bitsmap); + rv = SECITEM_CopyItem(arena, &point->reasons, &bitsmap); + if (rv != SECSuccess) + break; + } + + /* Get the crl issuer name if it's not omitted in the encoding */ + if (point->derCrlIssuer != NULL) { + point->crlIssuer = cert_DecodeGeneralNames(arena, + point->derCrlIssuer); + if (!point->crlIssuer) + break; + } + ++pointList; + } /* end while points remain */ + } while (0); + return (rv == SECSuccess ? value : NULL); +} |