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