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