summaryrefslogtreecommitdiff
path: root/nss/lib/pkcs7/certread.c
diff options
context:
space:
mode:
Diffstat (limited to 'nss/lib/pkcs7/certread.c')
-rw-r--r--nss/lib/pkcs7/certread.c543
1 files changed, 543 insertions, 0 deletions
diff --git a/nss/lib/pkcs7/certread.c b/nss/lib/pkcs7/certread.c
new file mode 100644
index 0000000..88812c7
--- /dev/null
+++ b/nss/lib/pkcs7/certread.c
@@ -0,0 +1,543 @@
+/* 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;
+ SECItem **certs;
+ int count;
+ PLArenaPool *arena;
+
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if ( arena == NULL ) {
+ return SECFailure;
+ }
+
+ PORT_Memset(&contentInfo, 0, sizeof(contentInfo));
+ rv = SEC_ASN1DecodeItem(arena, &contentInfo, ContentInfoTemplate,
+ pkcs7Item);
+ if ( rv != SECSuccess ) {
+ goto loser;
+ }
+
+ if ( GetContentTypeTag(&contentInfo) != SEC_OID_PKCS7_SIGNED_DATA ) {
+ goto loser;
+ }
+
+ certs = contentInfo.content.signedData->certificates;
+ if ( certs ) {
+ count = 0;
+
+ while ( *certs ) {
+ count++;
+ certs++;
+ }
+ rv = (* f)(arg, contentInfo.content.signedData->certificates, count);
+ }
+
+ rv = SECSuccess;
+
+ goto done;
+loser:
+ rv = SECFailure;
+
+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;
+ SECItem **certs;
+ int count;
+ SECItem **rawCerts = NULL;
+ PLArenaPool *arena;
+ ContentInfo contentInfo;
+
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ if ( arena == NULL ) {
+ return SECFailure;
+ }
+
+ PORT_Memset(&contentInfo, 0, sizeof(contentInfo));
+ rv = SEC_ASN1DecodeItem(arena, &contentInfo, ContentInfoTemplate,
+ certsItem);
+ if ( rv != SECSuccess ) {
+ goto loser;
+ }
+
+ if ( GetContentTypeTag(&contentInfo) != SEC_OID_NS_TYPE_CERT_SEQUENCE ) {
+ goto loser;
+ }
+
+ rv = SEC_QuickDERDecodeItem(arena, &rawCerts, SEC_CertSequenceTemplate,
+ contentInfo.content.data);
+
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ certs = rawCerts;
+ if ( certs ) {
+ count = 0;
+
+ while ( *certs ) {
+ count++;
+ certs++;
+ }
+ rv = (* f)(arg, rawCerts, count);
+ }
+
+ rv = SECSuccess;
+
+ goto done;
+loser:
+ rv = SECFailure;
+
+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;
+ int 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 + 2 ) ) {
+ if (certlen > ( seqLen + seqLenLen + 2 ))
+ 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)
+{
+ SECStatus rv;
+ collect_args *collectArgs;
+
+ collectArgs = (collect_args *)arg;
+
+ rv = SECITEM_CopyItem(collectArgs->arena, &collectArgs->cert, *certs);
+
+ return(rv);
+}
+
+
+/*
+ * 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);
+}