blob: 88812c771edc44c4abd517ffc609c8f9788887f2 [file] [log] [blame]
/* 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);
}