summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/pkcs7/certread.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/lib/pkcs7/certread.c')
-rw-r--r--security/nss/lib/pkcs7/certread.c535
1 files changed, 535 insertions, 0 deletions
diff --git a/security/nss/lib/pkcs7/certread.c b/security/nss/lib/pkcs7/certread.c
new file mode 100644
index 0000000000..15094f2d78
--- /dev/null
+++ b/security/nss/lib/pkcs7/certread.c
@@ -0,0 +1,535 @@
+/* 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);
+}