andre@0: /* This Source Code Form is subject to the terms of the Mozilla Public andre@0: * License, v. 2.0. If a copy of the MPL was not distributed with this andre@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ andre@0: andre@0: #include "cert.h" andre@0: #include "base64.h" andre@0: #include "secitem.h" andre@0: #include "secder.h" andre@0: #include "secasn1.h" andre@0: #include "secoid.h" andre@0: #include "secerr.h" andre@0: andre@0: SEC_ASN1_MKSUB(SEC_AnyTemplate) andre@0: SEC_ASN1_MKSUB(SEC_SetOfAnyTemplate) andre@0: andre@0: typedef struct ContentInfoStr ContentInfo; andre@0: typedef struct DegenerateSignedDataStr DegenerateSignedData; andre@0: andre@0: struct ContentInfoStr { andre@0: SECOidTag contentTypeTag; /* local; not part of encoding */ andre@0: SECItem contentType; andre@0: union { andre@0: SECItem *data; andre@0: DegenerateSignedData *signedData; andre@0: } content; andre@0: }; andre@0: andre@0: struct DegenerateSignedDataStr { andre@0: SECItem version; andre@0: SECItem **digestAlgorithms; andre@0: ContentInfo contentInfo; andre@0: SECItem **certificates; andre@0: SECItem **crls; andre@0: SECItem **signerInfos; andre@0: }; andre@0: andre@0: static const SEC_ASN1Template * andre@0: choose_content_template(void *src_or_dest, PRBool encoding); andre@0: andre@0: static const SEC_ASN1TemplateChooserPtr template_chooser andre@0: = choose_content_template; andre@0: andre@0: static const SEC_ASN1Template ContentInfoTemplate[] = { andre@0: { SEC_ASN1_SEQUENCE, andre@0: 0, NULL, sizeof(ContentInfo) }, andre@0: { SEC_ASN1_OBJECT_ID, andre@0: offsetof(ContentInfo,contentType) }, andre@0: { SEC_ASN1_OPTIONAL | SEC_ASN1_DYNAMIC | andre@0: SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, andre@0: offsetof(ContentInfo,content), andre@0: &template_chooser }, andre@0: { 0 } andre@0: }; andre@0: andre@0: static const SEC_ASN1Template DegenerateSignedDataTemplate[] = { andre@0: { SEC_ASN1_SEQUENCE, andre@0: 0, NULL, sizeof(DegenerateSignedData) }, andre@0: { SEC_ASN1_INTEGER, andre@0: offsetof(DegenerateSignedData,version) }, andre@0: { SEC_ASN1_SET_OF | SEC_ASN1_XTRN, andre@0: offsetof(DegenerateSignedData,digestAlgorithms), andre@0: SEC_ASN1_SUB(SEC_AnyTemplate) }, andre@0: { SEC_ASN1_INLINE, andre@0: offsetof(DegenerateSignedData,contentInfo), andre@0: ContentInfoTemplate }, andre@0: { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | andre@0: SEC_ASN1_XTRN | 0, andre@0: offsetof(DegenerateSignedData,certificates), andre@0: SEC_ASN1_SUB(SEC_SetOfAnyTemplate) }, andre@0: { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | andre@0: SEC_ASN1_XTRN | 1, andre@0: offsetof(DegenerateSignedData,crls), andre@0: SEC_ASN1_SUB(SEC_SetOfAnyTemplate) }, andre@0: { SEC_ASN1_SET_OF | SEC_ASN1_XTRN, andre@0: offsetof(DegenerateSignedData,signerInfos), andre@0: SEC_ASN1_SUB(SEC_AnyTemplate) }, andre@0: { 0 } andre@0: }; andre@0: andre@0: static const SEC_ASN1Template PointerToDegenerateSignedDataTemplate[] = { andre@0: { SEC_ASN1_POINTER, 0, DegenerateSignedDataTemplate } andre@0: }; andre@0: andre@0: static SECOidTag andre@0: GetContentTypeTag(ContentInfo *cinfo) andre@0: { andre@0: if (cinfo->contentTypeTag == SEC_OID_UNKNOWN) andre@0: cinfo->contentTypeTag = SECOID_FindOIDTag(&cinfo->contentType); andre@0: return cinfo->contentTypeTag; andre@0: } andre@0: andre@0: static const SEC_ASN1Template * andre@0: choose_content_template(void *src_or_dest, PRBool encoding) andre@0: { andre@0: const SEC_ASN1Template *theTemplate; andre@0: ContentInfo *cinfo; andre@0: SECOidTag kind; andre@0: andre@0: PORT_Assert(src_or_dest != NULL); andre@0: if (src_or_dest == NULL) andre@0: return NULL; andre@0: andre@0: cinfo = (ContentInfo*)src_or_dest; andre@0: kind = GetContentTypeTag(cinfo); andre@0: switch (kind) { andre@0: default: andre@0: theTemplate = SEC_ASN1_GET(SEC_PointerToAnyTemplate); andre@0: break; andre@0: case SEC_OID_PKCS7_DATA: andre@0: theTemplate = SEC_ASN1_GET(SEC_PointerToOctetStringTemplate); andre@0: break; andre@0: case SEC_OID_PKCS7_SIGNED_DATA: andre@0: theTemplate = PointerToDegenerateSignedDataTemplate; andre@0: break; andre@0: } andre@0: return theTemplate; andre@0: } andre@0: andre@0: static SECStatus andre@0: SEC_ReadPKCS7Certs(SECItem *pkcs7Item, CERTImportCertificateFunc f, void *arg) andre@0: { andre@0: ContentInfo contentInfo; andre@0: SECStatus rv; andre@0: SECItem **certs; andre@0: int count; andre@0: PLArenaPool *arena; andre@0: andre@0: arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); andre@0: if ( arena == NULL ) { andre@0: return SECFailure; andre@0: } andre@0: andre@0: PORT_Memset(&contentInfo, 0, sizeof(contentInfo)); andre@0: rv = SEC_ASN1DecodeItem(arena, &contentInfo, ContentInfoTemplate, andre@0: pkcs7Item); andre@0: if ( rv != SECSuccess ) { andre@0: goto loser; andre@0: } andre@0: andre@0: if ( GetContentTypeTag(&contentInfo) != SEC_OID_PKCS7_SIGNED_DATA ) { andre@0: goto loser; andre@0: } andre@0: andre@0: certs = contentInfo.content.signedData->certificates; andre@0: if ( certs ) { andre@0: count = 0; andre@0: andre@0: while ( *certs ) { andre@0: count++; andre@0: certs++; andre@0: } andre@0: rv = (* f)(arg, contentInfo.content.signedData->certificates, count); andre@0: } andre@0: andre@0: rv = SECSuccess; andre@0: andre@0: goto done; andre@0: loser: andre@0: rv = SECFailure; andre@0: andre@0: done: andre@0: if ( arena ) { andre@0: PORT_FreeArena(arena, PR_FALSE); andre@0: } andre@0: andre@0: return(rv); andre@0: } andre@0: andre@0: const SEC_ASN1Template SEC_CertSequenceTemplate[] = { andre@0: { SEC_ASN1_SEQUENCE_OF | SEC_ASN1_XTRN, 0, SEC_ASN1_SUB(SEC_AnyTemplate) } andre@0: }; andre@0: andre@0: static SECStatus andre@0: SEC_ReadCertSequence(SECItem *certsItem, CERTImportCertificateFunc f, void *arg) andre@0: { andre@0: SECStatus rv; andre@0: SECItem **certs; andre@0: int count; andre@0: SECItem **rawCerts = NULL; andre@0: PLArenaPool *arena; andre@0: ContentInfo contentInfo; andre@0: andre@0: arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); andre@0: if ( arena == NULL ) { andre@0: return SECFailure; andre@0: } andre@0: andre@0: PORT_Memset(&contentInfo, 0, sizeof(contentInfo)); andre@0: rv = SEC_ASN1DecodeItem(arena, &contentInfo, ContentInfoTemplate, andre@0: certsItem); andre@0: if ( rv != SECSuccess ) { andre@0: goto loser; andre@0: } andre@0: andre@0: if ( GetContentTypeTag(&contentInfo) != SEC_OID_NS_TYPE_CERT_SEQUENCE ) { andre@0: goto loser; andre@0: } andre@0: andre@0: rv = SEC_QuickDERDecodeItem(arena, &rawCerts, SEC_CertSequenceTemplate, andre@0: contentInfo.content.data); andre@0: andre@0: if (rv != SECSuccess) { andre@0: goto loser; andre@0: } andre@0: andre@0: certs = rawCerts; andre@0: if ( certs ) { andre@0: count = 0; andre@0: andre@0: while ( *certs ) { andre@0: count++; andre@0: certs++; andre@0: } andre@0: rv = (* f)(arg, rawCerts, count); andre@0: } andre@0: andre@0: rv = SECSuccess; andre@0: andre@0: goto done; andre@0: loser: andre@0: rv = SECFailure; andre@0: andre@0: done: andre@0: if ( arena ) { andre@0: PORT_FreeArena(arena, PR_FALSE); andre@0: } andre@0: andre@0: return(rv); andre@0: } andre@0: andre@0: CERTCertificate * andre@0: CERT_ConvertAndDecodeCertificate(char *certstr) andre@0: { andre@0: CERTCertificate *cert; andre@0: SECStatus rv; andre@0: SECItem der; andre@0: andre@0: rv = ATOB_ConvertAsciiToItem(&der, certstr); andre@0: if (rv != SECSuccess) andre@0: return NULL; andre@0: andre@0: cert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), andre@0: &der, NULL, PR_FALSE, PR_TRUE); andre@0: andre@0: PORT_Free(der.data); andre@0: return cert; andre@0: } andre@0: andre@0: static const char NS_CERT_HEADER[] = "-----BEGIN CERTIFICATE-----"; andre@0: static const char NS_CERT_TRAILER[] = "-----END CERTIFICATE-----"; andre@0: #define NS_CERT_HEADER_LEN ((sizeof NS_CERT_HEADER) - 1) andre@0: #define NS_CERT_TRAILER_LEN ((sizeof NS_CERT_TRAILER) - 1) andre@0: andre@0: /* andre@0: * read an old style ascii or binary certificate chain andre@0: */ andre@0: SECStatus andre@0: CERT_DecodeCertPackage(char *certbuf, andre@0: int certlen, andre@0: CERTImportCertificateFunc f, andre@0: void *arg) andre@0: { andre@0: unsigned char *cp; andre@0: unsigned char *bincert = NULL; andre@0: char * ascCert = NULL; andre@0: SECStatus rv; andre@0: andre@0: if ( certbuf == NULL ) { andre@0: PORT_SetError(SEC_ERROR_INVALID_ARGS); andre@0: return(SECFailure); andre@0: } andre@0: /* andre@0: * Make sure certlen is long enough to handle the longest possible andre@0: * reference in the code below: andre@0: * 0x30 0x84 l1 l2 l3 l4 + andre@0: * tag 9 o1 o2 o3 o4 o5 o6 o7 o8 o9 andre@0: * where 9 is the longest length of the expected oids we are testing. andre@0: * 6 + 11 = 17. 17 bytes is clearly too small to code any kind of andre@0: * certificate (a 128 bit ECC certificate contains at least an 8 byte andre@0: * key and a 16 byte signature, plus coding overhead). Typically a cert andre@0: * is much larger. So it's safe to require certlen to be at least 17 andre@0: * bytes. andre@0: */ andre@0: if (certlen < 17) { andre@0: PORT_SetError(SEC_ERROR_INPUT_LEN); andre@0: return(SECFailure); andre@0: } andre@0: andre@0: cp = (unsigned char *)certbuf; andre@0: andre@0: /* is a DER encoded certificate of some type? */ andre@0: if ( ( *cp & 0x1f ) == SEC_ASN1_SEQUENCE ) { andre@0: SECItem certitem; andre@0: SECItem *pcertitem = &certitem; andre@0: int seqLen, seqLenLen; andre@0: andre@0: cp++; andre@0: andre@0: if ( *cp & 0x80) { andre@0: /* Multibyte length */ andre@0: seqLenLen = cp[0] & 0x7f; andre@0: andre@0: switch (seqLenLen) { andre@0: case 4: andre@0: seqLen = ((unsigned long)cp[1]<<24) | andre@0: ((unsigned long)cp[2]<<16) | (cp[3]<<8) | cp[4]; andre@0: break; andre@0: case 3: andre@0: seqLen = ((unsigned long)cp[1]<<16) | (cp[2]<<8) | cp[3]; andre@0: break; andre@0: case 2: andre@0: seqLen = (cp[1]<<8) | cp[2]; andre@0: break; andre@0: case 1: andre@0: seqLen = cp[1]; andre@0: break; andre@0: case 0: andre@0: /* indefinite length */ andre@0: seqLen = 0; andre@0: break; andre@0: default: andre@0: goto notder; andre@0: } andre@0: cp += ( seqLenLen + 1 ); andre@0: andre@0: } else { andre@0: seqLenLen = 0; andre@0: seqLen = *cp; andre@0: cp++; andre@0: } andre@0: andre@0: /* check entire length if definite length */ andre@0: if ( seqLen || seqLenLen ) { andre@0: if ( certlen != ( seqLen + seqLenLen + 2 ) ) { andre@0: if (certlen > ( seqLen + seqLenLen + 2 )) andre@0: PORT_SetError(SEC_ERROR_EXTRA_INPUT); andre@0: else andre@0: PORT_SetError(SEC_ERROR_INPUT_LEN); andre@0: goto notder; andre@0: } andre@0: } andre@0: andre@0: /* check the type oid */ andre@0: if ( cp[0] == SEC_ASN1_OBJECT_ID ) { andre@0: SECOidData *oiddata; andre@0: SECItem oiditem; andre@0: /* XXX - assume DER encoding of OID len!! */ andre@0: oiditem.len = cp[1]; andre@0: /* if we add an oid below that is longer than 9 bytes, then we andre@0: * need to change the certlen check at the top of the function andre@0: * to prevent a buffer overflow andre@0: */ andre@0: if ( oiditem.len > 9 ) { andre@0: PORT_SetError(SEC_ERROR_UNRECOGNIZED_OID); andre@0: return(SECFailure); andre@0: } andre@0: oiditem.data = (unsigned char *)&cp[2]; andre@0: oiddata = SECOID_FindOID(&oiditem); andre@0: if ( oiddata == NULL ) { andre@0: return(SECFailure); andre@0: } andre@0: andre@0: certitem.data = (unsigned char*)certbuf; andre@0: certitem.len = certlen; andre@0: andre@0: switch ( oiddata->offset ) { andre@0: case SEC_OID_PKCS7_SIGNED_DATA: andre@0: /* oid: 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02 */ andre@0: return(SEC_ReadPKCS7Certs(&certitem, f, arg)); andre@0: break; andre@0: case SEC_OID_NS_TYPE_CERT_SEQUENCE: andre@0: /* oid: 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x02, 0x05 */ andre@0: return(SEC_ReadCertSequence(&certitem, f, arg)); andre@0: break; andre@0: default: andre@0: break; andre@0: } andre@0: andre@0: } else { andre@0: /* it had better be a certificate by now!! */ andre@0: certitem.data = (unsigned char*)certbuf; andre@0: certitem.len = certlen; andre@0: andre@0: rv = (* f)(arg, &pcertitem, 1); andre@0: return(rv); andre@0: } andre@0: } andre@0: andre@0: /* now look for a netscape base64 ascii encoded cert */ andre@0: notder: andre@0: { andre@0: unsigned char *certbegin = NULL; andre@0: unsigned char *certend = NULL; andre@0: char *pc; andre@0: int cl; andre@0: andre@0: /* Convert the ASCII data into a nul-terminated string */ andre@0: ascCert = (char *)PORT_Alloc(certlen + 1); andre@0: if (!ascCert) { andre@0: rv = SECFailure; andre@0: goto loser; andre@0: } andre@0: andre@0: PORT_Memcpy(ascCert, certbuf, certlen); andre@0: ascCert[certlen] = '\0'; andre@0: andre@0: pc = PORT_Strchr(ascCert, '\n'); /* find an EOL */ andre@0: if (!pc) { /* maybe this is a MAC file */ andre@0: pc = ascCert; andre@0: while (*pc && NULL != (pc = PORT_Strchr(pc, '\r'))) { andre@0: *pc++ = '\n'; andre@0: } andre@0: } andre@0: andre@0: cp = (unsigned char *)ascCert; andre@0: cl = certlen; andre@0: andre@0: /* find the beginning marker */ andre@0: while ( cl > NS_CERT_HEADER_LEN ) { andre@0: int found = 0; andre@0: if ( !PORT_Strncasecmp((char *)cp, NS_CERT_HEADER, andre@0: NS_CERT_HEADER_LEN) ) { andre@0: cl -= NS_CERT_HEADER_LEN; andre@0: cp += NS_CERT_HEADER_LEN; andre@0: found = 1; andre@0: } andre@0: andre@0: /* skip to next eol */ andre@0: while ( cl && ( *cp != '\n' )) { andre@0: cp++; andre@0: cl--; andre@0: } andre@0: andre@0: /* skip all blank lines */ andre@0: while ( cl && ( *cp == '\n' || *cp == '\r' )) { andre@0: cp++; andre@0: cl--; andre@0: } andre@0: if (cl && found) { andre@0: certbegin = cp; andre@0: break; andre@0: } andre@0: } andre@0: andre@0: if ( certbegin ) { andre@0: /* find the ending marker */ andre@0: while ( cl >= NS_CERT_TRAILER_LEN ) { andre@0: if ( !PORT_Strncasecmp((char *)cp, NS_CERT_TRAILER, andre@0: NS_CERT_TRAILER_LEN) ) { andre@0: certend = cp; andre@0: break; andre@0: } andre@0: andre@0: /* skip to next eol */ andre@0: while ( cl && ( *cp != '\n' )) { andre@0: cp++; andre@0: cl--; andre@0: } andre@0: andre@0: /* skip all blank lines */ andre@0: while ( cl && ( *cp == '\n' || *cp == '\r' )) { andre@0: cp++; andre@0: cl--; andre@0: } andre@0: } andre@0: } andre@0: andre@0: if ( certbegin && certend ) { andre@0: unsigned int binLen; andre@0: andre@0: *certend = 0; andre@0: /* convert to binary */ andre@0: bincert = ATOB_AsciiToData((char *)certbegin, &binLen); andre@0: if (!bincert) { andre@0: rv = SECFailure; andre@0: goto loser; andre@0: } andre@0: andre@0: /* now recurse to decode the binary */ andre@0: rv = CERT_DecodeCertPackage((char *)bincert, binLen, f, arg); andre@0: andre@0: } else { andre@0: PORT_SetError(SEC_ERROR_BAD_DER); andre@0: rv = SECFailure; andre@0: } andre@0: } andre@0: andre@0: loser: andre@0: andre@0: if ( bincert ) { andre@0: PORT_Free(bincert); andre@0: } andre@0: andre@0: if ( ascCert ) { andre@0: PORT_Free(ascCert); andre@0: } andre@0: andre@0: return(rv); andre@0: } andre@0: andre@0: typedef struct { andre@0: PLArenaPool *arena; andre@0: SECItem cert; andre@0: } collect_args; andre@0: andre@0: static SECStatus andre@0: collect_certs(void *arg, SECItem **certs, int numcerts) andre@0: { andre@0: SECStatus rv; andre@0: collect_args *collectArgs; andre@0: andre@0: collectArgs = (collect_args *)arg; andre@0: andre@0: rv = SECITEM_CopyItem(collectArgs->arena, &collectArgs->cert, *certs); andre@0: andre@0: return(rv); andre@0: } andre@0: andre@0: andre@0: /* andre@0: * read an old style ascii or binary certificate andre@0: */ andre@0: CERTCertificate * andre@0: CERT_DecodeCertFromPackage(char *certbuf, int certlen) andre@0: { andre@0: collect_args collectArgs; andre@0: SECStatus rv; andre@0: CERTCertificate *cert = NULL; andre@0: andre@0: collectArgs.arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); andre@0: andre@0: rv = CERT_DecodeCertPackage(certbuf, certlen, collect_certs, andre@0: (void *)&collectArgs); andre@0: if ( rv == SECSuccess ) { andre@0: cert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), andre@0: &collectArgs.cert, NULL, andre@0: PR_FALSE, PR_TRUE); andre@0: } andre@0: andre@0: PORT_FreeArena(collectArgs.arena, PR_FALSE); andre@0: andre@0: return(cert); andre@0: }