diff options
Diffstat (limited to 'security/nss/lib/certhigh')
-rw-r--r-- | security/nss/lib/certhigh/Makefile | 76 | ||||
-rw-r--r-- | security/nss/lib/certhigh/certhigh.c | 1194 | ||||
-rw-r--r-- | security/nss/lib/certhigh/certhtml.c | 605 | ||||
-rw-r--r-- | security/nss/lib/certhigh/certreq.c | 230 | ||||
-rw-r--r-- | security/nss/lib/certhigh/certvfy.c | 2111 | ||||
-rw-r--r-- | security/nss/lib/certhigh/config.mk | 43 | ||||
-rw-r--r-- | security/nss/lib/certhigh/crlv2.c | 126 | ||||
-rw-r--r-- | security/nss/lib/certhigh/manifest.mn | 59 | ||||
-rw-r--r-- | security/nss/lib/certhigh/ocsp.c | 3998 | ||||
-rw-r--r-- | security/nss/lib/certhigh/ocsp.h | 534 | ||||
-rw-r--r-- | security/nss/lib/certhigh/ocspt.h | 59 | ||||
-rw-r--r-- | security/nss/lib/certhigh/ocspti.h | 405 | ||||
-rw-r--r-- | security/nss/lib/certhigh/xcrldist.c | 230 |
13 files changed, 9670 insertions, 0 deletions
diff --git a/security/nss/lib/certhigh/Makefile b/security/nss/lib/certhigh/Makefile new file mode 100644 index 000000000..08b7137d5 --- /dev/null +++ b/security/nss/lib/certhigh/Makefile @@ -0,0 +1,76 @@ +#! gmake +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Netscape security libraries. +# +# The Initial Developer of the Original Code is Netscape +# Communications Corporation. Portions created by Netscape are +# Copyright (C) 1994-2000 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the +# terms of the GNU General Public License Version 2 or later (the +# "GPL"), in which case the provisions of the GPL are applicable +# instead of those above. If you wish to allow use of your +# version of this file only under the terms of the GPL and not to +# allow others to use your version of this file under the MPL, +# indicate your decision by deleting the provisions above and +# replace them with the notice and other provisions required by +# the GPL. If you do not delete the provisions above, a recipient +# may use your version of this file under either the MPL or the +# GPL. +# + +####################################################################### +# (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). # +####################################################################### + +-include config.mk + +####################################################################### +# (5) Execute "global" rules. (OPTIONAL) # +####################################################################### + +include $(CORE_DEPTH)/coreconf/rules.mk + +####################################################################### +# (6) Execute "component" rules. (OPTIONAL) # +####################################################################### + + + +####################################################################### +# (7) Execute "local" rules. (OPTIONAL). # +####################################################################### + +export:: private_export + diff --git a/security/nss/lib/certhigh/certhigh.c b/security/nss/lib/certhigh/certhigh.c new file mode 100644 index 000000000..5d8c59a9f --- /dev/null +++ b/security/nss/lib/certhigh/certhigh.c @@ -0,0 +1,1194 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ +#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" + +#ifndef NSS_3_4_CODE +#define NSS_3_4_CODE +#endif +#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; + char *token = NULL; + int len; + + /* 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) { + token=name1; + nickname1=token1; + nickname2=name2; + } else { + token=name2; + nickname1=token2; + nickname2=name1; + } + len = nickname1-token; + nickname1++; + if (PORT_Strcmp(nickname1,nickname2) != 0) { + return PR_FALSE; + } + /* 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; + int64 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; + + 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, + char *nickname, + SECCertUsage usage, + PRBool validOnly, + void *proto_win) +{ + CERTCertificate *cert = NULL; + CERTCertList *certList = NULL; + SECStatus rv; + int64 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 ) { + /* 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_END(CERT_LIST_HEAD(certList), 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 ) { + 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 ) { + return(PR_FAILURE); + } + + /* copy the string */ + len = PORT_Strlen(nickname) + 1; + node->string = (char*)PORT_ArenaAlloc(names->arena, len); + if ( node->string == NULL ) { + if (nickname) 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) +{ + PRArenaPool *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, 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->trust ) { + trust = cert->trust; + + /* 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_GetSSLCACerts(CERTCertDBHandle *handle) +{ + PRArenaPool *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_DistNamesFromNicknames(CERTCertDBHandle *handle, char **nicknames, + int nnames) +{ + CERTDistNames *dnames = NULL; + PRArenaPool *arena; + int i, rv; + SECItem *names = NULL; + CERTCertificate *cert = NULL; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) goto loser; + dnames = (CERTDistNames*)PORT_Alloc(sizeof(CERTDistNames)); + if (dnames == NULL) goto loser; + + dnames->arena = arena; + dnames->nnames = nnames; + dnames->names = names = (SECItem*)PORT_Alloc(nnames * sizeof(SECItem)); + 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; + PRArenaPool *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 ) { + goto loser; + } + + 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; + + encodedExtenValue.data = NULL; + encodedExtenValue.len = 0; + + rv = cert_FindExtension(cert->extensions, SEC_OID_X509_CRL_DIST_POINTS, + &encodedExtenValue); + if ( rv != SECSuccess ) { + return (NULL); + } + + return (CERT_DecodeCRLDistributionPoints (cert->arena, + &encodedExtenValue)); +} + +/* 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; + + 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 */ + if (cert->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; + } + + CERT_DestroyCertificate(cert); + cert = NULL; + + 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) +{ +#ifdef NSS_CLASSIC + CERTCertificateList *chain = NULL; + CERTCertificate *c; + SECItem *p; + int rv, len = 0; + PRArenaPool *tmpArena, *arena; + certNode *head, *tail, *node; + + /* + * Initialize stuff so we can goto loser. + */ + head = NULL; + arena = NULL; + + /* arena for linked list */ + tmpArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (tmpArena == NULL) goto no_memory; + + /* arena for SecCertificateList */ + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) goto no_memory; + + head = tail = (certNode*)PORT_ArenaZAlloc(tmpArena, sizeof(certNode)); + if (head == NULL) goto no_memory; + + /* put primary cert first in the linked list */ + head->cert = c = CERT_DupCertificate(cert); + if (head->cert == NULL) goto loser; + len++; + + /* add certs until we come to a self-signed one */ + while(SECITEM_CompareItem(&c->derIssuer, &c->derSubject) != SECEqual) { + c = CERT_FindCertIssuer(tail->cert, PR_Now(), usage); + if (c == NULL) { + /* no root is found, so make sure we don't attempt to delete one + * below + */ + includeRoot = PR_TRUE; + break; + } + + tail->next = (certNode*)PORT_ArenaZAlloc(tmpArena, sizeof(certNode)); + tail = tail->next; + if (tail == NULL) goto no_memory; + + tail->cert = c; + len++; + } + + /* now build the CERTCertificateList */ + chain = (CERTCertificateList *)PORT_ArenaAlloc(arena, sizeof(CERTCertificateList)); + if (chain == NULL) goto no_memory; + chain->certs = (SECItem*)PORT_ArenaAlloc(arena, len * sizeof(SECItem)); + if (chain->certs == NULL) goto no_memory; + + for(node = head, p = chain->certs; node; node = node->next, p++) { + rv = SECITEM_CopyItem(arena, p, &node->cert->derCert); + CERT_DestroyCertificate(node->cert); + node->cert = NULL; + if (rv < 0) goto loser; + } + if ( !includeRoot && len > 1) { + chain->len = len - 1; + } else { + chain->len = len; + } + + chain->arena = arena; + + PORT_FreeArena(tmpArena, PR_FALSE); + + return chain; + +no_memory: + PORT_SetError(SEC_ERROR_NO_MEMORY); +loser: + if (head != NULL) { + for (node = head; node; node = node->next) { + if (node->cert != NULL) + CERT_DestroyCertificate(node->cert); + } + } + + if (arena != NULL) { + PORT_FreeArena(arena, PR_FALSE); + } + + if (tmpArena != NULL) { + PORT_FreeArena(tmpArena, PR_FALSE); + } + + return NULL; +#else + CERTCertificateList *chain = NULL; + NSSCertificate **stanChain; + NSSCertificate *stanCert; + PRArenaPool *arena; + NSSUsage nssUsage; + int i, len; + + stanCert = STAN_GetNSSCertificate(cert); + nssUsage.anyUsage = PR_FALSE; + nssUsage.nss3usage = usage; + nssUsage.nss3lookingForCA = PR_FALSE; + stanChain = NSSCertificate_BuildChain(stanCert, NULL, &nssUsage, NULL, + NULL, 0, NULL, NULL); + if (!stanChain) { + 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; + SECITEM_CopyItem(arena, &chain->certs[i], &derCert); + 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; +#endif +} + +/* 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; + PRArenaPool *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(CERTCertificateList * oldList) +{ + CERTCertificateList *newList = NULL; + PRArenaPool *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/certhtml.c b/security/nss/lib/certhigh/certhtml.c new file mode 100644 index 000000000..e4e1215c2 --- /dev/null +++ b/security/nss/lib/certhigh/certhtml.c @@ -0,0 +1,605 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * certhtml.c --- convert a cert to html + * + * $Id$ + */ + +#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; +} + +static char * +gatherStrings(char **strings) +{ + char **strs; + int len; + char *ret; + char *s; + + /* find total length of all strings */ + strs = strings; + len = 0; + while ( *strs ) { + len += PORT_Strlen(*strs); + strs++; + } + + /* alloc enough memory for it */ + ret = (char*)PORT_Alloc(len + 1); + if ( !ret ) { + return(ret); + } + + s = ret; + + /* copy the strings */ + strs = strings; + while ( *strs ) { + PORT_Strcpy(s, *strs); + s += PORT_Strlen(*strs); + strs++; + } + + return( ret ); +} + +#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: + cn = CERT_DecodeAVAValue(&ava->value); + len += cn->len; + break; + case SEC_OID_AVA_COUNTRY_NAME: + country = CERT_DecodeAVAValue(&ava->value); + len += country->len; + break; + case SEC_OID_AVA_LOCALITY: + loc = CERT_DecodeAVAValue(&ava->value); + len += loc->len; + break; + case SEC_OID_AVA_STATE_OR_PROVINCE: + state = CERT_DecodeAVAValue(&ava->value); + len += state->len; + break; + case SEC_OID_AVA_ORGANIZATION_NAME: + org = CERT_DecodeAVAValue(&ava->value); + len += org->len; + break; + case SEC_OID_AVA_DN_QUALIFIER: + dq = CERT_DecodeAVAValue(&ava->value); + len += dq->len; + break; + case SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME: + if (ou_count < MAX_OUS) { + orgunit[ou_count] = CERT_DecodeAVAValue(&ava->value); + len += orgunit[ou_count++]->len; + } + break; + case SEC_OID_AVA_DC: + if (dc_count < MAX_DC) { + dc[dc_count] = CERT_DecodeAVAValue(&ava->value); + len += dc[dc_count++]->len; + } + break; + case SEC_OID_PKCS9_EMAIL_ADDRESS: + case SEC_OID_RFC1274_MAIL: + email = CERT_DecodeAVAValue(&ava->value); + len += email->len; + break; + default: + break; + } + } + } + + /* XXX - add some for formatting */ + len += 128; + + /* allocate buffer */ + buf = (char *)PORT_Alloc(len); + if ( !buf ) { + return(0); + } + + tmpbuf = buf; + + if ( cn ) { + PORT_Memcpy(tmpbuf, cn->data, cn->len); + tmpbuf += cn->len; + PORT_Memcpy(tmpbuf, BREAK, BREAKLEN); + tmpbuf += BREAKLEN; + SECITEM_FreeItem(cn, PR_TRUE); + } + if ( email ) { + PORT_Memcpy(tmpbuf, email->data, email->len); + tmpbuf += ( email->len ); + PORT_Memcpy(tmpbuf, BREAK, BREAKLEN); + tmpbuf += BREAKLEN; + SECITEM_FreeItem(email, PR_TRUE); + } + 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; + SECITEM_FreeItem(orgunit[i], PR_TRUE); + } + if ( dq ) { + PORT_Memcpy(tmpbuf, dq->data, dq->len); + tmpbuf += ( dq->len ); + PORT_Memcpy(tmpbuf, BREAK, BREAKLEN); + tmpbuf += BREAKLEN; + SECITEM_FreeItem(dq, PR_TRUE); + } + if ( org ) { + PORT_Memcpy(tmpbuf, org->data, org->len); + tmpbuf += ( org->len ); + PORT_Memcpy(tmpbuf, BREAK, BREAKLEN); + tmpbuf += BREAKLEN; + SECITEM_FreeItem(org, PR_TRUE); + } + 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; + SECITEM_FreeItem(dc[i], PR_TRUE); + } + first = PR_TRUE; + if ( loc ) { + PORT_Memcpy(tmpbuf, loc->data, loc->len); + tmpbuf += ( loc->len ); + first = PR_FALSE; + SECITEM_FreeItem(loc, PR_TRUE); + } + if ( state ) { + if ( !first ) { + PORT_Memcpy(tmpbuf, COMMA, COMMALEN); + tmpbuf += COMMALEN; + } + PORT_Memcpy(tmpbuf, state->data, state->len); + tmpbuf += ( state->len ); + first = PR_FALSE; + SECITEM_FreeItem(state, PR_TRUE); + } + if ( country ) { + if ( !first ) { + PORT_Memcpy(tmpbuf, COMMA, COMMALEN); + tmpbuf += COMMALEN; + } + PORT_Memcpy(tmpbuf, country->data, country->len); + tmpbuf += ( country->len ); + first = PR_FALSE; + SECITEM_FreeItem(country, PR_TRUE); + } + if ( !first ) { + PORT_Memcpy(tmpbuf, BREAK, BREAKLEN); + tmpbuf += BREAKLEN; + } + + *tmpbuf = 0; + + return(buf); +} + +static char *sec_FortezzaClearance(SECItem *clearance) { + unsigned char clr = 0; + + if (clearance->len > 0) { clr = clearance->data[0]; } + + if (clr & 0x4) return "Top Secret"; + if (clr & 0x8) return "Secret"; + if (clr & 0x10) return "Confidential"; + if (clr & 0x20) return "Sensitive"; + if (clr & 0x40) return "Unclassified"; + return "None"; +} + +static char *sec_FortezzaMessagePrivilege(SECItem *priv) { + unsigned char clr = 0; + + if (priv->len > 0) { clr = (priv->data[0]) & 0x78; } + + if (clr == 0x00) { + return "None"; + } else { + + return PR_smprintf("%s%s%s%s%s%s%s", + + clr&0x40?"Critical/Flash":"", + (clr&0x40) && (clr&0x38) ? ", " : "" , + + clr&0x20?"Immediate/Priority":"", + (clr&0x20) && (clr&0x18) ? ", " : "" , + + clr&0x10?"Routine/Deferred":"", + (clr&0x10) && (clr&0x08) ? ", " : "" , + + clr&0x08?"Rekey Agent":""); + } + +} + +static char *sec_FortezzaCertPrivilege(SECItem *priv) { + unsigned char clr = 0; + + if (priv->len > 0) { clr = priv->data[0]; } + + return PR_smprintf("%s%s%s%s%s%s%s%s%s%s%s%s", + clr&0x40?"Organizational Releaser":"", + (clr&0x40) && (clr&0x3e) ? "," : "" , + clr&0x20?"Policy Creation Authority":"", + (clr&0x20) && (clr&0x1e) ? "," : "" , + clr&0x10?"Certificate Authority":"", + (clr&0x10) && (clr&0x0e) ? "," : "" , + clr&0x08?"Local Managment Authority":"", + (clr&0x08) && (clr&0x06) ? "," : "" , + clr&0x04?"Configuration Vector Authority":"", + (clr&0x04) && (clr&0x02) ? "," : "" , + clr&0x02?"No Signature Capability":"", + clr&0x7e?"":"Signing Only" + ); +} + +static char *htmlcertstrings[] = { + "<table border=0 cellspacing=0 cellpadding=0><tr><td valign=top>" + "<font size=2><b>This Certificate belongs to:</b><br>" + "<table border=0 cellspacing=0 cellpadding=0><tr><td>", + 0, /* image goes here */ + 0, + 0, + "</td><td width=10> </td><td><font size=2>", + 0, /* subject name goes here */ + "</td></tr></table></font></td><td width=20> </td><td valign=top>" + "<font size=2><b>This Certificate was issued by:</b><br>" + "<table border=0 cellspacing=0 cellpadding=0><tr><td>", + 0, /* image goes here */ + 0, + 0, + "</td><td width=10> </td><td><font size=2>", + 0, /* issuer name goes here */ + "</td></tr></table></font></td></tr></table>" + "<b>Serial Number:</b> ", + 0, + "<br><b>This Certificate is valid from ", + 0, /* notBefore goes here */ + " to ", + 0, /* notAfter does here */ + "</b><br><b>Clearance:</b>", + 0, + "<br><b>DSS Privileges:</b>", + 0, + "<br><b>KEA Privileges:</b>", + 0, + "<br><b>KMID:</b>", + 0, + "<br><b>Certificate Fingerprint:</b>" + "<table border=0 cellspacing=0 cellpadding=0><tr>" + "<td width=10> </td><td><font size=2>", + 0, /* fingerprint goes here */ + "</td></tr></table>", + 0, /* comment header goes here */ + 0, /* comment goes here */ + 0, /* comment trailer goes here */ + 0 +}; + +char * +CERT_HTMLCertInfo(CERTCertificate *cert, PRBool showImages, PRBool showIssuer) +{ + SECStatus rv; + char *issuer, *subject, *serialNumber, *version; + char *notBefore, *notAfter; + char *ret; + char *nickname; + SECItem dummyitem; + unsigned char fingerprint[16]; /* result of MD5, always 16 bytes */ + char *fpstr; + SECItem fpitem; + char *commentstring = NULL; + SECKEYPublicKey *pubk; + char *DSSPriv; + char *KMID = NULL; + char *servername; + + if (!cert) { + return(0); + } + + issuer = CERT_FormatName (&cert->issuer); + subject = CERT_FormatName (&cert->subject); + version = CERT_Hexify (&cert->version,1); + serialNumber = CERT_Hexify (&cert->serialNumber,1); + notBefore = DER_TimeChoiceDayToAscii(&cert->validity.notBefore); + notAfter = DER_TimeChoiceDayToAscii(&cert->validity.notAfter); + servername = CERT_FindNSStringExtension(cert, + SEC_OID_NS_CERT_EXT_SSL_SERVER_NAME); + + nickname = cert->nickname; + if ( nickname == NULL ) { + showImages = PR_FALSE; + } + + dummyitem.data = NULL; + rv = CERT_FindCertExtension(cert, SEC_OID_NS_CERT_EXT_SUBJECT_LOGO, + &dummyitem); + if ( dummyitem.data ) { + PORT_Free(dummyitem.data); + } + + if ( rv || !showImages ) { + htmlcertstrings[1] = ""; + htmlcertstrings[2] = ""; + htmlcertstrings[3] = ""; + } else { + htmlcertstrings[1] = "<img src=\"about:security?subject-logo="; + htmlcertstrings[2] = nickname; + htmlcertstrings[3] = "\">"; + } + + if ( servername ) { + char *tmpstr; + tmpstr = (char *)PORT_Alloc(PORT_Strlen(subject) + + PORT_Strlen(servername) + + sizeof("<br>") + 1); + if ( tmpstr ) { + PORT_Strcpy(tmpstr, servername); + PORT_Strcat(tmpstr, "<br>"); + PORT_Strcat(tmpstr, subject); + PORT_Free(subject); + subject = tmpstr; + } + } + + htmlcertstrings[5] = subject; + + dummyitem.data = NULL; + + rv = CERT_FindCertExtension(cert, SEC_OID_NS_CERT_EXT_ISSUER_LOGO, + &dummyitem); + if ( dummyitem.data ) { + PORT_Free(dummyitem.data); + } + + if ( rv || !showImages ) { + htmlcertstrings[7] = ""; + htmlcertstrings[8] = ""; + htmlcertstrings[9] = ""; + } else { + htmlcertstrings[7] = "<img src=\"about:security?issuer-logo="; + htmlcertstrings[8] = nickname; + htmlcertstrings[9] = "\">"; + } + + + if (showIssuer == PR_TRUE) { + htmlcertstrings[11] = issuer; + } else { + htmlcertstrings[11] = ""; + } + + htmlcertstrings[13] = serialNumber; + htmlcertstrings[15] = notBefore; + htmlcertstrings[17] = notAfter; + + pubk = CERT_ExtractPublicKey(cert); + DSSPriv = NULL; + if (pubk && (pubk->keyType == fortezzaKey)) { + htmlcertstrings[18] = "</b><br><b>Clearance:</b>"; + htmlcertstrings[19] = sec_FortezzaClearance( + &pubk->u.fortezza.clearance); + htmlcertstrings[20] = "<br><b>DSS Privileges:</b>"; + DSSPriv = sec_FortezzaCertPrivilege( + &pubk->u.fortezza.DSSpriviledge); + htmlcertstrings[21] = DSSPriv; + htmlcertstrings[22] = "<br><b>KEA Privileges:</b>"; + htmlcertstrings[23] = sec_FortezzaMessagePrivilege( + &pubk->u.fortezza.KEApriviledge); + htmlcertstrings[24] = "<br><b>KMID:</b>"; + dummyitem.data = &pubk->u.fortezza.KMID[0]; + dummyitem.len = sizeof(pubk->u.fortezza.KMID); + KMID = CERT_Hexify (&dummyitem,0); + htmlcertstrings[25] = KMID; + } else { + /* clear out the headers in the non-fortezza cases */ + htmlcertstrings[18] = ""; + htmlcertstrings[19] = ""; + htmlcertstrings[20] = ""; + htmlcertstrings[21] = ""; + htmlcertstrings[22] = ""; + htmlcertstrings[23] = ""; + htmlcertstrings[24] = ""; + htmlcertstrings[25] = "</b>"; + } + + if (pubk) { + SECKEY_DestroyPublicKey(pubk); + } + +#define HTML_OFF 27 + rv = PK11_HashBuf(SEC_OID_MD5, fingerprint, + cert->derCert.data, cert->derCert.len); + + fpitem.data = fingerprint; + fpitem.len = sizeof(fingerprint); + + fpstr = CERT_Hexify (&fpitem,1); + + htmlcertstrings[HTML_OFF] = fpstr; + + commentstring = CERT_GetCertCommentString(cert); + + if (commentstring == NULL) { + htmlcertstrings[HTML_OFF+2] = ""; + htmlcertstrings[HTML_OFF+3] = ""; + htmlcertstrings[HTML_OFF+4] = ""; + } else { + htmlcertstrings[HTML_OFF+2] = + "<b>Comment:</b>" + "<table border=0 cellspacing=0 cellpadding=0><tr>" + "<td width=10> </td><td><font size=3>" + "<textarea name=foobar rows=4 cols=55 onfocus=\"this.blur()\">"; + htmlcertstrings[HTML_OFF+3] = commentstring; + htmlcertstrings[HTML_OFF+4] = "</textarea></font></td></tr></table>"; + } + + ret = gatherStrings(htmlcertstrings); + + if ( issuer ) { + PORT_Free(issuer); + } + + if ( subject ) { + PORT_Free(subject); + } + + if ( version ) { + PORT_Free(version); + } + + if ( serialNumber ) { + PORT_Free(serialNumber); + } + + if ( notBefore ) { + PORT_Free(notBefore); + } + + if ( notAfter ) { + PORT_Free(notAfter); + } + + if ( fpstr ) { + PORT_Free(fpstr); + } + if (DSSPriv) { + PORT_Free(DSSPriv); + } + + if (KMID) { + PORT_Free(KMID); + } + + if ( commentstring ) { + PORT_Free(commentstring); + } + + if ( servername ) { + PORT_Free(servername); + } + + return(ret); +} + diff --git a/security/nss/lib/certhigh/certreq.c b/security/nss/lib/certhigh/certreq.c new file mode 100644 index 000000000..1588c1896 --- /dev/null +++ b/security/nss/lib/certhigh/certreq.c @@ -0,0 +1,230 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +#include "cert.h" +#include "secder.h" +#include "key.h" +#include "secitem.h" +#include "secasn1.h" + +const SEC_ASN1Template CERT_AttributeTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(CERTAttribute) }, + { SEC_ASN1_OBJECT_ID, offsetof(CERTAttribute, attrType) }, + { SEC_ASN1_SET_OF, offsetof(CERTAttribute, attrValue), + 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; + PRArenaPool *arena; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + + if ( !arena ) { + return(0); + } + + c = (CERTCertificate *)PORT_ArenaZAlloc(arena, sizeof(CERTCertificate)); + + if (c) { + 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; +} + +/************************************************************************/ + +CERTCertificateRequest * +CERT_CreateCertificateRequest(CERTName *subject, + CERTSubjectPublicKeyInfo *spki, + SECItem **attributes) +{ + CERTCertificateRequest *certreq; + PRArenaPool *arena; + SECStatus rv; + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if ( arena == NULL ) { + return NULL; + } + + certreq = (CERTCertificateRequest *) + PORT_ArenaZAlloc(arena, sizeof(CERTCertificateRequest)); + + if (certreq != NULL) { + 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; + + /* Copy over attribute information */ + if (attributes) { + int i = 0; + + /* allocate space for attributes */ + while(attributes[i] != NULL) i++; + certreq->attributes = (SECItem**)PORT_ArenaZAlloc(arena, + sizeof(SECItem *) * (i + 1)); + if(!certreq->attributes) { + goto loser; + } + + /* copy attributes */ + i = 0; + while(attributes[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. + */ + certreq->attributes[i] = (SECItem*)PORT_ArenaZAlloc(arena, + sizeof(SECItem)); + if(!certreq->attributes[i]) { + goto loser; + }; + rv = SECITEM_CopyItem(arena, certreq->attributes[i], + attributes[i]); + if (rv != SECSuccess) { + goto loser; + } + i++; + } + certreq->attributes[i] = NULL; + } else { + /* + ** 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 = (SECItem**)PORT_ArenaZAlloc(arena, sizeof(SECItem *)); + if(!certreq->attributes) { + goto loser; + } + certreq->attributes[0] = NULL; + } + } else { + PORT_FreeArena(arena, PR_FALSE); + } + + return certreq; + +loser: + CERT_DestroyCertificateRequest(certreq); + return NULL; +} + +void +CERT_DestroyCertificateRequest(CERTCertificateRequest *req) +{ + if (req && req->arena) { + PORT_FreeArena(req->arena, PR_FALSE); + } + return; +} + diff --git a/security/nss/lib/certhigh/certvfy.c b/security/nss/lib/certhigh/certvfy.c new file mode 100644 index 000000000..21b9a439e --- /dev/null +++ b/security/nss/lib/certhigh/certvfy.c @@ -0,0 +1,2111 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ +#include "nspr.h" +#include "secerr.h" +#include "secport.h" +#include "seccomon.h" +#include "secoid.h" +#include "sslerr.h" +#include "genname.h" +#include "keyhi.h" +#include "cert.h" +#include "certdb.h" +#include "cryptohi.h" + +#ifndef NSS_3_4_CODE +#define NSS_3_4_CODE +#endif /* NSS_3_4_CODE */ +#include "nsspki.h" +#include "pkitm.h" +#include "pkim.h" +#include "pki3hack.h" +#include "base.h" + +#define PENDING_SLOP (24L*60L*60L) + +/* + * WARNING - this function is depricated, and will go away in the near future. + * It has been superseded by CERT_CheckCertValidTimes(). + * + * Check the validity times of a certificate + */ +SECStatus +CERT_CertTimesValid(CERTCertificate *c) +{ + int64 now, notBefore, notAfter, pendingSlop; + SECStatus rv; + + /* if cert is already marked OK, then don't bother to check */ + if ( c->timeOK ) { + return(SECSuccess); + } + + /* get current time */ + now = PR_Now(); + rv = CERT_GetCertTimes(c, ¬Before, ¬After); + + if (rv) { + return(SECFailure); + } + + LL_I2L(pendingSlop, PENDING_SLOP); + LL_SUB(notBefore, notBefore, pendingSlop); + + if (LL_CMP(now, <, notBefore) || LL_CMP(now, >, notAfter)) { + PORT_SetError(SEC_ERROR_EXPIRED_CERTIFICATE); + return(SECFailure); + } + + return(SECSuccess); +} + +/* + * verify the signature of a signed data object with the given DER publickey + */ +SECStatus +CERT_VerifySignedDataWithPublicKey(CERTSignedData *sd, + SECKEYPublicKey *pubKey, + void *wincx) +{ + SECStatus rv; + SECOidTag algid; + SECItem sig; + + if ( !pubKey || !sd ) { + PORT_SetError(PR_INVALID_ARGUMENT_ERROR); + return SECFailure; + } + + /* check the signature */ + sig = sd->signature; + /* convert sig->len from bit counts to byte count. */ + DER_ConvertBitString(&sig); + + algid = SECOID_GetAlgorithmTag(&sd->signatureAlgorithm); + rv = VFY_VerifyData(sd->data.data, sd->data.len, pubKey, &sig, + algid, wincx); + + return rv ? SECFailure : 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, + int64 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; +} + + +/* + * This must only be called on a cert that is known to have an issuer + * with an invalid time + */ +CERTCertificate * +CERT_FindExpiredIssuer(CERTCertDBHandle *handle, CERTCertificate *cert) +{ + CERTCertificate *issuerCert = NULL; + CERTCertificate *subjectCert; + int count; + SECStatus rv; + SECComparison rvCompare; + + subjectCert = CERT_DupCertificate(cert); + if ( subjectCert == NULL ) { + goto loser; + } + + for ( count = 0; count < CERT_MAX_CERT_CHAIN; count++ ) { + /* find the certificate of the issuer */ + issuerCert = CERT_FindCertByName(handle, &subjectCert->derIssuer); + + if ( ! issuerCert ) { + goto loser; + } + + rv = CERT_CertTimesValid(issuerCert); + if ( rv == SECFailure ) { + /* this is the invalid issuer */ + CERT_DestroyCertificate(subjectCert); + return(issuerCert); + } + + /* make sure that the issuer is not self signed. If it is, then + * stop here to prevent looping. + */ + rvCompare = SECITEM_CompareItem(&issuerCert->derSubject, + &issuerCert->derIssuer); + if (rvCompare == SECEqual) { + PORT_Assert(0); /* No expired issuer! */ + goto loser; + } + CERT_DestroyCertificate(subjectCert); + subjectCert = issuerCert; + } + +loser: + if ( issuerCert ) { + CERT_DestroyCertificate(issuerCert); + } + + if ( subjectCert ) { + CERT_DestroyCertificate(subjectCert); + } + + return(NULL); +} + +/* Software FORTEZZA installation hack. The software fortezza installer does + * not have access to the krl and cert.db file. Accept FORTEZZA Certs without + * KRL's in this case. + */ +static int dont_use_krl = 0; +/* not a public exposed function... */ +void sec_SetCheckKRLState(int value) { dont_use_krl = value; } + +SECStatus +SEC_CheckKRL(CERTCertDBHandle *handle,SECKEYPublicKey *key, + CERTCertificate *rootCert, int64 t, void * wincx) +{ + CERTSignedCrl *crl = NULL; + SECStatus rv = SECFailure; + SECStatus rv2; + CERTCrlEntry **crlEntry; + SECCertTimeValidity validity; + CERTCertificate *issuerCert = NULL; + + if (dont_use_krl) return SECSuccess; + + /* first look up the KRL */ + crl = SEC_FindCrlByName(handle,&rootCert->derSubject, SEC_KRL_TYPE); + if (crl == NULL) { + PORT_SetError(SEC_ERROR_NO_KRL); + goto done; + } + + /* get the issuing certificate */ + issuerCert = CERT_FindCertByName(handle, &crl->crl.derName); + if (issuerCert == NULL) { + PORT_SetError(SEC_ERROR_KRL_BAD_SIGNATURE); + goto done; + } + + + /* now verify the KRL signature */ + rv2 = CERT_VerifySignedData(&crl->signatureWrap, issuerCert, t, wincx); + if (rv2 != SECSuccess) { + PORT_SetError(SEC_ERROR_KRL_BAD_SIGNATURE); + goto done; + } + + /* Verify the date validity of the KRL */ + validity = SEC_CheckCrlTimes(&crl->crl, t); + if (validity == secCertTimeExpired) { + PORT_SetError(SEC_ERROR_KRL_EXPIRED); + goto done; + } + + /* now make sure the key in this cert is still valid */ + if (key->keyType != fortezzaKey) { + PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN); + goto done; /* This should be an assert? */ + } + + /* now make sure the key is not on the revocation list */ + for (crlEntry = crl->crl.entries; crlEntry && *crlEntry; crlEntry++) { + if (PORT_Memcmp((*crlEntry)->serialNumber.data, + key->u.fortezza.KMID, + (*crlEntry)->serialNumber.len) == 0) { + PORT_SetError(SEC_ERROR_REVOKED_KEY); + goto done; + } + } + rv = SECSuccess; + +done: + if (issuerCert) CERT_DestroyCertificate(issuerCert); + if (crl) SEC_DestroyCrl(crl); + return rv; +} + +SECStatus +SEC_CheckCRL(CERTCertDBHandle *handle,CERTCertificate *cert, + CERTCertificate *caCert, int64 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, int64 validTime, SECCertUsage usage) +{ +#ifdef NSS_CLASSIC + CERTAuthKeyID * authorityKeyID = NULL; + CERTCertificate * issuerCert = NULL; + SECItem * caName; + PRArenaPool *tmpArena = NULL; + + tmpArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + + if ( !tmpArena ) { + goto loser; + } + authorityKeyID = CERT_FindAuthKeyIDExten(tmpArena,cert); + + if ( authorityKeyID != NULL ) { + /* has the authority key ID extension */ + if ( authorityKeyID->keyID.data != NULL ) { + /* extension contains a key ID, so lookup based on it */ + issuerCert = CERT_FindCertByKeyID(cert->dbhandle, &cert->derIssuer, + &authorityKeyID->keyID); + if ( issuerCert == NULL ) { + PORT_SetError (SEC_ERROR_UNKNOWN_ISSUER); + goto loser; + } + + } else if ( authorityKeyID->authCertIssuer != NULL ) { + /* no key ID, so try issuer and serial number */ + caName = (SECItem*)CERT_GetGeneralNameByType(authorityKeyID->authCertIssuer, + certDirectoryName, PR_TRUE); + + /* + * caName is NULL when the authCertIssuer field is not + * being used, or other name form is used instead. + * If the directoryName format and serialNumber fields are + * used, we use them to find the CA cert. + * Note: + * By the time it gets here, we known for sure that if the + * authCertIssuer exists, then the authCertSerialNumber + * must also exists (CERT_DecodeAuthKeyID() ensures this). + * We don't need to check again. + */ + + if (caName != NULL) { + CERTIssuerAndSN issuerSN; + + issuerSN.derIssuer.data = caName->data; + issuerSN.derIssuer.len = caName->len; + issuerSN.serialNumber.data = + authorityKeyID->authCertSerialNumber.data; + issuerSN.serialNumber.len = + authorityKeyID->authCertSerialNumber.len; + issuerCert = CERT_FindCertByIssuerAndSN(cert->dbhandle, + &issuerSN); + if ( issuerCert == NULL ) { + PORT_SetError (SEC_ERROR_UNKNOWN_ISSUER); + goto loser; + } + } + } + } + if ( issuerCert == NULL ) { + /* if there is not authorityKeyID, then try to find the issuer */ + /* find a valid CA cert with correct usage */ + issuerCert = CERT_FindMatchingCert(cert->dbhandle, + &cert->derIssuer, + certOwnerCA, usage, PR_TRUE, + validTime, PR_TRUE); + + /* if that fails, then fall back to grabbing any cert with right name*/ + if ( issuerCert == NULL ) { + issuerCert = CERT_FindCertByName(cert->dbhandle, &cert->derIssuer); + if ( issuerCert == NULL ) { + PORT_SetError (SEC_ERROR_UNKNOWN_ISSUER); + } + } + } + +loser: + if (tmpArena != NULL) { + PORT_FreeArena(tmpArena, PR_FALSE); + tmpArena = NULL; + } + + return(issuerCert); +#else + NSSCertificate *me; + NSSTime *nssTime; + NSSUsage nssUsage; + NSSCertificate *chain[3]; + PRStatus status; + me = STAN_GetNSSCertificate(cert); + nssTime = NSSTime_SetPRTime(NULL, validTime); + nssUsage.anyUsage = PR_FALSE; + nssUsage.nss3usage = usage; + nssUsage.nss3lookingForCA = PR_TRUE; + memset(chain, 0, 3*sizeof(NSSCertificate *)); + if (!me) { + PORT_SetError (SEC_ERROR_BAD_DATABASE); + return NULL; + } + (void)NSSCertificate_BuildChain(me, nssTime, &nssUsage, NULL, + chain, 2, NULL, &status); + nss_ZFreeIf(nssTime); + if (status == PR_SUCCESS) { + /* 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; + } else { + CERT_DestroyCertificate(cert); /* the first cert in the chain */ + return STAN_GetCERTCertificate(chain[1]); /* return the 2nd */ + } + } else { + if (chain[0]) { + CERT_DestroyCertificate(cert); + } + PORT_SetError (SEC_ERROR_UNKNOWN_ISSUER); + } + return NULL; +#endif +} + +/* + * 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 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); +} + + + + + +static void +AddToVerifyLog(CERTVerifyLog *log, CERTCertificate *cert, unsigned 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 ) { \ + AddToVerifyLog(log, cert, PORT_GetError(), depth, (void *)arg); \ + } else { \ + goto loser; \ + } + +#define LOG_ERROR(log,cert,depth,arg) \ + if ( log != NULL ) { \ + AddToVerifyLog(log, cert, PORT_GetError(), depth, (void *)arg); \ + } + + +typedef enum { cbd_None, cbd_User, cbd_CA } cbd_FortezzaType; + +static SECStatus +cert_VerifyFortezzaV1Cert(CERTCertDBHandle *handle, CERTCertificate *cert, + cbd_FortezzaType *next_type, cbd_FortezzaType last_type, + int64 t, void *wincx) +{ + unsigned char priv = 0; + SECKEYPublicKey *key; + SECStatus rv; + + *next_type = cbd_CA; + + /* read the key */ + key = CERT_ExtractPublicKey(cert); + + /* Cant' get Key? fail. */ + if (key == NULL) { + PORT_SetError(SEC_ERROR_BAD_KEY); + return SECFailure; + } + + + /* if the issuer is not an old fortezza cert, we bail */ + if (key->keyType != fortezzaKey) { + SECKEY_DestroyPublicKey(key); + /* CA Cert not fortezza */ + PORT_SetError(SEC_ERROR_NOT_FORTEZZA_ISSUER); + return SECFailure; + } + + /* get the privilege mask */ + if (key->u.fortezza.DSSpriviledge.len > 0) { + priv = key->u.fortezza.DSSpriviledge.data[0]; + } + + /* + * make sure the CA's keys are OK + */ + + rv = SEC_CheckKRL(handle, key, NULL, t, wincx); + SECKEY_DestroyPublicKey(key); + if (rv != SECSuccess) { + return rv; + } + + switch (last_type) { + case cbd_User: + /* first check for subordination */ + /*rv = FortezzaSubordinateCheck(cert,issuerCert);*/ + rv = SECSuccess; + + /* now check for issuer privilege */ + if ((rv != SECSuccess) || ((priv & 0x10) == 0)) { + /* bail */ + PORT_SetError (SEC_ERROR_CA_CERT_INVALID); + return SECFailure; + } + break; + case cbd_CA: + if ((priv & 0x20) == 0) { + /* bail */ + PORT_SetError (SEC_ERROR_CA_CERT_INVALID); + return SECFailure; + } + break; + case cbd_None: + *next_type = (priv & 0x30) ? cbd_CA : cbd_User; + break; + default: + /* bail */ /* shouldn't ever happen */ + PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER); + return SECFailure; + } + return SECSuccess; +} + + +static SECStatus +cert_VerifyCertChain(CERTCertDBHandle *handle, CERTCertificate *cert, + PRBool checkSig, PRBool* sigerror, + SECCertUsage certUsage, int64 t, void *wincx, + CERTVerifyLog *log, PRBool* revoked) +{ + SECTrustType trustType; + CERTBasicConstraints basicConstraint; + CERTCertificate *issuerCert = NULL; + CERTCertificate *subjectCert = NULL; + CERTCertificate *badCert = NULL; + PRBool isca; + PRBool isFortezzaV1 = PR_FALSE; + SECStatus rv; + SECStatus rvFinal = SECSuccess; + int count; + int currentPathLen = -1; + int flags; + unsigned int caCertType; + unsigned int requiredCAKeyUsage; + unsigned int requiredFlags; + PRArenaPool *arena = NULL; + CERTGeneralName *namesList = NULL; + CERTGeneralName *subjectNameList = NULL; + CERTCertificate **certsList = NULL; + int certsListLen = 16; + int namesCount = 0; + + cbd_FortezzaType last_type = cbd_None; + + 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 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; + } + + subjectCert = CERT_DupCertificate(cert); + if ( subjectCert == NULL ) { + goto loser; + } + + /* determine if the cert is fortezza. + */ + isFortezzaV1 = (PRBool) + (CERT_GetCertKeyType(&subjectCert->subjectPublicKeyInfo) + == fortezzaKey); + + if (isFortezzaV1) { + rv = cert_VerifyFortezzaV1Cert(handle, subjectCert, &last_type, + cbd_None, t, wincx); + if (rv == SECFailure) { + /**** PORT_SetError is already set by cert_VerifyFortezzaV1Cert **/ + LOG_ERROR_OR_EXIT(log,subjectCert,0,0); + } + } + + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (arena == NULL) { + goto loser; + } + + certsList = PORT_ZNewArray(CERTCertificate *, certsListLen); + if (certsList == NULL) + goto loser; + + for ( count = 0; count < CERT_MAX_CERT_CHAIN; count++ ) { + int subjectNameListLen; + int i; + PRBool validCAOverride = PR_FALSE; + + /* Construct a list of names for the current and all previous + * certifcates to be verified against the name constraints extension + * of the issuer certificate. + */ + subjectNameList = CERT_GetCertificateNames(subjectCert, arena); + subjectNameListLen = CERT_GetNamesLength(subjectNameList); + if (certsListLen <= namesCount + subjectNameListLen) { + certsListLen = (namesCount + subjectNameListLen) * 2; + certsList = + (CERTCertificate **)PORT_Realloc(certsList, + certsListLen * sizeof(CERTCertificate *)); + if (certsList == NULL) { + goto loser; + } + } + for (i = 0; i < subjectNameListLen; i++) { + certsList[namesCount + i] = subjectCert; + } + namesCount += subjectNameListLen; + namesList = cert_CombineNamesLists(namesList, subjectNameList); + + /* 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 { + PORT_SetError(SEC_ERROR_BAD_SIGNATURE); + LOG_ERROR_OR_EXIT(log,subjectCert,count,0); + } + } + } + + /* + * XXX - fortezza may need error logging stuff added + */ + if (isFortezzaV1) { + rv = cert_VerifyFortezzaV1Cert(handle, issuerCert, &last_type, + last_type, t, wincx); + if (rv == SECFailure) { + /**** PORT_SetError is already set by * + * cert_VerifyFortezzaV1Cert **/ + LOG_ERROR_OR_EXIT(log,subjectCert,0,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). + * + * In the fortezza (V1) case, we've already checked the CA bits + * in the key, so we're presumed to be a CA; however we really don't + * want to bypass Basic constraint or netscape extension parsing. + * + * In Fortezza V2, basicConstraint will be set for every CA,PCA,PAA + */ + + 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); + } else { + currentPathLen = CERT_UNLIMITED_PATH_CONSTRAINT; + } + /* no basic constraints found, if we're fortezza, CA bit is already + * verified (isca = PR_TRUE). otherwise, we aren't (yet) a ca + * isca = PR_FALSE */ + isca = isFortezzaV1; + } else { + if ( basicConstraint.isCA == PR_FALSE ) { + PORT_SetError (SEC_ERROR_CA_CERT_INVALID); + LOG_ERROR_OR_EXIT(log,issuerCert,count+1,0); + } + + /* make sure that the path len constraint is properly set. + */ + if ( basicConstraint.pathLenConstraint == + CERT_UNLIMITED_PATH_CONSTRAINT ) { + currentPathLen = CERT_UNLIMITED_PATH_CONSTRAINT; + } else if ( currentPathLen == CERT_UNLIMITED_PATH_CONSTRAINT ) { + /* error if the previous CA's path length constraint is + * unlimited but its CA's path is not. + */ + PORT_SetError (SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID); + LOG_ERROR_OR_EXIT(log,issuerCert,count+1,basicConstraint.pathLenConstraint); + } else if (basicConstraint.pathLenConstraint > currentPathLen) { + currentPathLen = basicConstraint.pathLenConstraint; + } else { + PORT_SetError (SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID); + LOG_ERROR_OR_EXIT(log,issuerCert,count+1,basicConstraint.pathLenConstraint); + } + + isca = PR_TRUE; + } + + /* 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 ( issuerCert->trust ) { + /* 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) { + /* XXX NSS has done this for years, but it seems incorrect. */ + rv = rvFinal; + goto done; + } + + /* + * check the trust parms of the issuer + */ + 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(issuerCert->trust, trustType); + + if (flags & CERTDB_VALID_CA) { + if ( ( flags & requiredFlags ) == requiredFlags) { + /* we found a trusted one, so return */ + rv = rvFinal; + goto done; + } + validCAOverride = PR_TRUE; + } + } + + 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 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; + } + /* 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; + } + + CERT_DestroyCertificate(subjectCert); + subjectCert = issuerCert; + } + + subjectCert = NULL; + PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER); + LOG_ERROR(log,issuerCert,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, SECCertUsage certUsage, int64 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, int64 t, + void *wincx, CERTVerifyLog *log) +{ + SECTrustType trustType; + CERTBasicConstraints basicConstraint; + PRBool isca; + PRBool validCAOverride = PR_FALSE; + SECStatus rv; + SECComparison rvCompare; + SECStatus rvFinal = SECSuccess; + int flags; + unsigned int caCertType; + unsigned int requiredCAKeyUsage; + unsigned int requiredFlags; + CERTCertificate *issuerCert; + + + 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 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). + * + * In the fortezza (V1) case, we've already checked the CA bits + * in the key, so we're presumed to be a CA; however we really don't + * want to bypass Basic constraint or netscape extension parsing. + * + * In Fortezza V2, basicConstraint will be set for every CA,PCA,PAA + */ + + 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, if we're fortezza, CA bit is already + * verified (isca = PR_TRUE). otherwise, we aren't (yet) a ca + * isca = PR_FALSE */ + 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->trust ) { + /* 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 parms of the issuer + */ + flags = SEC_GET_TRUST_FLAGS(cert->trust, trustType); + + if (flags & CERTDB_VALID_CA) { + if ( ( flags & requiredFlags ) == requiredFlags) { + /* we found a trusted one, so return */ + rv = rvFinal; + goto done; + } + validCAOverride = PR_TRUE; + } + } + 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. + */ + rvCompare = SECITEM_CompareItem(&cert->derSubject, &cert->derIssuer); + if (rvCompare == SECEqual) { + 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_ITERATION() { \ + i*=2; \ + certUsage++; \ + continue; \ +} + +#define VALID_USAGE() { \ + NEXT_ITERATION(); \ +} + +#define INVALID_USAGE() { \ + if (returnedUsages) { \ + *returnedUsages &= (~i); \ + } \ + if (PR_TRUE == requiredUsage) { \ + valid = SECFailure; \ + } \ + NEXT_ITERATION(); \ +} + +/* + * 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, int64 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; + + 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 */ + +#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(log,cert,0,0); + return SECFailure; + } +#endif + + /* make sure that the cert is valid at time t */ + allowOverride = (PRBool)((requiredUsages & certificateUsageSSLServer) || + (requiredUsages & certificateUsageSSLServerWithStepUp)); + validity = CERT_CheckCertValidTimes(cert, t, allowOverride); + if ( validity != secCertTimeValid ) { + LOG_ERROR(log,cert,0,validity); + return SECFailure; + } + + /* check key usage and netscape cert type */ + cert_GetCertType(cert); + certType = cert->nsCertType; + + for (i=1;i<=certificateUsageHighest && !(SECFailure == valid && !returnedUsages) ;) { + PRBool requiredUsage = (i & requiredUsages) ? PR_TRUE : PR_FALSE; + if (PR_FALSE == requiredUsage && PR_FALSE == checkAllUsages) { + NEXT_ITERATION(); + } + 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: + 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_ITERATION(); + + 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(); + } + + /* check trust flags to see if this cert is directly trusted */ + if ( cert->trust ) { /* the cert is in the DB */ + switch ( certUsage ) { + case certUsageSSLClient: + case certUsageSSLServer: + flags = cert->trust->sslFlags; + + /* is the cert directly trusted or not trusted ? */ + if ( flags & CERTDB_VALID_PEER ) {/*the trust record is valid*/ + if ( flags & CERTDB_TRUSTED ) { /* trust this cert */ + VALID_USAGE(); + } else { /* don't trust this cert */ + if (PR_TRUE == requiredUsage) { + PORT_SetError(SEC_ERROR_UNTRUSTED_CERT); + } + LOG_ERROR(log,cert,0,flags); + INVALID_USAGE(); + } + } + break; + case certUsageSSLServerWithStepUp: + /* XXX - step up certs can't be directly trusted */ + break; + case certUsageSSLCA: + break; + case certUsageEmailSigner: + case certUsageEmailRecipient: + flags = cert->trust->emailFlags; + + /* is the cert directly trusted or not trusted ? */ + if ( ( flags & ( CERTDB_VALID_PEER | CERTDB_TRUSTED ) ) == + ( CERTDB_VALID_PEER | CERTDB_TRUSTED ) ) { + VALID_USAGE(); + } + break; + case certUsageObjectSigner: + flags = cert->trust->objectSigningFlags; + + /* is the cert directly trusted or not trusted ? */ + if ( flags & CERTDB_VALID_PEER ) {/*the trust record is valid*/ + if ( flags & CERTDB_TRUSTED ) { /* trust this cert */ + VALID_USAGE(); + } else { /* don't trust this cert */ + if (PR_TRUE == requiredUsage) { + PORT_SetError(SEC_ERROR_UNTRUSTED_CERT); + } + LOG_ERROR(log,cert,0,flags); + INVALID_USAGE(); + } + } + break; + case certUsageVerifyCA: + case certUsageStatusResponder: + flags = cert->trust->sslFlags; + /* is the cert directly trusted or not trusted ? */ + if ( ( flags & ( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) == + ( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) { + VALID_USAGE(); + } + flags = cert->trust->emailFlags; + /* is the cert directly trusted or not trusted ? */ + if ( ( flags & ( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) == + ( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) { + VALID_USAGE(); + } + flags = cert->trust->objectSigningFlags; + /* is the cert directly trusted or not trusted ? */ + if ( ( flags & ( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) == + ( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) { + VALID_USAGE(); + } + break; + case certUsageAnyCA: + case certUsageProtectedObjectSigner: + case certUsageUserCertImport: + /* XXX to make the compiler happy. Should these be + * explicitly handled? + */ + break; + } + } + + 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 reponder 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_ITERATION(); + } + + return(valid); +} + +/* obsolete, do not use for new code */ +SECStatus +CERT_VerifyCert(CERTCertDBHandle *handle, CERTCertificate *cert, + PRBool checkSig, SECCertUsage certUsage, int64 t, + void *wincx, CERTVerifyLog *log) +{ + SECStatus rv; + unsigned int requiredKeyUsage; + unsigned int requiredCertType; + unsigned int flags; + unsigned int certType; + 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)); + 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 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: + 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); + } + + /* check trust flags to see if this cert is directly trusted */ + if ( cert->trust ) { /* the cert is in the DB */ + switch ( certUsage ) { + case certUsageSSLClient: + case certUsageSSLServer: + flags = cert->trust->sslFlags; + + /* is the cert directly trusted or not trusted ? */ + if ( flags & CERTDB_VALID_PEER ) {/*the trust record is valid*/ + if ( flags & CERTDB_TRUSTED ) { /* trust this cert */ + goto winner; + } else { /* don't trust this cert */ + PORT_SetError(SEC_ERROR_UNTRUSTED_CERT); + LOG_ERROR_OR_EXIT(log,cert,0,flags); + } + } + break; + case certUsageSSLServerWithStepUp: + /* XXX - step up certs can't be directly trusted */ + break; + case certUsageSSLCA: + break; + case certUsageEmailSigner: + case certUsageEmailRecipient: + flags = cert->trust->emailFlags; + + /* is the cert directly trusted or not trusted ? */ + if ( ( flags & ( CERTDB_VALID_PEER | CERTDB_TRUSTED ) ) == + ( CERTDB_VALID_PEER | CERTDB_TRUSTED ) ) { + goto winner; + } + break; + case certUsageObjectSigner: + flags = cert->trust->objectSigningFlags; + + /* is the cert directly trusted or not trusted ? */ + if ( flags & CERTDB_VALID_PEER ) {/*the trust record is valid*/ + if ( flags & CERTDB_TRUSTED ) { /* trust this cert */ + goto winner; + } else { /* don't trust this cert */ + PORT_SetError(SEC_ERROR_UNTRUSTED_CERT); + LOG_ERROR_OR_EXIT(log,cert,0,flags); + } + } + break; + case certUsageVerifyCA: + case certUsageStatusResponder: + flags = cert->trust->sslFlags; + /* is the cert directly trusted or not trusted ? */ + if ( ( flags & ( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) == + ( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) { + goto winner; + } + flags = cert->trust->emailFlags; + /* is the cert directly trusted or not trusted ? */ + if ( ( flags & ( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) == + ( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) { + goto winner; + } + flags = cert->trust->objectSigningFlags; + /* is the cert directly trusted or not trusted ? */ + if ( ( flags & ( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) == + ( CERTDB_VALID_CA | CERTDB_TRUSTED_CA ) ) { + goto winner; + } + break; + case certUsageAnyCA: + case certUsageProtectedObjectSigner: + case certUsageUserCertImport: + /* XXX to make the compiler happy. Should these be + * explicitly handled? + */ + break; + } + } + + 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 reponder 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, which + * is all done inside VerifyCertChain) by bypassing this code. + */ + statusConfig = CERT_GetStatusConfig(handle); + if (certUsage != certUsageStatusResponder && statusConfig != NULL) { + if (statusConfig->statusChecker != NULL) { + rv = (* statusConfig->statusChecker)(handle, cert, + t, wincx); + if (rv != SECSuccess) { + LOG_ERROR_OR_EXIT(log,cert,0,0); + } + } + } + +winner: + 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 + * certUsageEmailSigner + * certUsageEmailRecipient + * certUsageObjectSigner + */ + +CERTCertificate * +CERT_FindMatchingCert(CERTCertDBHandle *handle, SECItem *derName, + CERTCertOwner owner, SECCertUsage usage, + PRBool preferTrusted, int64 validTime, PRBool validOnly) +{ + CERTCertList *certList = NULL; + CERTCertificate *cert = NULL; + 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->trust == NULL ) { + flags = 0; + } else { + flags = SEC_GET_TRUST_FLAGS(cert->trust, 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; + int64 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(PRArenaPool *arena, CERTCertificate *cert, + char *expiredString, char *notYetGoodString) +{ + SECCertTimeValidity validity; + char *nickname, *tmpstr; + + 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(cert->nickname); + } else { + nickname = PORT_ArenaStrdup(arena, cert->nickname); + } + + 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", cert->nickname, + expiredString); + } else { + /* not yet valid */ + tmpstr = PR_smprintf("%s%s", cert->nickname, + notYetGoodString); + } + 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; + PRArenaPool *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, int64 time, SECCertUsage usage) +{ + CERTCertList *chain = NULL; + + 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) { + if (SECSuccess != CERT_AddCertToListTail(chain, cert)) { + /* return partial chain */ + PORT_SetError(SEC_ERROR_NO_MEMORY); + return chain; + } + + if (SECITEM_CompareItem(&cert->derIssuer, &cert->derSubject) + == SECEqual) { + /* 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/config.mk b/security/nss/lib/certhigh/config.mk new file mode 100644 index 000000000..0a00dc61e --- /dev/null +++ b/security/nss/lib/certhigh/config.mk @@ -0,0 +1,43 @@ +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Netscape security libraries. +# +# The Initial Developer of the Original Code is Netscape +# Communications Corporation. Portions created by Netscape are +# Copyright (C) 1994-2000 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the +# terms of the GNU General Public License Version 2 or later (the +# "GPL"), in which case the provisions of the GPL are applicable +# instead of those above. If you wish to allow use of your +# version of this file only under the terms of the GPL and not to +# allow others to use your version of this file under the MPL, +# indicate your decision by deleting the provisions above and +# replace them with the notice and other provisions required by +# the GPL. If you do not delete the provisions above, a recipient +# may use your version of this file under either the MPL or the +# GPL. +# + +# +# Override TARGETS variable so that only static libraries +# are specifed as dependencies within rules.mk. +# + +TARGETS = $(LIBRARY) +SHARED_LIBRARY = +IMPORT_LIBRARY = +PROGRAM = + diff --git a/security/nss/lib/certhigh/crlv2.c b/security/nss/lib/certhigh/crlv2.c new file mode 100644 index 000000000..2600d9878 --- /dev/null +++ b/security/nss/lib/certhigh/crlv2.c @@ -0,0 +1,126 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * Code for dealing with x.509 v3 crl and crl entries extensions. + * + * $Id$ + */ + +#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)); +} + + +SECStatus CERT_FindCRLNumberExten (CERTCrl *crl, CERTCrlNumber *value) +{ + SECItem encodedExtenValue; + SECStatus rv; + + encodedExtenValue.data = NULL; + encodedExtenValue.len = 0; + + rv = cert_FindExtension + (crl->extensions, SEC_OID_X509_CRL_NUMBER, &encodedExtenValue); + if ( rv != SECSuccess ) + return (rv); + + rv = SEC_ASN1DecodeItem (NULL, value, SEC_IntegerTemplate, + &encodedExtenValue); + PORT_Free (encodedExtenValue.data); + return (rv); +} + +SECStatus CERT_FindCRLReasonExten (CERTCrl *crl, SECItem *value) +{ + return (CERT_FindBitStringExtension + (crl->extensions, SEC_OID_X509_REASON_CODE, value)); +} + +SECStatus CERT_FindInvalidDateExten (CERTCrl *crl, int64 *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_GeneralizedTimeTemplate, &encodedExtenValue); + if (rv != SECSuccess) + return (rv); + rv = DER_GeneralizedTimeToTime(value, &encodedExtenValue); + PORT_Free (decodedExtenValue.data); + PORT_Free (encodedExtenValue.data); + return (rv); +} diff --git a/security/nss/lib/certhigh/manifest.mn b/security/nss/lib/certhigh/manifest.mn new file mode 100644 index 000000000..80deff5bb --- /dev/null +++ b/security/nss/lib/certhigh/manifest.mn @@ -0,0 +1,59 @@ +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Netscape security libraries. +# +# The Initial Developer of the Original Code is Netscape +# Communications Corporation. Portions created by Netscape are +# Copyright (C) 1994-2000 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the +# terms of the GNU General Public License Version 2 or later (the +# "GPL"), in which case the provisions of the GPL are applicable +# instead of those above. If you wish to allow use of your +# version of this file only under the terms of the GPL and not to +# allow others to use your version of this file under the MPL, +# indicate your decision by deleting the provisions above and +# replace them with the notice and other provisions required by +# the GPL. If you do not delete the provisions above, a recipient +# may use your version of this file under either the MPL or the +# GPL. +# +CORE_DEPTH = ../../.. + +EXPORTS = \ + ocsp.h \ + ocspt.h \ + $(NULL) + +PRIVATE_EXPORTS = \ + ocspti.h \ + $(NULL) + +MODULE = nss + +CSRCS = \ + certhtml.c \ + certreq.c \ + crlv2.c \ + ocsp.c \ + certhigh.c \ + certvfy.c \ + xcrldist.c \ + $(NULL) + +REQUIRES = dbm + +LIBRARY_NAME = certhi + diff --git a/security/nss/lib/certhigh/ocsp.c b/security/nss/lib/certhigh/ocsp.c new file mode 100644 index 000000000..96ee31f5a --- /dev/null +++ b/security/nss/lib/certhigh/ocsp.c @@ -0,0 +1,3998 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * Implementation of OCSP services, for both client and server. + * (XXX, really, mostly just for client right now, but intended to do both.) + * + * $Id$ + */ + +#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 "xconst.h" +#include "secerr.h" +#include "secoid.h" +#include "hasht.h" +#include "sechash.h" +#include "secasn1.h" +#include "keyhi.h" +#include "cryptohi.h" +#include "ocsp.h" +#include "ocspti.h" +#include "genname.h" +#include "certxutl.h" +#include "pk11func.h" /* for PK11_HashBuf */ +#include <stdarg.h> + + +/* + * 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; + + +/* + * 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 | 0, + offsetof(ocspTBSRequest, version), + SEC_IntegerTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(ocspTBSRequest, derRequestorName), + 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, + offsetof(ocspSignature, signatureAlgorithm), + SECOID_AlgorithmIDTemplate }, + { SEC_ASN1_BIT_STRING, + offsetof(ocspSignature, signature) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_EXPLICIT | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(ocspSignature, derCerts), + 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, + offsetof(CERTOCSPCertID, hashAlgorithm), + 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 } + */ +static 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 } + */ +static 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_POINTER, + offsetof(ocspBasicOCSPResponse, tbsResponseData), + ocsp_ResponseDataTemplate }, + { SEC_ASN1_INLINE, + offsetof(ocspBasicOCSPResponse, responseSignature.signatureAlgorithm), + SECOID_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), + 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 | 0, + offsetof(ocspResponseData, version), + 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: + */ +static const SEC_ASN1Template ocsp_ResponderIDByNameTemplate[] = { + { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(ocspResponderID, responderIDValue.name), + CERT_NameTemplate } +}; +static const SEC_ASN1Template ocsp_ResponderIDByKeyTemplate[] = { + { SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 2, + offsetof(ocspResponderID, responderIDValue.keyHash), + SEC_OctetStringTemplate } +}; +static const SEC_ASN1Template ocsp_ResponderIDOtherTemplate[] = { + { SEC_ASN1_ANY, + offsetof(ocspResponderID, responderIDValue.other) } +}; + +/* + * 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 | 0, + offsetof(CERTOCSPSingleResponse, nextUpdate), + 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 | 0, + offsetof(ocspCertStatus, certStatusInfo.goodInfo), + 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 | 2, + offsetof(ocspCertStatus, certStatusInfo.unknownInfo), + SEC_NullTemplate } +}; +static const SEC_ASN1Template ocsp_CertStatusOtherTemplate[] = { + { SEC_ASN1_POINTER, + offsetof(ocspCertStatus, certStatusInfo.otherInfo), + 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 | 0, + offsetof(ocspRevokedInfo, revocationReason), + 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: + * PRArenaPool *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(PRArenaPool *arena, CERTOCSPRequest *request, + void *pwArg) +{ + ocspTBSRequest *tbsRequest; + SECStatus rv; + + /* XXX All of these should generate errors if they fail. */ + PORT_Assert(request); + PORT_Assert(request->tbsRequest); + + tbsRequest = 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(SECItem *src) +{ + PRArenaPool *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->poolp) { + PORT_FreeArena(certID->poolp, PR_FALSE); + return SECSuccess; + } + return SECFailure; +} + + +/* + * 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(PRArenaPool *arena, CERTCertificate *cert, int64 time) +{ + CERTOCSPCertID *certID; + CERTCertificate *issuerCert = NULL; + SECItem *tempItem = 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; + } + + tempItem = SEC_ASN1EncodeItem(NULL, NULL, &issuerCert->subject, + CERT_NameTemplate); + if (tempItem == NULL) { + goto loser; + } + + if (SECITEM_AllocItem(arena, &(certID->issuerNameHash), + SHA1_LENGTH) == NULL) { + goto loser; + } + rv = PK11_HashBuf(SEC_OID_SHA1, certID->issuerNameHash.data, + tempItem->data, tempItem->len); + if (rv != SECSuccess) { + goto loser; + } + certID->issuerSHA1NameHash.data = certID->issuerNameHash.data; + certID->issuerSHA1NameHash.len = certID->issuerNameHash.len; + /* cache the other two hash algorithms as well */ + if (SECITEM_AllocItem(arena, &(certID->issuerMD5NameHash), + MD5_LENGTH) == NULL) { + goto loser; + } + rv = PK11_HashBuf(SEC_OID_MD5, certID->issuerMD5NameHash.data, + tempItem->data, tempItem->len); + if (rv != SECSuccess) { + goto loser; + } + if (SECITEM_AllocItem(arena, &(certID->issuerMD2NameHash), + MD2_LENGTH) == NULL) { + goto loser; + } + rv = PK11_HashBuf(SEC_OID_MD2, certID->issuerMD2NameHash.data, + tempItem->data, tempItem->len); + if (rv != SECSuccess) { + goto loser; + } + + SECITEM_FreeItem(tempItem, PR_TRUE); + tempItem = NULL; + + if (CERT_SPKDigestValueForCert(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_SPKDigestValueForCert(arena, issuerCert, SEC_OID_MD5, + &(certID->issuerMD5KeyHash)) == NULL) { + goto loser; + } + if (CERT_SPKDigestValueForCert(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); + } + if (tempItem != NULL) { + SECITEM_FreeItem(tempItem, PR_TRUE); + } + PORT_ArenaRelease(arena, mark); + return NULL; +} + +CERTOCSPCertID* +CERT_CreateOCSPCertID(CERTCertificate *cert, int64 time) +{ + PRArenaPool *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; +} + + + +/* + * 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; + + 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(PRArenaPool *arena, CERTCertList *certList, + int64 time, PRBool includeLocator) +{ + ocspSingleRequest **requestList = NULL; + CERTCertListNode *node; + 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; + + 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; +} + + +/* + * 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. + * int64 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, int64 time, + PRBool addServiceLocator, + CERTCertificate *signerCert) +{ + PRArenaPool *arena = NULL; + CERTOCSPRequest *request = NULL; + ocspTBSRequest *tbsRequest = NULL; + + /* + * XXX This should set an error, but since it is only temporary and + * since PSM will not initially provide a way to turn on signing of + * requests anyway, I figure we can just skip defining an error that + * will be obsolete in the next release. When we are prepared to + * put signing of requests back in, this entire check will go away, + * and later in this function 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) { + return 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 */ + + /* + * Now create the list of single requests, one for each cert. + */ + tbsRequest->requestList = ocsp_CreateSingleRequestList(arena, certList, + time, + addServiceLocator); + if (tbsRequest->requestList == NULL) { + goto loser; + } + return request; + +loser: + if (arena != NULL) { + PORT_FreeArena(arena, PR_FALSE); + } + return NULL; +} + + +/* + * 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; +} + +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_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; +} + + +/* + * 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(ocspResponderIDType 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(PRArenaPool *arena, + CERTOCSPSingleResponse **responses) +{ + ocspCertStatus *certStatus; + ocspCertStatusType certStatusType; + const SEC_ASN1Template *certStatusTemplate; + int derTag; + int i; + SECStatus rv = SECFailure; + + if (responses == NULL) /* nothing to do */ + return SECSuccess; + + for (i = 0; responses[i] != NULL; i++) { + /* + * 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(arena, sizeof(ocspCertStatus)); + if (certStatus == NULL) { + goto loser; + } + rv = SEC_ASN1DecodeItem(arena, certStatus, certStatusTemplate, + &responses[i]->derCertStatus); + 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 ocspResponderIDType +ocsp_ResponderIDTypeByTag(int derTag) +{ + ocspResponderIDType 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(PRArenaPool *arena, SECItem *src) +{ + void *mark; + ocspBasicOCSPResponse *basicResponse; + ocspResponseData *responseData; + ocspResponderID *responderID; + ocspResponderIDType 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(PRArenaPool *arena, ocspResponseBytes *rbytes) +{ + PORT_Assert(rbytes != NULL); /* internal error, really */ + if (rbytes == NULL) + PORT_SetError(SEC_ERROR_INVALID_ARGS); /* XXX set better error? */ + + 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(SECItem *src) +{ + PRArenaPool *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). + */ +static ocspResponseData * +ocsp_GetResponseData(CERTOCSPResponse *response) +{ + 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); + + return responseData; +} + +/* + * Much like the routine above, except it returns the response signature. + * Again, no copy is done. + */ +static ocspSignature * +ocsp_GetResponseSignature(CERTOCSPResponse *response) +{ + ocspBasicOCSPResponse *basic; + + PORT_Assert(response != NULL); + if (NULL == response->responseBytes) { + return 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); + + 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(char *url, char **pHostname, PRUint16 *pPort, char **pPath) +{ + unsigned short port = 80; /* default, in case not in url */ + char *hostname = NULL; + char *path = NULL; + 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("/"); + } + + *pHostname = hostname; + *pPort = port; + *pPath = path; + return SECSuccess; + +loser: + if (hostname != NULL) + PORT_Free(hostname); + if (path != NULL) + PORT_Free(path); + 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. + */ +static PRFileDesc * +ocsp_SendEncodedRequest(char *location, SECItem *encodedRequest) +{ + char *hostname = NULL; + char *path = NULL; + PRUint16 port; + SECStatus rv; + PRFileDesc *sock = NULL; + PRFileDesc *returnSock = NULL; + char *header = NULL; + + /* + * 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; + + header = PR_smprintf("POST %s HTTP/1.0\r\n" + "Host: %s:%d\r\n" + "Content-Type: application/ocsp-request\r\n" + "Content-Length: %u\r\n\r\n", + path, hostname, port, 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(PRArenaPool *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; +} + + +/* + * FUNCTION: CERT_GetEncodedOCSPResponse + * Creates and sends a request to an OCSP responder, then reads and + * returns the (encoded) response. + * INPUTS: + * PRArenaPool *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. + * char *location + * The location of the OCSP responder (a URL). + * int64 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_GetEncodedOCSPResponse(PRArenaPool *arena, CERTCertList *certList, + char *location, int64 time, + PRBool addServiceLocator, + CERTCertificate *signerCert, void *pwArg, + CERTOCSPRequest **pRequest) +{ + CERTOCSPRequest *request = NULL; + SECItem *encodedRequest = NULL; + SECItem *encodedResponse = NULL; + PRFileDesc *sock = NULL; + SECStatus rv; + + request = CERT_CreateOCSPRequest(certList, time, addServiceLocator, + signerCert); + if (request == NULL) + 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; + + sock = ocsp_SendEncodedRequest(location, encodedRequest); + if (sock == NULL) + goto loser; + + encodedResponse = ocsp_GetEncodedResponse(arena, sock); + 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); + if (sock != NULL) + PR_Close(sock); + + return encodedResponse; +} + + +/* Checks a certificate for the key usage extension of OCSP signer. */ +static PRBool +ocsp_CertIsOCSPSigner(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; + 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; + SECItem extItem; + + extItem.data = NULL; + rv = CERT_FindCertExtension(cert, SEC_OID_PKIX_OCSP_NO_CHECK, + &extItem); + if (extItem.data != NULL) { + PORT_Free(extItem.data); + } + 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_SPKDigestValueForCert(NULL,testCert,SEC_OID_SHA1, &item) == NULL) { + return PR_FALSE; + } + if (SECITEM_ItemsAreEqual(certIndex,&item)) { + return PR_TRUE; + } + if (CERT_SPKDigestValueForCert(NULL,testCert,SEC_OID_MD5, &item) == NULL) { + return PR_FALSE; + } + if (SECITEM_ItemsAreEqual(certIndex,&item)) { + return PR_TRUE; + } + if (CERT_SPKDigestValueForCert(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); + +/* + * Check the signature on some OCSP data. This is a helper function that + * can be used to check either a request or a response. The result is + * saved in the signature structure itself for future reference (to avoid + * repeating the expensive verification operation), as well as returned. + * In addition to checking the signature, the certificate (and its chain) + * are also checked for validity (at the specified time) and usage. + * + * The type of cert lookup to be performed is specified by "lookupByName": + * if true, then "certIndex" is actually a CERTName; otherwise it is a + * SECItem which contains a key hash. + * + * If the signature verifies okay, and the argument "pSignerCert" is not + * null, that parameter will be filled-in with a pointer to the signer's + * certificate. The caller is then responsible for destroying the cert. + * + * A return of SECSuccess means the verification succeeded. If not, + * an error will be set with the reason. Most likely are: + * 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.) + */ +static SECStatus +ocsp_CheckSignature(ocspSignature *signature, void *tbs, + const SEC_ASN1Template *encodeTemplate, + CERTCertDBHandle *handle, SECCertUsage certUsage, + int64 checkTime, PRBool lookupByName, void *certIndex, + void *pwArg, CERTCertificate **pSignerCert, + CERTCertificate *issuer) +{ + SECItem rawSignature; + SECItem *encodedTBS = NULL; + CERTCertificate *responder = NULL; + CERTCertificate *signerCert = NULL; + SECKEYPublicKey *signerKey = NULL; + CERTCertificate **certs = NULL; + SECStatus rv = SECFailure; + int certCount; + int i; + + /* + * 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; + } + + /* + * 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. + */ + certCount = 0; + if (signature->derCerts != NULL) { + for (; signature->derCerts[certCount] != NULL; certCount++) { + /* just counting */ + /*IMPORT CERT TO SPKI TABLE */ + } + } + rv = CERT_ImportCerts(handle, certUsage, 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 *encodedName; + + encodedName = SEC_ASN1EncodeItem(NULL, NULL, certIndex, + CERT_NameTemplate); + if (encodedName == NULL) + goto finish; + + signerCert = CERT_FindCertByName(handle, encodedName); + SECITEM_FreeItem(encodedName, PR_TRUE); + } 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. + */ + 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) { + rv = SECFailure; + if (PORT_GetError() == SEC_ERROR_UNKNOWN_CERT) { + /* Make the error a little more specific. */ + PORT_SetError(SEC_ERROR_UNKNOWN_SIGNER); + } + 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; + + /* + * Just because we have a cert does not mean it is any good; check + * it for validity, trust and usage. + */ + rv = CERT_VerifyCert(handle, signerCert, PR_TRUE, certUsage, checkTime, + pwArg, NULL); + if (rv != SECSuccess) { + PORT_SetError(SEC_ERROR_OCSP_INVALID_SIGNING_CERT); + goto finish; + } + + /* + * Now get the public key from the signer's certificate; we need + * it to perform the verification. + */ + signerKey = CERT_ExtractPublicKey(signerCert); + if (signerKey == NULL) + goto finish; + + /* + * Prepare the data to be verified; it needs to be DER encoded first. + */ + encodedTBS = SEC_ASN1EncodeItem(NULL, NULL, tbs, encodeTemplate); + if (encodedTBS == NULL) + goto finish; + + /* + * 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. + */ + rawSignature = signature->signature; + /* + * The raw signature is a bit string, but we need to represent its + * length in bytes, because that is what the verify function expects. + */ + DER_ConvertBitString(&rawSignature); + + rv = VFY_VerifyData(encodedTBS->data, encodedTBS->len, signerKey, + &rawSignature, + SECOID_GetAlgorithmTag(&signature->signatureAlgorithm), + 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); + } + } + + if (encodedTBS != NULL) + SECITEM_FreeItem(encodedTBS, PR_TRUE); + + if (signerKey != NULL) + SECKEY_DestroyPublicKey(signerKey); + + if (certs != NULL) + CERT_DestroyCertArray(certs, certCount); + /* Free CERTS from SPKDigest Table */ + + 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) +{ + ocspResponseData *tbsData; /* this is what is signed */ + PRBool byName; + void *certIndex; + int64 producedAt; + SECStatus rv; + + tbsData = ocsp_GetResponseData(response); + + PORT_Assert(tbsData->responderID != NULL); + switch (tbsData->responderID->responderIDType) { + case ocspResponderID_byName: + byName = PR_TRUE; + certIndex = &tbsData->responderID->responderIDValue.name; + break; + case ocspResponderID_byKey: + byName = PR_FALSE; + certIndex = &tbsData->responderID->responderIDValue.keyHash; + break; + case ocspResponderID_other: + default: + PORT_Assert(0); + PORT_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE); + return SECFailure; + } + + /* + * ocsp_CheckSignature 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) + return rv; + + return ocsp_CheckSignature(ocsp_GetResponseSignature(response), + tbsData, ocsp_ResponseDataTemplate, + handle, certUsageStatusResponder, producedAt, + byName, certIndex, pwArg, pSignerCert, issuer); +} + +/* + * See if two certIDs match. This can be easy or difficult, depending + * on whether the same hash algorithm was used. + */ +static PRBool +ocsp_CertIDsMatch(CERTCertDBHandle *handle, + CERTOCSPCertID *certID1, CERTOCSPCertID *certID2) +{ + PRBool match = PR_FALSE; + SECItem *foundHash = NULL; + 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(&certID1->serialNumber, + &certID2->serialNumber) != SECEqual) { + goto done; + } + + if (SECOID_CompareAlgorithmID(&certID1->hashAlgorithm, + &certID2->hashAlgorithm) == SECEqual) { + /* + * If the hash algorithms match then we can do a simple compare + * of the hash values themselves. + */ + if ((SECITEM_CompareItem(&certID1->issuerNameHash, + &certID2->issuerNameHash) == SECEqual) + && (SECITEM_CompareItem(&certID1->issuerKeyHash, + &certID2->issuerKeyHash) == SECEqual)) { + match = PR_TRUE; + } + goto done; + } + + hashAlg = SECOID_FindOIDTag(&certID2->hashAlgorithm.algorithm); + switch (hashAlg) { + case SEC_OID_SHA1: + keyHash = &certID1->issuerSHA1KeyHash; + nameHash = &certID1->issuerSHA1NameHash; + break; + case SEC_OID_MD5: + keyHash = &certID1->issuerMD5KeyHash; + nameHash = &certID1->issuerMD5NameHash; + break; + case SEC_OID_MD2: + keyHash = &certID1->issuerMD2KeyHash; + nameHash = &certID1->issuerMD2NameHash; + break; + default: + foundHash = NULL; + break; + } + + if (foundHash == NULL) { + goto done; + } + PORT_Assert(keyHash && nameHash); + + if ((SECITEM_CompareItem(nameHash, &certID2->issuerNameHash) == SECEqual) + && (SECITEM_CompareItem(keyHash, &certID2->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(handle, certID, single->certID) == PR_TRUE) { + 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 true if the given signerCert is the default responder for + * the given certID. If not, or if any error, return false. + */ +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 given signerCert is the default responder for + * the given certID. If not, or if any error, return false. + */ +static PRBool +ocsp_CertIsDefaultResponderForCertID(CERTCertDBHandle *handle, + CERTCertificate *signerCert, + CERTOCSPCertID *certID) +{ + CERTCertificate *defaultResponderCert; + + defaultResponderCert = ocsp_CertGetDefaultResponder(handle, certID); + return (PRBool) (defaultResponderCert == signerCert); +} + +/* + * 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, + int64 thisUpdate) +{ + CERTCertificate *issuerCert = NULL; + SECItem *issuerKeyHash = NULL; + SECOidTag hashAlg; + PRBool okay = PR_FALSE; + + /* + * Check first for a trusted responder, which overrides everything else. + */ + if (ocsp_CertIsDefaultResponderForCertID(handle, signerCert, certID)) + 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. + */ + + if (ocsp_CertIsOCSPSigner(signerCert)) { + /* + * 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); + goto loser; + } + } else { + /* + * The signer must *be* the issuer of the cert being checked. + */ + issuerCert = signerCert; + } + + hashAlg = SECOID_FindOIDTag(&certID->hashAlgorithm.algorithm); + issuerKeyHash = CERT_SPKDigestValueForCert(NULL, issuerCert, hashAlg, NULL); + if (issuerKeyHash == NULL) + goto loser; + + if (SECITEM_CompareItem(issuerKeyHash, + &certID->issuerKeyHash) != SECEqual) { + PORT_SetError(SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE); + goto loser; + } + + okay = PR_TRUE; + +loser: + if (issuerKeyHash != NULL) + SECITEM_FreeItem(issuerKeyHash, PR_TRUE); + + if (issuerCert != NULL && issuerCert != signerCert) + CERT_DestroyCertificate(issuerCert); + + return okay; +} + +/* + * 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(int64 checkTime) +{ + int64 now = PR_Now(); + int64 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 */ + +/* + * 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, + int64 producedAt) +{ + CERTOCSPCertID *certID = single->certID; + int64 now, thisUpdate, nextUpdate, tmstamp, tmp; + SECStatus rv; + + /* + * 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(tmstamp, tmstamp, tmp); /* convert the slop time to PRTime */ + LL_ADD(tmstamp, tmstamp, 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; + + if (LL_CMP(nextUpdate, <, now) || LL_CMP(producedAt, >, nextUpdate)) { + PORT_SetError(SEC_ERROR_OCSP_OLD_RESPONSE); + return SECFailure; + } + } else if (ocsp_TimeIsRecent(thisUpdate) != PR_TRUE) { + PORT_SetError(SEC_ERROR_OCSP_OLD_RESPONSE); + return SECFailure; + } + + 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_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. + */ +char * +CERT_GetOCSPAuthorityInfoAccessLocation(CERTCertificate *cert) +{ + CERTGeneralName *locname = NULL; + SECItem *location = NULL; + SECItem *encodedAuthInfoAccess = NULL; + CERTAuthInfoAccess **authInfoAccess = NULL; + char *locURI = NULL; + PRArenaPool *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) + 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_EXTENSION_NOT_FOUND); + 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_EXTENSION_NOT_FOUND); + 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 a default responder is set up, 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. + */ +static char * +ocsp_GetResponderLocation(CERTCertDBHandle *handle, CERTCertificate *cert, + PRBool *isDefault) +{ + ocspCheckingContext *ocspcx; + + 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; + return CERT_GetOCSPAuthorityInfoAccessLocation(cert); +} + +/* + * Return SECSuccess if the cert was revoked *after* "time", + * SECFailure otherwise. + */ +static SECStatus +ocsp_CertRevokedAfter(ocspRevokedInfo *revokedInfo, int64 time) +{ + int64 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. + */ +static SECStatus +ocsp_CertHasGoodStatus(CERTOCSPSingleResponse *single, int64 time) +{ + ocspCertStatus *status; + SECStatus rv; + + status = single->certStatus; + + 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; +} + + +/* + * 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) + * int64 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, + int64 time, void *pwArg) +{ + char *location = NULL; + PRBool locationIsDefault; + CERTCertList *certList = NULL; + SECItem *encodedResponse = NULL; + CERTOCSPRequest *request = NULL; + CERTOCSPResponse *response = NULL; + CERTCertificate *signerCert = NULL; + CERTCertificate *issuerCert = NULL; + CERTOCSPCertID *certID; + SECStatus rv = SECFailure; + + + /* + * 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, &locationIsDefault); + if (location == NULL) { + if (PORT_GetError() == SEC_ERROR_EXTENSION_NOT_FOUND) + return SECSuccess; + else + return SECFailure; + } + + /* + * For now, create a cert-list of one. + * 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.) + */ + + certList = CERT_NewCertList(); + if (certList == NULL) + goto loser; + + /* dup it because freeing the list will destroy the cert, too */ + cert = CERT_DupCertificate(cert); + if (cert == NULL) + goto loser; + + if (CERT_AddCertToListTail(certList, cert) != SECSuccess) { + CERT_DestroyCertificate(cert); + goto loser; + } + + /* + * 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. + */ + encodedResponse = CERT_GetEncodedOCSPResponse(NULL, certList, location, + time, locationIsDefault, + NULL, pwArg, &request); + if (encodedResponse == NULL) { + goto loser; + } + + response = CERT_DecodeOCSPResponse(encodedResponse); + if (response == NULL) { + goto loser; + } + + /* + * 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(response) != 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(response, 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. It will need + * to loop, and for each certID in the request, find the matching + * single response and check the status specified by it. + * + * We are helped here in that we know that the requests are made with + * the request list in the same order as the order of the certs we hand + * to it. This is why I can directly access the first member of the + * single request array for the one cert I care about. + */ + + certID = request->tbsRequest->requestList[0]->reqCert; + rv = CERT_GetOCSPStatusForCertID(handle, response, certID, + signerCert, time); +loser: + if (issuerCert != NULL) + CERT_DestroyCertificate(issuerCert); + if (signerCert != NULL) + CERT_DestroyCertificate(signerCert); + if (response != NULL) + CERT_DestroyOCSPResponse(response); + if (request != NULL) + CERT_DestroyOCSPRequest(request); + if (encodedResponse != NULL) + SECITEM_FreeItem(encodedResponse, PR_TRUE); + if (certList != NULL) + CERT_DestroyCertList(certList); + if (location != NULL) + PORT_Free(location); + return rv; +} + +SECStatus +CERT_GetOCSPStatusForCertID(CERTCertDBHandle *handle, + CERTOCSPResponse *response, + CERTOCSPCertID *certID, + CERTCertificate *signerCert, + int64 time) +{ + SECStatus rv; + ocspResponseData *responseData; + int64 producedAt; + CERTOCSPSingleResponse *single; + + /* + * The ResponseData part is the real guts of the response. + */ + responseData = ocsp_GetResponseData(response); + 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; + + /* + * Okay, the last step is to check whether the status says revoked, + * and if so how that compares to the time value passed into this routine. + */ + + rv = ocsp_CertHasGoodStatus(single, time); +loser: + 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; + } + + /* + * 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 (statusContext != NULL) + PORT_Free(statusContext); + 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; + } else { + PORT_Assert(statusContext->useDefaultResponder == PR_FALSE); + CERT_DestroyCertificate(cert); + } + + 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; + + 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; + + /* + * And hang onto it. + */ + statusContext->defaultResponderCert = cert; + + /* + * 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; + + 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; + + if (statusContext->defaultResponderCert != NULL) { + CERT_DestroyCertificate(statusContext->defaultResponderCert); + statusContext->defaultResponderCert = NULL; + } + + /* + * Finally, record the fact. + */ + statusContext->useDefaultResponder = PR_FALSE; + return SECSuccess; +} + +/* + * 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_SPKDigestValueForCert(PRArenaPool *arena, CERTCertificate *cert, + SECOidTag digestAlg, SECItem *fill) +{ + const SECHashObject *digestObject; + void *digestContext; + SECItem *result = NULL; + void *mark = NULL; + SECItem spk; + + 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; + } + fill = result; + } + + /* + * 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); + + /* + * Now digest the value, using the specified algorithm. + */ + digestContext = digestObject->create(); + if ( digestContext == NULL ) { + goto loser; + } + digestObject->begin(digestContext); + digestObject->update(digestContext, spk.data, spk.len); + digestObject->end(digestContext, fill->data, &(fill->len), fill->len); + digestObject->destroy(digestContext, PR_TRUE); + + if ( arena != NULL ) { + PORT_ArenaUnmark(arena, mark); + } + return(fill); + +loser: + if ( arena != NULL ) { + PORT_ArenaRelease(arena, mark); + } else { + if ( result != NULL ) { + SECITEM_FreeItem(result, (fill == NULL) ? PR_TRUE : PR_FALSE); + } + } + return(NULL); +} + +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_other: + 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 000000000..428c624d4 --- /dev/null +++ b/security/nss/lib/certhigh/ocsp.h @@ -0,0 +1,534 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * Interface to the OCSP implementation. + * + * $Id$ + */ + +#ifndef _OCSP_H_ +#define _OCSP_H_ + + +#include "plarena.h" +#include "seccomon.h" +#include "secoidt.h" +#include "keyt.h" +#include "certt.h" +#include "ocspt.h" + + +/************************************************************************/ +SEC_BEGIN_PROTOS + +/* + * 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. + * 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.). + */ +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); + +/* + * ------------------------------------------------------- + * 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. + * int64 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, int64 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: + * PRArenaPool *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(PRArenaPool *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(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(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: + * PRArenaPool *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. + * char *location + * The location of the OCSP responder (a URL). + * int64 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(PRArenaPool *arena, CERTCertList *certList, + char *location, int64 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(CERTCertificate *cert); + +/* + * 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) + * int64 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, + int64 time, void *pwArg); +/* + * FUNCTION: CERT_GetOCSPStatusForCertID + * Returns the OCSP status contained in the passed in paramter 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. + * int64 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, + int64 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. + * int64 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, int64 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); +/************************************************************************/ +SEC_END_PROTOS + +#endif /* _OCSP_H_ */ diff --git a/security/nss/lib/certhigh/ocspt.h b/security/nss/lib/certhigh/ocspt.h new file mode 100644 index 000000000..3f1563855 --- /dev/null +++ b/security/nss/lib/certhigh/ocspt.h @@ -0,0 +1,59 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * Public header for exported OCSP types. + * + * $Id$ + */ + +#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 CERTOCSPCertStatusStr CERTOCSPCertStatus; +typedef struct CERTOCSPSingleResponseStr CERTOCSPSingleResponse; + +#endif /* _OCSPT_H_ */ diff --git a/security/nss/lib/certhigh/ocspti.h b/security/nss/lib/certhigh/ocspti.h new file mode 100644 index 000000000..2bb7bfe72 --- /dev/null +++ b/security/nss/lib/certhigh/ocspti.h @@ -0,0 +1,405 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * Private header defining OCSP types. + * + * $Id$ + */ + +#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 { + PRArenaPool *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 { + PRArenaPool *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; + PRArenaPool *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_successful = 0, + ocspResponse_malformedRequest = 1, + ocspResponse_internalError = 2, + ocspResponse_tryLater = 3, + ocspResponse_unused = 4, + ocspResponse_sigRequired = 5, + ocspResponse_unauthorized = 6, + ocspResponse_other /* unknown/unrecognized value */ +} 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 { + PRArenaPool *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 { + 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; +}; + +/* + * 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_byName, + ocspResponderID_byKey, + ocspResponderID_other /* unknown kind of responderID */ +} ocspResponderIDType; + +struct ocspResponderIDStr { + ocspResponderIDType 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 { + PRArenaPool *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 000000000..d428d73c8 --- /dev/null +++ b/security/nss/lib/certhigh/xcrldist.c @@ -0,0 +1,230 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * Code for dealing with x.509 v3 CRL Distribution Point extension. + */ +#include "genname.h" +#include "certt.h" +#include "secerr.h" + +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 CRLDistributionPointTemplate[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(CRLDistributionPoint) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | + SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 0, + offsetof(CRLDistributionPoint,derDistPoint), SEC_AnyTemplate}, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(CRLDistributionPoint,bitsmap), 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 (PRArenaPool *arena, CERTCrlDistributionPoints *value, + SECItem *derValue) +{ + CRLDistributionPoint **pointList, *point; + PRArenaPool *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; + + if (point->distPointType == generalName) { + point->derFullName = cert_EncodeGeneralNames + (ourPool, point->distPoint.fullName); + + if (point->derFullName) { + rv = (SEC_ASN1EncodeItem (ourPool, &point->derDistPoint, + point, FullNameTemplate) == NULL) ? SECFailure : SECSuccess; + } else { + rv = SECFailure; + } + } + else if (point->distPointType == relativeDistinguishedName) { + if (SEC_ASN1EncodeItem + (ourPool, &point->derDistPoint, + point, RelativeNameTemplate) == NULL) + rv = SECFailure; + } + /* distributionPointName is omitted */ + else if (point->distPointType != 0) { + PORT_SetError (SEC_ERROR_EXTENSION_VALUE_INVALID); + rv = SECFailure; + } + 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->crlIssuer) + break; + } + + ++pointList; + } + if (rv != SECSuccess) + break; + if (SEC_ASN1EncodeItem + (arena, derValue, value, CERTCRLDistributionPointsTemplate) == NULL) { + rv = SECFailure; + break; + } + } while (0); + PORT_FreeArena (ourPool, PR_FALSE); + return (rv); +} + +CERTCrlDistributionPoints * +CERT_DecodeCRLDistributionPoints (PRArenaPool *arena, SECItem *encodedValue) +{ + CERTCrlDistributionPoints *value = NULL; + CRLDistributionPoint **pointList, *point; + SECStatus rv; + SECItem newEncodedValue; + + PORT_Assert (arena); + do { + value = (CERTCrlDistributionPoints*)PORT_ArenaZAlloc (arena, sizeof (*value)); + 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 (*pointList) { + point = *pointList; + + /* get the data if the distributionPointName is not omitted */ + if (point->derDistPoint.data != NULL) { + point->distPointType = (DistributionPointTypes) + ((point->derDistPoint.data[0] & 0x1f) +1); + if (point->distPointType == generalName) { + SECItem innerDER; + + innerDER.data = NULL; + rv = SEC_QuickDERDecodeItem + (arena, point, FullNameTemplate, &(point->derDistPoint)); + if (rv != SECSuccess) + break; + point->distPoint.fullName = cert_DecodeGeneralNames + (arena, point->derFullName); + + if (!point->distPoint.fullName) + break; + } + else if ( relativeDistinguishedName) { + rv = SEC_QuickDERDecodeItem + (arena, point, RelativeNameTemplate, &(point->derDistPoint)); + if (rv != SECSuccess) + break; + } + else { + PORT_SetError (SEC_ERROR_EXTENSION_VALUE_INVALID); + break; + } + } + + /* Get the reason code if it's not omitted in the encoding */ + if (point->bitsmap.data != NULL) { + point->reasons.data = (unsigned char*) PORT_ArenaAlloc + (arena, (point->bitsmap.len + 7) >> 3); + if (!point->reasons.data) { + rv = SECFailure; + break; + } + PORT_Memcpy (point->reasons.data, point->bitsmap.data, + point->reasons.len = ((point->bitsmap.len + 7) >> 3)); + } + + /* 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; + } + } while (0); + return (rv == SECSuccess ? value : NULL); +} |