diff options
Diffstat (limited to 'security/nss/lib/pkcs7')
-rw-r--r-- | security/nss/lib/pkcs7/Makefile | 46 | ||||
-rw-r--r-- | security/nss/lib/pkcs7/certread.c | 535 | ||||
-rw-r--r-- | security/nss/lib/pkcs7/exports.gyp | 33 | ||||
-rw-r--r-- | security/nss/lib/pkcs7/manifest.mn | 34 | ||||
-rw-r--r-- | security/nss/lib/pkcs7/p7common.c | 663 | ||||
-rw-r--r-- | security/nss/lib/pkcs7/p7create.c | 1306 | ||||
-rw-r--r-- | security/nss/lib/pkcs7/p7decode.c | 1919 | ||||
-rw-r--r-- | security/nss/lib/pkcs7/p7encode.c | 1079 | ||||
-rw-r--r-- | security/nss/lib/pkcs7/p7local.c | 1309 | ||||
-rw-r--r-- | security/nss/lib/pkcs7/p7local.h | 137 | ||||
-rw-r--r-- | security/nss/lib/pkcs7/pkcs7.gyp | 29 | ||||
-rw-r--r-- | security/nss/lib/pkcs7/pkcs7t.h | 233 | ||||
-rw-r--r-- | security/nss/lib/pkcs7/secmime.c | 800 | ||||
-rw-r--r-- | security/nss/lib/pkcs7/secmime.h | 160 | ||||
-rw-r--r-- | security/nss/lib/pkcs7/secpkcs7.h | 626 |
15 files changed, 8909 insertions, 0 deletions
diff --git a/security/nss/lib/pkcs7/Makefile b/security/nss/lib/pkcs7/Makefile new file mode 100644 index 0000000000..aae54b1e0f --- /dev/null +++ b/security/nss/lib/pkcs7/Makefile @@ -0,0 +1,46 @@ +#! gmake +# +# 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/. + +####################################################################### +# (1) Include initial platform-independent assignments (MANDATORY). # +####################################################################### + +include manifest.mn + +####################################################################### +# (2) Include "global" configuration information. (OPTIONAL) # +####################################################################### + +include $(CORE_DEPTH)/coreconf/config.mk + +####################################################################### +# (3) Include "component" configuration information. (OPTIONAL) # +####################################################################### + + + +####################################################################### +# (4) Include "local" platform-dependent assignments (OPTIONAL). # +####################################################################### + +####################################################################### +# (5) Execute "global" rules. (OPTIONAL) # +####################################################################### + +include $(CORE_DEPTH)/coreconf/rules.mk + +####################################################################### +# (6) Execute "component" rules. (OPTIONAL) # +####################################################################### + + + +####################################################################### +# (7) Execute "local" rules. (OPTIONAL). # +####################################################################### + + + 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); +} diff --git a/security/nss/lib/pkcs7/exports.gyp b/security/nss/lib/pkcs7/exports.gyp new file mode 100644 index 0000000000..e5c9f09275 --- /dev/null +++ b/security/nss/lib/pkcs7/exports.gyp @@ -0,0 +1,33 @@ +# 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/. +{ + 'includes': [ + '../../coreconf/config.gypi' + ], + 'targets': [ + { + 'target_name': 'lib_pkcs7_exports', + 'type': 'none', + 'copies': [ + { + 'files': [ + 'pkcs7t.h', + 'secmime.h', + 'secpkcs7.h' + ], + 'destination': '<(nss_public_dist_dir)/<(module)' + }, + { + 'files': [ + 'p7local.h' + ], + 'destination': '<(nss_private_dist_dir)/<(module)' + } + ] + } + ], + 'variables': { + 'module': 'nss' + } +} diff --git a/security/nss/lib/pkcs7/manifest.mn b/security/nss/lib/pkcs7/manifest.mn new file mode 100644 index 0000000000..9ca2d4de5e --- /dev/null +++ b/security/nss/lib/pkcs7/manifest.mn @@ -0,0 +1,34 @@ +# +# 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/. + +CORE_DEPTH = ../.. + +EXPORTS = \ + secmime.h \ + secpkcs7.h \ + pkcs7t.h \ + $(NULL) + +PRIVATE_EXPORTS = \ + p7local.h \ + $(NULL) + +MODULE = nss + +CSRCS = \ + certread.c \ + p7common.c \ + p7create.c \ + p7decode.c \ + p7encode.c \ + p7local.c \ + secmime.c \ + $(NULL) + +LIBRARY_NAME = pkcs7 +SHARED_LIBRARY = $(NULL) + +# This part of the code, including all sub-dirs, can be optimized for size +export ALLOW_OPT_CODE_SIZE = 1 diff --git a/security/nss/lib/pkcs7/p7common.c b/security/nss/lib/pkcs7/p7common.c new file mode 100644 index 0000000000..8a6ac033e5 --- /dev/null +++ b/security/nss/lib/pkcs7/p7common.c @@ -0,0 +1,663 @@ +/* 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/. */ + +/* + * PKCS7 implementation -- the exported parts that are used whether + * creating or decoding. + */ + +#include "p7local.h" + +#include "cert.h" +#include "secitem.h" +#include "secoid.h" +#include "pk11func.h" + +/* + * Find out (saving pointer to lookup result for future reference) + * and return the inner content type. + */ +SECOidTag +SEC_PKCS7ContentType(SEC_PKCS7ContentInfo *cinfo) +{ + if (cinfo->contentTypeTag == NULL) + cinfo->contentTypeTag = SECOID_FindOID(&(cinfo->contentType)); + + if (cinfo->contentTypeTag == NULL) + return SEC_OID_UNKNOWN; + + return cinfo->contentTypeTag->offset; +} + +/* + * Destroy a PKCS7 contentInfo and all of its sub-pieces. + */ +void +SEC_PKCS7DestroyContentInfo(SEC_PKCS7ContentInfo *cinfo) +{ + SECOidTag kind; + CERTCertificate **certs; + CERTCertificateList **certlists; + SEC_PKCS7SignerInfo **signerinfos; + SEC_PKCS7RecipientInfo **recipientinfos; + + PORT_Assert(cinfo->refCount > 0); + if (cinfo->refCount <= 0) + return; + + cinfo->refCount--; + if (cinfo->refCount > 0) + return; + + certs = NULL; + certlists = NULL; + recipientinfos = NULL; + signerinfos = NULL; + + kind = SEC_PKCS7ContentType(cinfo); + switch (kind) { + case SEC_OID_PKCS7_ENVELOPED_DATA: { + SEC_PKCS7EnvelopedData *edp; + + edp = cinfo->content.envelopedData; + if (edp != NULL) { + recipientinfos = edp->recipientInfos; + } + } break; + case SEC_OID_PKCS7_SIGNED_DATA: { + SEC_PKCS7SignedData *sdp; + + sdp = cinfo->content.signedData; + if (sdp != NULL) { + certs = sdp->certs; + certlists = sdp->certLists; + signerinfos = sdp->signerInfos; + } + } break; + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: { + SEC_PKCS7SignedAndEnvelopedData *saedp; + + saedp = cinfo->content.signedAndEnvelopedData; + if (saedp != NULL) { + certs = saedp->certs; + certlists = saedp->certLists; + recipientinfos = saedp->recipientInfos; + signerinfos = saedp->signerInfos; + if (saedp->sigKey != NULL) + PK11_FreeSymKey(saedp->sigKey); + } + } break; + default: + /* XXX Anything else that needs to be "manually" freed/destroyed? */ + break; + } + + if (certs != NULL) { + CERTCertificate *cert; + + while ((cert = *certs++) != NULL) { + CERT_DestroyCertificate(cert); + } + } + + if (certlists != NULL) { + CERTCertificateList *certlist; + + while ((certlist = *certlists++) != NULL) { + CERT_DestroyCertificateList(certlist); + } + } + + if (recipientinfos != NULL) { + SEC_PKCS7RecipientInfo *ri; + + while ((ri = *recipientinfos++) != NULL) { + if (ri->cert != NULL) + CERT_DestroyCertificate(ri->cert); + } + } + + if (signerinfos != NULL) { + SEC_PKCS7SignerInfo *si; + + while ((si = *signerinfos++) != NULL) { + if (si->cert != NULL) + CERT_DestroyCertificate(si->cert); + if (si->certList != NULL) + CERT_DestroyCertificateList(si->certList); + } + } + + if (cinfo->poolp != NULL) { + PORT_FreeArena(cinfo->poolp, PR_FALSE); /* XXX clear it? */ + } +} + +/* + * Return a copy of the given contentInfo. The copy may be virtual + * or may be real -- either way, the result needs to be passed to + * SEC_PKCS7DestroyContentInfo later (as does the original). + */ +SEC_PKCS7ContentInfo * +SEC_PKCS7CopyContentInfo(SEC_PKCS7ContentInfo *cinfo) +{ + if (cinfo == NULL) + return NULL; + + PORT_Assert(cinfo->refCount > 0); + + if (cinfo->created) { + /* + * Want to do a real copy of these; otherwise subsequent + * changes made to either copy are likely to be a surprise. + * XXX I suspect that this will not actually be called for yet, + * which is why the assert, so to notice if it is... + */ + PORT_Assert(0); + /* + * XXX Create a new pool here, and copy everything from + * within. For cert stuff, need to call the appropriate + * copy functions, etc. + */ + } + + cinfo->refCount++; + return cinfo; +} + +/* + * Return a pointer to the actual content. In the case of those types + * which are encrypted, this returns the *plain* content. + * XXX Needs revisiting if/when we handle nested encrypted types. + */ +SECItem * +SEC_PKCS7GetContent(SEC_PKCS7ContentInfo *cinfo) +{ + SECOidTag kind; + + kind = SEC_PKCS7ContentType(cinfo); + switch (kind) { + case SEC_OID_PKCS7_DATA: + return cinfo->content.data; + case SEC_OID_PKCS7_DIGESTED_DATA: { + SEC_PKCS7DigestedData *digd; + + digd = cinfo->content.digestedData; + if (digd == NULL) + break; + return SEC_PKCS7GetContent(&(digd->contentInfo)); + } + case SEC_OID_PKCS7_ENCRYPTED_DATA: { + SEC_PKCS7EncryptedData *encd; + + encd = cinfo->content.encryptedData; + if (encd == NULL) + break; + return &(encd->encContentInfo.plainContent); + } + case SEC_OID_PKCS7_ENVELOPED_DATA: { + SEC_PKCS7EnvelopedData *envd; + + envd = cinfo->content.envelopedData; + if (envd == NULL) + break; + return &(envd->encContentInfo.plainContent); + } + case SEC_OID_PKCS7_SIGNED_DATA: { + SEC_PKCS7SignedData *sigd; + + sigd = cinfo->content.signedData; + if (sigd == NULL) + break; + return SEC_PKCS7GetContent(&(sigd->contentInfo)); + } + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: { + SEC_PKCS7SignedAndEnvelopedData *saed; + + saed = cinfo->content.signedAndEnvelopedData; + if (saed == NULL) + break; + return &(saed->encContentInfo.plainContent); + } + default: + PORT_Assert(0); + break; + } + + return NULL; +} + +/* + * XXX Fix the placement and formatting of the + * following routines (i.e. make them consistent with the rest of + * the pkcs7 code -- I think some/many belong in other files and + * they all need a formatting/style rehaul) + */ + +/* retrieve the algorithm identifier for encrypted data. + * the identifier returned is a copy of the algorithm identifier + * in the content info and needs to be freed after being used. + * + * cinfo is the content info for which to retrieve the + * encryption algorithm. + * + * if the content info is not encrypted data or an error + * occurs NULL is returned. + */ +SECAlgorithmID * +SEC_PKCS7GetEncryptionAlgorithm(SEC_PKCS7ContentInfo *cinfo) +{ + SECAlgorithmID *alg = 0; + switch (SEC_PKCS7ContentType(cinfo)) { + case SEC_OID_PKCS7_ENCRYPTED_DATA: + alg = &cinfo->content.encryptedData->encContentInfo.contentEncAlg; + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + alg = &cinfo->content.envelopedData->encContentInfo.contentEncAlg; + break; + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: + alg = &cinfo->content.signedAndEnvelopedData + ->encContentInfo.contentEncAlg; + break; + default: + alg = 0; + break; + } + + return alg; +} + +/* set the content of the content info. For data content infos, + * the data is set. For encrytped content infos, the plainContent + * is set, and is expected to be encrypted later. + * + * cinfo is the content info where the data will be set + * + * buf is a buffer of the data to set + * + * len is the length of the data being set. + * + * in the event of an error, SECFailure is returned. SECSuccess + * indicates the content was successfully set. + */ +SECStatus +SEC_PKCS7SetContent(SEC_PKCS7ContentInfo *cinfo, + const char *buf, + unsigned long len) +{ + SECOidTag cinfo_type; + SECStatus rv; + SECItem content; + SECOidData *contentTypeTag = NULL; + + content.type = siBuffer; + content.data = (unsigned char *)buf; + content.len = len; + + cinfo_type = SEC_PKCS7ContentType(cinfo); + + /* set inner content */ + switch (cinfo_type) { + case SEC_OID_PKCS7_SIGNED_DATA: + if (content.len > 0) { + /* we "leak" the old content here, but as it's all in the pool */ + /* it does not really matter */ + + /* create content item if necessary */ + if (cinfo->content.signedData->contentInfo.content.data == NULL) + cinfo->content.signedData->contentInfo.content.data = SECITEM_AllocItem(cinfo->poolp, NULL, 0); + rv = SECITEM_CopyItem(cinfo->poolp, + cinfo->content.signedData->contentInfo.content.data, + &content); + } else { + cinfo->content.signedData->contentInfo.content.data->data = NULL; + cinfo->content.signedData->contentInfo.content.data->len = 0; + rv = SECSuccess; + } + if (rv == SECFailure) + goto loser; + + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + /* XXX this forces the inner content type to be "data" */ + /* do we really want to override without asking or reason? */ + contentTypeTag = SECOID_FindOIDByTag(SEC_OID_PKCS7_DATA); + if (contentTypeTag == NULL) + goto loser; + rv = SECITEM_CopyItem(cinfo->poolp, + &(cinfo->content.encryptedData->encContentInfo.contentType), + &(contentTypeTag->oid)); + if (rv == SECFailure) + goto loser; + if (content.len > 0) { + rv = SECITEM_CopyItem(cinfo->poolp, + &(cinfo->content.encryptedData->encContentInfo.plainContent), + &content); + } else { + cinfo->content.encryptedData->encContentInfo.plainContent.data = NULL; + cinfo->content.encryptedData->encContentInfo.encContent.data = NULL; + cinfo->content.encryptedData->encContentInfo.plainContent.len = 0; + cinfo->content.encryptedData->encContentInfo.encContent.len = 0; + rv = SECSuccess; + } + if (rv == SECFailure) + goto loser; + break; + case SEC_OID_PKCS7_DATA: + cinfo->content.data = (SECItem *)PORT_ArenaZAlloc(cinfo->poolp, + sizeof(SECItem)); + if (cinfo->content.data == NULL) + goto loser; + if (content.len > 0) { + rv = SECITEM_CopyItem(cinfo->poolp, + cinfo->content.data, &content); + } else { + /* handle case with NULL content */ + rv = SECSuccess; + } + if (rv == SECFailure) + goto loser; + break; + default: + goto loser; + } + + return SECSuccess; + +loser: + + return SECFailure; +} + +/* the content of an encrypted data content info is encrypted. + * it is assumed that for encrypted data, that the data has already + * been set and is in the "plainContent" field of the content info. + * + * cinfo is the content info to encrypt + * + * key is the key with which to perform the encryption. if the + * algorithm is a password based encryption algorithm, the + * key is actually a password which will be processed per + * PKCS #5. + * + * in the event of an error, SECFailure is returned. SECSuccess + * indicates a success. + */ +SECStatus +SEC_PKCS7EncryptContents(PLArenaPool *poolp, + SEC_PKCS7ContentInfo *cinfo, + SECItem *key, + void *wincx) +{ + SECAlgorithmID *algid = NULL; + SECItem *src; + SECItem *dest; + SECItem *blocked_data = NULL; + void *mark; + void *cx; + PK11SymKey *eKey = NULL; + PK11SlotInfo *slot = NULL; + + CK_MECHANISM_TYPE cryptoMechType; + int bs; + SECStatus rv = SECFailure; + SECItem *c_param = NULL; + + if ((cinfo == NULL) || (key == NULL)) + return SECFailure; + + if (SEC_PKCS7ContentType(cinfo) != SEC_OID_PKCS7_ENCRYPTED_DATA) + return SECFailure; + + algid = SEC_PKCS7GetEncryptionAlgorithm(cinfo); + if (algid == NULL) + return SECFailure; + + if (poolp == NULL) + poolp = cinfo->poolp; + + mark = PORT_ArenaMark(poolp); + + src = &cinfo->content.encryptedData->encContentInfo.plainContent; + dest = &cinfo->content.encryptedData->encContentInfo.encContent; + dest->data = (unsigned char *)PORT_ArenaZAlloc(poolp, (src->len + 64)); + dest->len = (src->len + 64); + if (dest->data == NULL) { + rv = SECFailure; + goto loser; + } + + slot = PK11_GetInternalKeySlot(); + if (slot == NULL) { + rv = SECFailure; + goto loser; + } + + eKey = PK11_PBEKeyGen(slot, algid, key, PR_FALSE, wincx); + if (eKey == NULL) { + rv = SECFailure; + goto loser; + } + + cryptoMechType = PK11_GetPBECryptoMechanism(algid, &c_param, key); + if (cryptoMechType == CKM_INVALID_MECHANISM) { + rv = SECFailure; + goto loser; + } + + /* block according to PKCS 8 */ + bs = PK11_GetBlockSize(cryptoMechType, c_param); + rv = SECSuccess; + if (bs) { + char pad_char; + pad_char = (char)(bs - (src->len % bs)); + if (src->len % bs) { + rv = SECSuccess; + blocked_data = PK11_BlockData(src, bs); + if (blocked_data) { + PORT_Memset((blocked_data->data + blocked_data->len - (int)pad_char), + pad_char, (int)pad_char); + } else { + rv = SECFailure; + goto loser; + } + } else { + blocked_data = SECITEM_DupItem(src); + if (blocked_data) { + blocked_data->data = (unsigned char *)PORT_Realloc( + blocked_data->data, + blocked_data->len + bs); + if (blocked_data->data) { + blocked_data->len += bs; + PORT_Memset((blocked_data->data + src->len), (char)bs, bs); + } else { + rv = SECFailure; + goto loser; + } + } else { + rv = SECFailure; + goto loser; + } + } + } else { + blocked_data = SECITEM_DupItem(src); + if (!blocked_data) { + rv = SECFailure; + goto loser; + } + } + + cx = PK11_CreateContextBySymKey(cryptoMechType, CKA_ENCRYPT, + eKey, c_param); + if (cx == NULL) { + rv = SECFailure; + goto loser; + } + + rv = PK11_CipherOp((PK11Context *)cx, dest->data, (int *)(&dest->len), + (int)(src->len + 64), blocked_data->data, + (int)blocked_data->len); + PK11_DestroyContext((PK11Context *)cx, PR_TRUE); + +loser: + /* let success fall through */ + if (blocked_data != NULL) + SECITEM_ZfreeItem(blocked_data, PR_TRUE); + + if (rv == SECFailure) + PORT_ArenaRelease(poolp, mark); + else + PORT_ArenaUnmark(poolp, mark); + + if (eKey != NULL) + PK11_FreeSymKey(eKey); + + if (slot != NULL) + PK11_FreeSlot(slot); + + if (c_param != NULL) + SECITEM_ZfreeItem(c_param, PR_TRUE); + + return rv; +} + +/* the content of an encrypted data content info is decrypted. + * it is assumed that for encrypted data, that the data has already + * been set and is in the "encContent" field of the content info. + * + * cinfo is the content info to decrypt + * + * key is the key with which to perform the decryption. if the + * algorithm is a password based encryption algorithm, the + * key is actually a password which will be processed per + * PKCS #5. + * + * in the event of an error, SECFailure is returned. SECSuccess + * indicates a success. + */ +SECStatus +SEC_PKCS7DecryptContents(PLArenaPool *poolp, + SEC_PKCS7ContentInfo *cinfo, + SECItem *key, + void *wincx) +{ + SECAlgorithmID *algid = NULL; + SECStatus rv = SECFailure; + SECItem *dest, *src; + void *mark; + + PK11SymKey *eKey = NULL; + PK11SlotInfo *slot = NULL; + CK_MECHANISM_TYPE cryptoMechType; + void *cx; + SECItem *c_param = NULL; + int bs; + + if ((cinfo == NULL) || (key == NULL)) + return SECFailure; + + if (SEC_PKCS7ContentType(cinfo) != SEC_OID_PKCS7_ENCRYPTED_DATA) + return SECFailure; + + algid = SEC_PKCS7GetEncryptionAlgorithm(cinfo); + if (algid == NULL) + return SECFailure; + + if (poolp == NULL) + poolp = cinfo->poolp; + + mark = PORT_ArenaMark(poolp); + + src = &cinfo->content.encryptedData->encContentInfo.encContent; + dest = &cinfo->content.encryptedData->encContentInfo.plainContent; + dest->data = (unsigned char *)PORT_ArenaZAlloc(poolp, (src->len + 64)); + dest->len = (src->len + 64); + if (dest->data == NULL) { + rv = SECFailure; + goto loser; + } + + slot = PK11_GetInternalKeySlot(); + if (slot == NULL) { + rv = SECFailure; + goto loser; + } + + eKey = PK11_PBEKeyGen(slot, algid, key, PR_FALSE, wincx); + if (eKey == NULL) { + rv = SECFailure; + goto loser; + } + + cryptoMechType = PK11_GetPBECryptoMechanism(algid, &c_param, key); + if (cryptoMechType == CKM_INVALID_MECHANISM) { + rv = SECFailure; + goto loser; + } + + cx = PK11_CreateContextBySymKey(cryptoMechType, CKA_DECRYPT, + eKey, c_param); + if (cx == NULL) { + rv = SECFailure; + goto loser; + } + + rv = PK11_CipherOp((PK11Context *)cx, dest->data, (int *)(&dest->len), + (int)(src->len + 64), src->data, (int)src->len); + PK11_DestroyContext((PK11Context *)cx, PR_TRUE); + + bs = PK11_GetBlockSize(cryptoMechType, c_param); + if (bs) { + /* check for proper badding in block algorithms. this assumes + * RC2 cbc or a DES cbc variant. and the padding is thus defined + */ + if (((int)dest->data[dest->len - 1] <= bs) && + ((int)dest->data[dest->len - 1] > 0)) { + dest->len -= (int)dest->data[dest->len - 1]; + } else { + rv = SECFailure; + /* set an error ? */ + } + } + +loser: + /* let success fall through */ + if (rv == SECFailure) + PORT_ArenaRelease(poolp, mark); + else + PORT_ArenaUnmark(poolp, mark); + + if (eKey != NULL) + PK11_FreeSymKey(eKey); + + if (slot != NULL) + PK11_FreeSlot(slot); + + if (c_param != NULL) + SECITEM_ZfreeItem(c_param, PR_TRUE); + + return rv; +} + +SECItem ** +SEC_PKCS7GetCertificateList(SEC_PKCS7ContentInfo *cinfo) +{ + switch (SEC_PKCS7ContentType(cinfo)) { + case SEC_OID_PKCS7_SIGNED_DATA: + return cinfo->content.signedData->rawCerts; + break; + default: + return NULL; + break; + } +} + +int +SEC_PKCS7GetKeyLength(SEC_PKCS7ContentInfo *cinfo) +{ + if (cinfo->contentTypeTag->offset == SEC_OID_PKCS7_ENVELOPED_DATA) + return cinfo->content.envelopedData->encContentInfo.keysize; + else + return 0; +} diff --git a/security/nss/lib/pkcs7/p7create.c b/security/nss/lib/pkcs7/p7create.c new file mode 100644 index 0000000000..f193303862 --- /dev/null +++ b/security/nss/lib/pkcs7/p7create.c @@ -0,0 +1,1306 @@ +/* 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/. */ + +/* + * PKCS7 creation. + */ + +#include "p7local.h" + +#include "cert.h" +#include "secasn1.h" +#include "secitem.h" +#include "secoid.h" +#include "pk11func.h" +#include "prtime.h" +#include "secerr.h" +#include "secder.h" +#include "secpkcs5.h" + +const int NSS_PBE_DEFAULT_ITERATION_COUNT = /* used in p12e.c too */ +#ifdef DEBUG + 10000 +#else + 600000 +#endif + ; + +static SECStatus +sec_pkcs7_init_content_info(SEC_PKCS7ContentInfo *cinfo, PLArenaPool *poolp, + SECOidTag kind, PRBool detached) +{ + void *thing; + int version; + SECItem *versionp; + SECStatus rv; + + PORT_Assert(cinfo != NULL && poolp != NULL); + if (cinfo == NULL || poolp == NULL) + return SECFailure; + + cinfo->contentTypeTag = SECOID_FindOIDByTag(kind); + PORT_Assert(cinfo->contentTypeTag && cinfo->contentTypeTag->offset == kind); + + rv = SECITEM_CopyItem(poolp, &(cinfo->contentType), + &(cinfo->contentTypeTag->oid)); + if (rv != SECSuccess) + return rv; + + if (detached) + return SECSuccess; + + switch (kind) { + default: + case SEC_OID_PKCS7_DATA: + thing = PORT_ArenaZAlloc(poolp, sizeof(SECItem)); + cinfo->content.data = (SECItem *)thing; + versionp = NULL; + version = -1; + break; + case SEC_OID_PKCS7_DIGESTED_DATA: + thing = PORT_ArenaZAlloc(poolp, sizeof(SEC_PKCS7DigestedData)); + cinfo->content.digestedData = (SEC_PKCS7DigestedData *)thing; + versionp = &(cinfo->content.digestedData->version); + version = SEC_PKCS7_DIGESTED_DATA_VERSION; + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + thing = PORT_ArenaZAlloc(poolp, sizeof(SEC_PKCS7EncryptedData)); + cinfo->content.encryptedData = (SEC_PKCS7EncryptedData *)thing; + versionp = &(cinfo->content.encryptedData->version); + version = SEC_PKCS7_ENCRYPTED_DATA_VERSION; + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + thing = PORT_ArenaZAlloc(poolp, sizeof(SEC_PKCS7EnvelopedData)); + cinfo->content.envelopedData = + (SEC_PKCS7EnvelopedData *)thing; + versionp = &(cinfo->content.envelopedData->version); + version = SEC_PKCS7_ENVELOPED_DATA_VERSION; + break; + case SEC_OID_PKCS7_SIGNED_DATA: + thing = PORT_ArenaZAlloc(poolp, sizeof(SEC_PKCS7SignedData)); + cinfo->content.signedData = + (SEC_PKCS7SignedData *)thing; + versionp = &(cinfo->content.signedData->version); + version = SEC_PKCS7_SIGNED_DATA_VERSION; + break; + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: + thing = PORT_ArenaZAlloc(poolp, sizeof(SEC_PKCS7SignedAndEnvelopedData)); + cinfo->content.signedAndEnvelopedData = + (SEC_PKCS7SignedAndEnvelopedData *)thing; + versionp = &(cinfo->content.signedAndEnvelopedData->version); + version = SEC_PKCS7_SIGNED_AND_ENVELOPED_DATA_VERSION; + break; + } + + if (thing == NULL) + return SECFailure; + + if (versionp != NULL) { + SECItem *dummy; + + PORT_Assert(version >= 0); + dummy = SEC_ASN1EncodeInteger(poolp, versionp, version); + if (dummy == NULL) + return SECFailure; + PORT_Assert(dummy == versionp); + } + + return SECSuccess; +} + +static SEC_PKCS7ContentInfo * +sec_pkcs7_create_content_info(SECOidTag kind, PRBool detached, + SECKEYGetPasswordKey pwfn, void *pwfn_arg) +{ + SEC_PKCS7ContentInfo *cinfo; + PLArenaPool *poolp; + SECStatus rv; + + poolp = PORT_NewArena(1024); /* XXX what is right value? */ + if (poolp == NULL) + return NULL; + + cinfo = (SEC_PKCS7ContentInfo *)PORT_ArenaZAlloc(poolp, sizeof(*cinfo)); + if (cinfo == NULL) { + PORT_FreeArena(poolp, PR_FALSE); + return NULL; + } + + cinfo->poolp = poolp; + cinfo->pwfn = pwfn; + cinfo->pwfn_arg = pwfn_arg; + cinfo->created = PR_TRUE; + cinfo->refCount = 1; + + rv = sec_pkcs7_init_content_info(cinfo, poolp, kind, detached); + if (rv != SECSuccess) { + PORT_FreeArena(poolp, PR_FALSE); + return NULL; + } + + return cinfo; +} + +/* + * Add a signer to a PKCS7 thing, verifying the signature cert first. + * Any error returns SECFailure. + * + * XXX Right now this only adds the *first* signer. It fails if you try + * to add a second one -- this needs to be fixed. + */ +static SECStatus +sec_pkcs7_add_signer(SEC_PKCS7ContentInfo *cinfo, + CERTCertificate *cert, + SECCertUsage certusage, + CERTCertDBHandle *certdb, + SECOidTag digestalgtag, + SECItem *digestdata) +{ + SEC_PKCS7SignerInfo *signerinfo, **signerinfos, ***signerinfosp; + SECAlgorithmID *digestalg, **digestalgs, ***digestalgsp; + SECItem *digest, **digests, ***digestsp; + SECItem *dummy; + void *mark; + SECStatus rv; + SECOidTag kind; + + kind = SEC_PKCS7ContentType(cinfo); + switch (kind) { + case SEC_OID_PKCS7_SIGNED_DATA: { + SEC_PKCS7SignedData *sdp; + + sdp = cinfo->content.signedData; + digestalgsp = &(sdp->digestAlgorithms); + digestsp = &(sdp->digests); + signerinfosp = &(sdp->signerInfos); + } break; + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: { + SEC_PKCS7SignedAndEnvelopedData *saedp; + + saedp = cinfo->content.signedAndEnvelopedData; + digestalgsp = &(saedp->digestAlgorithms); + digestsp = &(saedp->digests); + signerinfosp = &(saedp->signerInfos); + } break; + default: + return SECFailure; /* XXX set an error? */ + } + + /* + * XXX I think that CERT_VerifyCert should do this if *it* is passed + * a NULL database. + */ + if (certdb == NULL) { + certdb = CERT_GetDefaultCertDB(); + if (certdb == NULL) + return SECFailure; /* XXX set an error? */ + } + + if (CERT_VerifyCert(certdb, cert, PR_TRUE, certusage, PR_Now(), + cinfo->pwfn_arg, NULL) != SECSuccess) { + /* XXX Did CERT_VerifyCert set an error? */ + return SECFailure; + } + + /* + * XXX This is the check that we do not already have a signer. + * This is not what we really want -- we want to allow this + * and *add* the new signer. + */ + PORT_Assert(*signerinfosp == NULL && *digestalgsp == NULL && *digestsp == NULL); + if (*signerinfosp != NULL || *digestalgsp != NULL || *digestsp != NULL) + return SECFailure; + + mark = PORT_ArenaMark(cinfo->poolp); + + signerinfo = (SEC_PKCS7SignerInfo *)PORT_ArenaZAlloc(cinfo->poolp, + sizeof(SEC_PKCS7SignerInfo)); + if (signerinfo == NULL) { + PORT_ArenaRelease(cinfo->poolp, mark); + return SECFailure; + } + + dummy = SEC_ASN1EncodeInteger(cinfo->poolp, &signerinfo->version, + SEC_PKCS7_SIGNER_INFO_VERSION); + if (dummy == NULL) { + PORT_ArenaRelease(cinfo->poolp, mark); + return SECFailure; + } + PORT_Assert(dummy == &signerinfo->version); + + signerinfo->cert = CERT_DupCertificate(cert); + if (signerinfo->cert == NULL) { + PORT_ArenaRelease(cinfo->poolp, mark); + return SECFailure; + } + + signerinfo->issuerAndSN = CERT_GetCertIssuerAndSN(cinfo->poolp, cert); + if (signerinfo->issuerAndSN == NULL) { + PORT_ArenaRelease(cinfo->poolp, mark); + return SECFailure; + } + + rv = SECOID_SetAlgorithmID(cinfo->poolp, &signerinfo->digestAlg, + digestalgtag, NULL); + if (rv != SECSuccess) { + PORT_ArenaRelease(cinfo->poolp, mark); + return SECFailure; + } + + /* + * Okay, now signerinfo is all set. We just need to put it and its + * companions (another copy of the digest algorithm, and the digest + * itself if given) into the main structure. + * + * XXX If we are handling more than one signer, the following code + * needs to look through the digest algorithms already specified + * and see if the same one is there already. If it is, it does not + * need to be added again. Also, if it is there *and* the digest + * is not null, then the digest given should match the digest already + * specified -- if not, that is an error. Finally, the new signerinfo + * should be *added* to the set already found. + */ + + signerinfos = (SEC_PKCS7SignerInfo **)PORT_ArenaAlloc(cinfo->poolp, + 2 * sizeof(SEC_PKCS7SignerInfo *)); + if (signerinfos == NULL) { + PORT_ArenaRelease(cinfo->poolp, mark); + return SECFailure; + } + signerinfos[0] = signerinfo; + signerinfos[1] = NULL; + + digestalg = PORT_ArenaZAlloc(cinfo->poolp, sizeof(SECAlgorithmID)); + digestalgs = PORT_ArenaAlloc(cinfo->poolp, 2 * sizeof(SECAlgorithmID *)); + if (digestalg == NULL || digestalgs == NULL) { + PORT_ArenaRelease(cinfo->poolp, mark); + return SECFailure; + } + rv = SECOID_SetAlgorithmID(cinfo->poolp, digestalg, digestalgtag, NULL); + if (rv != SECSuccess) { + PORT_ArenaRelease(cinfo->poolp, mark); + return SECFailure; + } + digestalgs[0] = digestalg; + digestalgs[1] = NULL; + + if (digestdata != NULL) { + digest = (SECItem *)PORT_ArenaAlloc(cinfo->poolp, sizeof(SECItem)); + digests = (SECItem **)PORT_ArenaAlloc(cinfo->poolp, + 2 * sizeof(SECItem *)); + if (digest == NULL || digests == NULL) { + PORT_ArenaRelease(cinfo->poolp, mark); + return SECFailure; + } + rv = SECITEM_CopyItem(cinfo->poolp, digest, digestdata); + if (rv != SECSuccess) { + PORT_ArenaRelease(cinfo->poolp, mark); + return SECFailure; + } + digests[0] = digest; + digests[1] = NULL; + } else { + digests = NULL; + } + + *signerinfosp = signerinfos; + *digestalgsp = digestalgs; + *digestsp = digests; + + PORT_ArenaUnmark(cinfo->poolp, mark); + return SECSuccess; +} + +/* + * Helper function for creating an empty signedData. + */ +static SEC_PKCS7ContentInfo * +sec_pkcs7_create_signed_data(SECKEYGetPasswordKey pwfn, void *pwfn_arg) +{ + SEC_PKCS7ContentInfo *cinfo; + SEC_PKCS7SignedData *sigd; + SECStatus rv; + + cinfo = sec_pkcs7_create_content_info(SEC_OID_PKCS7_SIGNED_DATA, PR_FALSE, + pwfn, pwfn_arg); + if (cinfo == NULL) + return NULL; + + sigd = cinfo->content.signedData; + PORT_Assert(sigd != NULL); + + /* + * XXX Might we want to allow content types other than data? + * If so, via what interface? + */ + rv = sec_pkcs7_init_content_info(&(sigd->contentInfo), cinfo->poolp, + SEC_OID_PKCS7_DATA, PR_TRUE); + if (rv != SECSuccess) { + SEC_PKCS7DestroyContentInfo(cinfo); + return NULL; + } + + return cinfo; +} + +/* + * Start a PKCS7 signing context. + * + * "cert" is the cert that will be used to sign the data. It will be + * checked for validity. + * + * "certusage" describes the signing usage (e.g. certUsageEmailSigner) + * XXX Maybe SECCertUsage should be split so that our caller just says + * "email" and *we* add the "signing" part -- otherwise our caller + * could be lying about the usage; we do not want to allow encryption + * certs for signing or vice versa. + * + * "certdb" is the cert database to use for verifying the cert. + * It can be NULL if a default database is available (like in the client). + * + * "digestalg" names the digest algorithm (e.g. SEC_OID_SHA1). + * + * "digest" is the actual digest of the data. It must be provided in + * the case of detached data or NULL if the content will be included. + * + * The return value can be passed to functions which add things to + * it like attributes, then eventually to SEC_PKCS7Encode() or to + * SEC_PKCS7EncoderStart() to create the encoded data, and finally to + * SEC_PKCS7DestroyContentInfo(). + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ +SEC_PKCS7ContentInfo * +SEC_PKCS7CreateSignedData(CERTCertificate *cert, + SECCertUsage certusage, + CERTCertDBHandle *certdb, + SECOidTag digestalg, + SECItem *digest, + SECKEYGetPasswordKey pwfn, void *pwfn_arg) +{ + SEC_PKCS7ContentInfo *cinfo; + SECStatus rv; + + cinfo = sec_pkcs7_create_signed_data(pwfn, pwfn_arg); + if (cinfo == NULL) + return NULL; + + rv = sec_pkcs7_add_signer(cinfo, cert, certusage, certdb, + digestalg, digest); + if (rv != SECSuccess) { + SEC_PKCS7DestroyContentInfo(cinfo); + return NULL; + } + + return cinfo; +} + +static SEC_PKCS7Attribute * +sec_pkcs7_create_attribute(PLArenaPool *poolp, SECOidTag oidtag, + SECItem *value, PRBool encoded) +{ + SEC_PKCS7Attribute *attr; + SECItem **values; + void *mark; + + PORT_Assert(poolp != NULL); + mark = PORT_ArenaMark(poolp); + + attr = (SEC_PKCS7Attribute *)PORT_ArenaAlloc(poolp, + sizeof(SEC_PKCS7Attribute)); + if (attr == NULL) + goto loser; + + attr->typeTag = SECOID_FindOIDByTag(oidtag); + if (attr->typeTag == NULL) + goto loser; + + if (SECITEM_CopyItem(poolp, &(attr->type), + &(attr->typeTag->oid)) != SECSuccess) + goto loser; + + values = (SECItem **)PORT_ArenaAlloc(poolp, 2 * sizeof(SECItem *)); + if (values == NULL) + goto loser; + + if (value != NULL) { + SECItem *copy; + + copy = (SECItem *)PORT_ArenaAlloc(poolp, sizeof(SECItem)); + if (copy == NULL) + goto loser; + + if (SECITEM_CopyItem(poolp, copy, value) != SECSuccess) + goto loser; + + value = copy; + } + + values[0] = value; + values[1] = NULL; + attr->values = values; + attr->encoded = encoded; + + PORT_ArenaUnmark(poolp, mark); + return attr; + +loser: + PORT_Assert(mark != NULL); + PORT_ArenaRelease(poolp, mark); + return NULL; +} + +static SECStatus +sec_pkcs7_add_attribute(SEC_PKCS7ContentInfo *cinfo, + SEC_PKCS7Attribute ***attrsp, + SEC_PKCS7Attribute *attr) +{ + SEC_PKCS7Attribute **attrs; + SECItem *ct_value; + void *mark; + + PORT_Assert(SEC_PKCS7ContentType(cinfo) == SEC_OID_PKCS7_SIGNED_DATA); + if (SEC_PKCS7ContentType(cinfo) != SEC_OID_PKCS7_SIGNED_DATA) + return SECFailure; + + attrs = *attrsp; + if (attrs != NULL) { + int count; + + /* + * We already have some attributes, and just need to add this + * new one. + */ + + /* + * We should already have the *required* attributes, which were + * created/added at the same time the first attribute was added. + */ + PORT_Assert(sec_PKCS7FindAttribute(attrs, + SEC_OID_PKCS9_CONTENT_TYPE, + PR_FALSE) != NULL); + PORT_Assert(sec_PKCS7FindAttribute(attrs, + SEC_OID_PKCS9_MESSAGE_DIGEST, + PR_FALSE) != NULL); + + for (count = 0; attrs[count] != NULL; count++) + ; + attrs = (SEC_PKCS7Attribute **)PORT_ArenaGrow(cinfo->poolp, attrs, + (count + 1) * sizeof(SEC_PKCS7Attribute *), + (count + 2) * sizeof(SEC_PKCS7Attribute *)); + if (attrs == NULL) + return SECFailure; + + attrs[count] = attr; + attrs[count + 1] = NULL; + *attrsp = attrs; + + return SECSuccess; + } + + /* + * This is the first time an attribute is going in. + * We need to create and add the required attributes, and then + * we will also add in the one our caller gave us. + */ + + /* + * There are 2 required attributes, plus the one our caller wants + * to add, plus we always end with a NULL one. Thus, four slots. + */ + attrs = (SEC_PKCS7Attribute **)PORT_ArenaAlloc(cinfo->poolp, + 4 * sizeof(SEC_PKCS7Attribute *)); + if (attrs == NULL) + return SECFailure; + + mark = PORT_ArenaMark(cinfo->poolp); + + /* + * First required attribute is the content type of the data + * being signed. + */ + ct_value = &(cinfo->content.signedData->contentInfo.contentType); + attrs[0] = sec_pkcs7_create_attribute(cinfo->poolp, + SEC_OID_PKCS9_CONTENT_TYPE, + ct_value, PR_FALSE); + /* + * Second required attribute is the message digest of the data + * being signed; we leave the value NULL for now (just create + * the place for it to go), and the encoder will fill it in later. + */ + attrs[1] = sec_pkcs7_create_attribute(cinfo->poolp, + SEC_OID_PKCS9_MESSAGE_DIGEST, + NULL, PR_FALSE); + if (attrs[0] == NULL || attrs[1] == NULL) { + PORT_ArenaRelease(cinfo->poolp, mark); + return SECFailure; + } + + attrs[2] = attr; + attrs[3] = NULL; + *attrsp = attrs; + + PORT_ArenaUnmark(cinfo->poolp, mark); + return SECSuccess; +} + +/* + * Add the signing time to the authenticated (i.e. signed) attributes + * of "cinfo". This is expected to be included in outgoing signed + * messages for email (S/MIME) but is likely useful in other situations. + * + * This should only be added once; a second call will either do + * nothing or replace an old signing time with a newer one. + * + * XXX This will probably just shove the current time into "cinfo" + * but it will not actually get signed until the entire item is + * processed for encoding. Is this (expected to be small) delay okay? + * + * "cinfo" should be of type signedData (the only kind of pkcs7 data + * that is allowed authenticated attributes); SECFailure will be returned + * if it is not. + */ +SECStatus +SEC_PKCS7AddSigningTime(SEC_PKCS7ContentInfo *cinfo) +{ + SEC_PKCS7SignerInfo **signerinfos; + SEC_PKCS7Attribute *attr; + SECItem stime; + SECStatus rv; + int si; + + PORT_Assert(SEC_PKCS7ContentType(cinfo) == SEC_OID_PKCS7_SIGNED_DATA); + if (SEC_PKCS7ContentType(cinfo) != SEC_OID_PKCS7_SIGNED_DATA) + return SECFailure; + + signerinfos = cinfo->content.signedData->signerInfos; + + /* There has to be a signer, or it makes no sense. */ + if (signerinfos == NULL || signerinfos[0] == NULL) + return SECFailure; + + rv = DER_EncodeTimeChoice(NULL, &stime, PR_Now()); + if (rv != SECSuccess) + return rv; + + attr = sec_pkcs7_create_attribute(cinfo->poolp, + SEC_OID_PKCS9_SIGNING_TIME, + &stime, PR_FALSE); + SECITEM_FreeItem(&stime, PR_FALSE); + + if (attr == NULL) + return SECFailure; + + rv = SECSuccess; + for (si = 0; signerinfos[si] != NULL; si++) { + SEC_PKCS7Attribute *oattr; + + oattr = sec_PKCS7FindAttribute(signerinfos[si]->authAttr, + SEC_OID_PKCS9_SIGNING_TIME, PR_FALSE); + PORT_Assert(oattr == NULL); + if (oattr != NULL) + continue; /* XXX or would it be better to replace it? */ + + rv = sec_pkcs7_add_attribute(cinfo, &(signerinfos[si]->authAttr), + attr); + if (rv != SECSuccess) + break; /* could try to continue, but may as well give up now */ + } + + return rv; +} + +/* + * Add the specified attribute to the authenticated (i.e. signed) attributes + * of "cinfo" -- "oidtag" describes the attribute and "value" is the + * value to be associated with it. NOTE! "value" must already be encoded; + * no interpretation of "oidtag" is done. Also, it is assumed that this + * signedData has only one signer -- if we ever need to add attributes + * when there is more than one signature, we need a way to specify *which* + * signature should get the attribute. + * + * XXX Technically, a signed attribute can have multiple values; if/when + * we ever need to support an attribute which takes multiple values, we + * either need to change this interface or create an AddSignedAttributeValue + * which can be called subsequently, and would then append a value. + * + * "cinfo" should be of type signedData (the only kind of pkcs7 data + * that is allowed authenticated attributes); SECFailure will be returned + * if it is not. + */ +SECStatus +SEC_PKCS7AddSignedAttribute(SEC_PKCS7ContentInfo *cinfo, + SECOidTag oidtag, + SECItem *value) +{ + SEC_PKCS7SignerInfo **signerinfos; + SEC_PKCS7Attribute *attr; + + PORT_Assert(SEC_PKCS7ContentType(cinfo) == SEC_OID_PKCS7_SIGNED_DATA); + if (SEC_PKCS7ContentType(cinfo) != SEC_OID_PKCS7_SIGNED_DATA) + return SECFailure; + + signerinfos = cinfo->content.signedData->signerInfos; + + /* + * No signature or more than one means no deal. + */ + if (signerinfos == NULL || signerinfos[0] == NULL || signerinfos[1] != NULL) + return SECFailure; + + attr = sec_pkcs7_create_attribute(cinfo->poolp, oidtag, value, PR_TRUE); + if (attr == NULL) + return SECFailure; + + return sec_pkcs7_add_attribute(cinfo, &(signerinfos[0]->authAttr), attr); +} + +/* + * Mark that the signer certificates and their issuing chain should + * be included in the encoded data. This is expected to be used + * in outgoing signed messages for email (S/MIME). + * + * "certdb" is the cert database to use for finding the chain. + * It can be NULL, meaning use the default database. + * + * "cinfo" should be of type signedData or signedAndEnvelopedData; + * SECFailure will be returned if it is not. + */ +SECStatus +SEC_PKCS7IncludeCertChain(SEC_PKCS7ContentInfo *cinfo, + CERTCertDBHandle *certdb) +{ + SECOidTag kind; + SEC_PKCS7SignerInfo *signerinfo, **signerinfos; + + kind = SEC_PKCS7ContentType(cinfo); + switch (kind) { + case SEC_OID_PKCS7_SIGNED_DATA: + signerinfos = cinfo->content.signedData->signerInfos; + break; + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: + signerinfos = cinfo->content.signedAndEnvelopedData->signerInfos; + break; + default: + return SECFailure; /* XXX set an error? */ + } + + if (signerinfos == NULL) /* no signer, no certs? */ + return SECFailure; /* XXX set an error? */ + + if (certdb == NULL) { + certdb = CERT_GetDefaultCertDB(); + if (certdb == NULL) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + return SECFailure; + } + } + + /* XXX Should it be an error if we find no signerinfo or no certs? */ + while ((signerinfo = *signerinfos++) != NULL) { + if (signerinfo->cert != NULL) + /* get the cert chain. don't send the root to avoid contamination + * of old clients with a new root that they don't trust + */ + signerinfo->certList = CERT_CertChainFromCert(signerinfo->cert, + certUsageEmailSigner, + PR_FALSE); + } + + return SECSuccess; +} + +/* + * Helper function to add a certificate chain for inclusion in the + * bag of certificates in a signedData. + */ +static SECStatus +sec_pkcs7_add_cert_chain(SEC_PKCS7ContentInfo *cinfo, + CERTCertificate *cert, + CERTCertDBHandle *certdb) +{ + SECOidTag kind; + CERTCertificateList *certlist, **certlists, ***certlistsp; + int count; + + kind = SEC_PKCS7ContentType(cinfo); + switch (kind) { + case SEC_OID_PKCS7_SIGNED_DATA: { + SEC_PKCS7SignedData *sdp; + + sdp = cinfo->content.signedData; + certlistsp = &(sdp->certLists); + } break; + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: { + SEC_PKCS7SignedAndEnvelopedData *saedp; + + saedp = cinfo->content.signedAndEnvelopedData; + certlistsp = &(saedp->certLists); + } break; + default: + return SECFailure; /* XXX set an error? */ + } + + if (certdb == NULL) { + certdb = CERT_GetDefaultCertDB(); + if (certdb == NULL) { + PORT_SetError(SEC_ERROR_BAD_DATABASE); + return SECFailure; + } + } + + certlist = CERT_CertChainFromCert(cert, certUsageEmailSigner, PR_FALSE); + if (certlist == NULL) + return SECFailure; + + certlists = *certlistsp; + if (certlists == NULL) { + count = 0; + certlists = (CERTCertificateList **)PORT_ArenaAlloc(cinfo->poolp, + 2 * sizeof(CERTCertificateList *)); + } else { + for (count = 0; certlists[count] != NULL; count++) + ; + PORT_Assert(count); /* should be at least one already */ + certlists = (CERTCertificateList **)PORT_ArenaGrow(cinfo->poolp, + certlists, + (count + 1) * sizeof(CERTCertificateList *), + (count + 2) * sizeof(CERTCertificateList *)); + } + + if (certlists == NULL) { + CERT_DestroyCertificateList(certlist); + return SECFailure; + } + + certlists[count] = certlist; + certlists[count + 1] = NULL; + + *certlistsp = certlists; + + return SECSuccess; +} + +/* + * Helper function to add a certificate for inclusion in the bag of + * certificates in a signedData. + */ +static SECStatus +sec_pkcs7_add_certificate(SEC_PKCS7ContentInfo *cinfo, + CERTCertificate *cert) +{ + SECOidTag kind; + CERTCertificate **certs, ***certsp; + int count; + + kind = SEC_PKCS7ContentType(cinfo); + switch (kind) { + case SEC_OID_PKCS7_SIGNED_DATA: { + SEC_PKCS7SignedData *sdp; + + sdp = cinfo->content.signedData; + certsp = &(sdp->certs); + } break; + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: { + SEC_PKCS7SignedAndEnvelopedData *saedp; + + saedp = cinfo->content.signedAndEnvelopedData; + certsp = &(saedp->certs); + } break; + default: + return SECFailure; /* XXX set an error? */ + } + + cert = CERT_DupCertificate(cert); + if (cert == NULL) + return SECFailure; + + certs = *certsp; + if (certs == NULL) { + count = 0; + certs = (CERTCertificate **)PORT_ArenaAlloc(cinfo->poolp, + 2 * sizeof(CERTCertificate *)); + } else { + for (count = 0; certs[count] != NULL; count++) + ; + PORT_Assert(count); /* should be at least one already */ + certs = (CERTCertificate **)PORT_ArenaGrow(cinfo->poolp, certs, + (count + 1) * sizeof(CERTCertificate *), + (count + 2) * sizeof(CERTCertificate *)); + } + + if (certs == NULL) { + CERT_DestroyCertificate(cert); + return SECFailure; + } + + certs[count] = cert; + certs[count + 1] = NULL; + + *certsp = certs; + + return SECSuccess; +} + +/* + * Create a PKCS7 certs-only container. + * + * "cert" is the (first) cert that will be included. + * + * "include_chain" specifies whether the entire chain for "cert" should + * be included. + * + * "certdb" is the cert database to use for finding the chain. + * It can be NULL in when "include_chain" is false, or when meaning + * use the default database. + * + * More certs and chains can be added via AddCertificate and AddCertChain. + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ +SEC_PKCS7ContentInfo * +SEC_PKCS7CreateCertsOnly(CERTCertificate *cert, + PRBool include_chain, + CERTCertDBHandle *certdb) +{ + SEC_PKCS7ContentInfo *cinfo; + SECStatus rv; + + cinfo = sec_pkcs7_create_signed_data(NULL, NULL); + if (cinfo == NULL) + return NULL; + + if (include_chain) + rv = sec_pkcs7_add_cert_chain(cinfo, cert, certdb); + else + rv = sec_pkcs7_add_certificate(cinfo, cert); + + if (rv != SECSuccess) { + SEC_PKCS7DestroyContentInfo(cinfo); + return NULL; + } + + return cinfo; +} + +/* + * Add "cert" and its entire chain to the set of certs included in "cinfo". + * + * "certdb" is the cert database to use for finding the chain. + * It can be NULL, meaning use the default database. + * + * "cinfo" should be of type signedData or signedAndEnvelopedData; + * SECFailure will be returned if it is not. + */ +SECStatus +SEC_PKCS7AddCertChain(SEC_PKCS7ContentInfo *cinfo, + CERTCertificate *cert, + CERTCertDBHandle *certdb) +{ + SECOidTag kind; + + kind = SEC_PKCS7ContentType(cinfo); + if (kind != SEC_OID_PKCS7_SIGNED_DATA && kind != SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA) + return SECFailure; /* XXX set an error? */ + + return sec_pkcs7_add_cert_chain(cinfo, cert, certdb); +} + +/* + * Add "cert" to the set of certs included in "cinfo". + * + * "cinfo" should be of type signedData or signedAndEnvelopedData; + * SECFailure will be returned if it is not. + */ +SECStatus +SEC_PKCS7AddCertificate(SEC_PKCS7ContentInfo *cinfo, CERTCertificate *cert) +{ + SECOidTag kind; + + kind = SEC_PKCS7ContentType(cinfo); + if (kind != SEC_OID_PKCS7_SIGNED_DATA && kind != SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA) + return SECFailure; /* XXX set an error? */ + + return sec_pkcs7_add_certificate(cinfo, cert); +} + +static SECStatus +sec_pkcs7_init_encrypted_content_info(SEC_PKCS7EncryptedContentInfo *enccinfo, + PLArenaPool *poolp, + SECOidTag kind, PRBool detached, + SECOidTag encalg, int keysize) +{ + SECStatus rv; + + PORT_Assert(enccinfo != NULL && poolp != NULL); + if (enccinfo == NULL || poolp == NULL) + return SECFailure; + + /* + * XXX Some day we may want to allow for other kinds. That needs + * more work and modifications to the creation interface, etc. + * For now, allow but notice callers who pass in other kinds. + * They are responsible for creating the inner type and encoding, + * if it is other than DATA. + */ + PORT_Assert(kind == SEC_OID_PKCS7_DATA); + + enccinfo->contentTypeTag = SECOID_FindOIDByTag(kind); + PORT_Assert(enccinfo->contentTypeTag && enccinfo->contentTypeTag->offset == kind); + + rv = SECITEM_CopyItem(poolp, &(enccinfo->contentType), + &(enccinfo->contentTypeTag->oid)); + if (rv != SECSuccess) + return rv; + + /* Save keysize and algorithm for later. */ + enccinfo->keysize = keysize; + enccinfo->encalg = encalg; + + return SECSuccess; +} + +/* + * Add a recipient to a PKCS7 thing, verifying their cert first. + * Any error returns SECFailure. + */ +static SECStatus +sec_pkcs7_add_recipient(SEC_PKCS7ContentInfo *cinfo, + CERTCertificate *cert, + SECCertUsage certusage, + CERTCertDBHandle *certdb) +{ + SECOidTag kind; + SEC_PKCS7RecipientInfo *recipientinfo, **recipientinfos, ***recipientinfosp; + SECItem *dummy; + void *mark; + int count; + + kind = SEC_PKCS7ContentType(cinfo); + switch (kind) { + case SEC_OID_PKCS7_ENVELOPED_DATA: { + SEC_PKCS7EnvelopedData *edp; + + edp = cinfo->content.envelopedData; + recipientinfosp = &(edp->recipientInfos); + } break; + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: { + SEC_PKCS7SignedAndEnvelopedData *saedp; + + saedp = cinfo->content.signedAndEnvelopedData; + recipientinfosp = &(saedp->recipientInfos); + } break; + default: + return SECFailure; /* XXX set an error? */ + } + + /* + * XXX I think that CERT_VerifyCert should do this if *it* is passed + * a NULL database. + */ + if (certdb == NULL) { + certdb = CERT_GetDefaultCertDB(); + if (certdb == NULL) + return SECFailure; /* XXX set an error? */ + } + + if (CERT_VerifyCert(certdb, cert, PR_TRUE, certusage, PR_Now(), + cinfo->pwfn_arg, NULL) != SECSuccess) { + /* XXX Did CERT_VerifyCert set an error? */ + return SECFailure; + } + + mark = PORT_ArenaMark(cinfo->poolp); + + recipientinfo = (SEC_PKCS7RecipientInfo *)PORT_ArenaZAlloc(cinfo->poolp, + sizeof(SEC_PKCS7RecipientInfo)); + if (recipientinfo == NULL) { + PORT_ArenaRelease(cinfo->poolp, mark); + return SECFailure; + } + + dummy = SEC_ASN1EncodeInteger(cinfo->poolp, &recipientinfo->version, + SEC_PKCS7_RECIPIENT_INFO_VERSION); + if (dummy == NULL) { + PORT_ArenaRelease(cinfo->poolp, mark); + return SECFailure; + } + PORT_Assert(dummy == &recipientinfo->version); + + recipientinfo->cert = CERT_DupCertificate(cert); + if (recipientinfo->cert == NULL) { + PORT_ArenaRelease(cinfo->poolp, mark); + return SECFailure; + } + + recipientinfo->issuerAndSN = CERT_GetCertIssuerAndSN(cinfo->poolp, cert); + if (recipientinfo->issuerAndSN == NULL) { + PORT_ArenaRelease(cinfo->poolp, mark); + return SECFailure; + } + + /* + * Okay, now recipientinfo is all set. We just need to put it into + * the main structure. + * + * If this is the first recipient, allocate a new recipientinfos array; + * otherwise, reallocate the array, making room for the new entry. + */ + recipientinfos = *recipientinfosp; + if (recipientinfos == NULL) { + count = 0; + recipientinfos = (SEC_PKCS7RecipientInfo **)PORT_ArenaAlloc( + cinfo->poolp, + 2 * sizeof(SEC_PKCS7RecipientInfo *)); + } else { + for (count = 0; recipientinfos[count] != NULL; count++) + ; + PORT_Assert(count); /* should be at least one already */ + recipientinfos = (SEC_PKCS7RecipientInfo **)PORT_ArenaGrow( + cinfo->poolp, recipientinfos, + (count + 1) * sizeof(SEC_PKCS7RecipientInfo *), + (count + 2) * sizeof(SEC_PKCS7RecipientInfo *)); + } + + if (recipientinfos == NULL) { + PORT_ArenaRelease(cinfo->poolp, mark); + return SECFailure; + } + + recipientinfos[count] = recipientinfo; + recipientinfos[count + 1] = NULL; + + *recipientinfosp = recipientinfos; + + PORT_ArenaUnmark(cinfo->poolp, mark); + return SECSuccess; +} + +/* + * Start a PKCS7 enveloping context. + * + * "cert" is the cert for the recipient. It will be checked for validity. + * + * "certusage" describes the encryption usage (e.g. certUsageEmailRecipient) + * XXX Maybe SECCertUsage should be split so that our caller just says + * "email" and *we* add the "recipient" part -- otherwise our caller + * could be lying about the usage; we do not want to allow encryption + * certs for signing or vice versa. + * + * "certdb" is the cert database to use for verifying the cert. + * It can be NULL if a default database is available (like in the client). + * + * "encalg" specifies the bulk encryption algorithm to use (e.g. SEC_OID_RC2). + * + * "keysize" specifies the bulk encryption key size, in bits. + * + * The return value can be passed to functions which add things to + * it like more recipients, then eventually to SEC_PKCS7Encode() or to + * SEC_PKCS7EncoderStart() to create the encoded data, and finally to + * SEC_PKCS7DestroyContentInfo(). + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ +extern SEC_PKCS7ContentInfo * +SEC_PKCS7CreateEnvelopedData(CERTCertificate *cert, + SECCertUsage certusage, + CERTCertDBHandle *certdb, + SECOidTag encalg, + int keysize, + SECKEYGetPasswordKey pwfn, void *pwfn_arg) +{ + SEC_PKCS7ContentInfo *cinfo; + SEC_PKCS7EnvelopedData *envd; + SECStatus rv; + + cinfo = sec_pkcs7_create_content_info(SEC_OID_PKCS7_ENVELOPED_DATA, + PR_FALSE, pwfn, pwfn_arg); + if (cinfo == NULL) + return NULL; + + rv = sec_pkcs7_add_recipient(cinfo, cert, certusage, certdb); + if (rv != SECSuccess) { + SEC_PKCS7DestroyContentInfo(cinfo); + return NULL; + } + + envd = cinfo->content.envelopedData; + PORT_Assert(envd != NULL); + + /* + * XXX Might we want to allow content types other than data? + * If so, via what interface? + */ + rv = sec_pkcs7_init_encrypted_content_info(&(envd->encContentInfo), + cinfo->poolp, + SEC_OID_PKCS7_DATA, PR_FALSE, + encalg, keysize); + if (rv != SECSuccess) { + SEC_PKCS7DestroyContentInfo(cinfo); + return NULL; + } + + /* XXX Anything more to do here? */ + + return cinfo; +} + +/* + * Add another recipient to an encrypted message. + * + * "cinfo" should be of type envelopedData or signedAndEnvelopedData; + * SECFailure will be returned if it is not. + * + * "cert" is the cert for the recipient. It will be checked for validity. + * + * "certusage" describes the encryption usage (e.g. certUsageEmailRecipient) + * XXX Maybe SECCertUsage should be split so that our caller just says + * "email" and *we* add the "recipient" part -- otherwise our caller + * could be lying about the usage; we do not want to allow encryption + * certs for signing or vice versa. + * + * "certdb" is the cert database to use for verifying the cert. + * It can be NULL if a default database is available (like in the client). + */ +SECStatus +SEC_PKCS7AddRecipient(SEC_PKCS7ContentInfo *cinfo, + CERTCertificate *cert, + SECCertUsage certusage, + CERTCertDBHandle *certdb) +{ + return sec_pkcs7_add_recipient(cinfo, cert, certusage, certdb); +} + +/* + * Create an empty PKCS7 data content info. + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ +SEC_PKCS7ContentInfo * +SEC_PKCS7CreateData(void) +{ + return sec_pkcs7_create_content_info(SEC_OID_PKCS7_DATA, PR_FALSE, + NULL, NULL); +} + +/* + * Create an empty PKCS7 encrypted content info. + * + * "algorithm" specifies the bulk encryption algorithm to use. + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ +SEC_PKCS7ContentInfo * +SEC_PKCS7CreateEncryptedData(SECOidTag algorithm, int keysize, + SECKEYGetPasswordKey pwfn, void *pwfn_arg) +{ + SEC_PKCS7ContentInfo *cinfo; + SECAlgorithmID *algid; + SEC_PKCS7EncryptedData *enc_data; + SECStatus rv; + + cinfo = sec_pkcs7_create_content_info(SEC_OID_PKCS7_ENCRYPTED_DATA, + PR_FALSE, pwfn, pwfn_arg); + if (cinfo == NULL) + return NULL; + + enc_data = cinfo->content.encryptedData; + algid = &(enc_data->encContentInfo.contentEncAlg); + + if (!SEC_PKCS5IsAlgorithmPBEAlgTag(algorithm)) { + rv = SECOID_SetAlgorithmID(cinfo->poolp, algid, algorithm, NULL); + } else { + /* Assume password-based-encryption. + * Note: we can't generate pkcs5v2 from this interface. + * PK11_CreateBPEAlgorithmID generates pkcs5v2 by accepting + * non-PBE oids and assuming that they are pkcs5v2 oids, but + * NSS_CMSEncryptedData_Create accepts non-PBE oids as regular + * CMS encrypted data, so we can't tell SEC_PKCS7CreateEncryptedtedData + * to create pkcs5v2 PBEs */ + SECAlgorithmID *pbe_algid; + pbe_algid = PK11_CreatePBEAlgorithmID(algorithm, + NSS_PBE_DEFAULT_ITERATION_COUNT, + NULL); + if (pbe_algid == NULL) { + rv = SECFailure; + } else { + rv = SECOID_CopyAlgorithmID(cinfo->poolp, algid, pbe_algid); + SECOID_DestroyAlgorithmID(pbe_algid, PR_TRUE); + } + } + + if (rv != SECSuccess) { + SEC_PKCS7DestroyContentInfo(cinfo); + return NULL; + } + + rv = sec_pkcs7_init_encrypted_content_info(&(enc_data->encContentInfo), + cinfo->poolp, + SEC_OID_PKCS7_DATA, PR_FALSE, + algorithm, keysize); + if (rv != SECSuccess) { + SEC_PKCS7DestroyContentInfo(cinfo); + return NULL; + } + + return cinfo; +} + +SEC_PKCS7ContentInfo * +SEC_PKCS7CreateEncryptedDataWithPBEV2(SECOidTag pbe_algorithm, + SECOidTag cipher_algorithm, + SECOidTag prf_algorithm, + int keysize, + SECKEYGetPasswordKey pwfn, void *pwfn_arg) +{ + SEC_PKCS7ContentInfo *cinfo; + SECAlgorithmID *algid; + SEC_PKCS7EncryptedData *enc_data; + SECStatus rv; + + PORT_Assert(SEC_PKCS5IsAlgorithmPBEAlgTag(pbe_algorithm)); + + cinfo = sec_pkcs7_create_content_info(SEC_OID_PKCS7_ENCRYPTED_DATA, + PR_FALSE, pwfn, pwfn_arg); + if (cinfo == NULL) + return NULL; + + enc_data = cinfo->content.encryptedData; + algid = &(enc_data->encContentInfo.contentEncAlg); + + SECAlgorithmID *pbe_algid; + pbe_algid = PK11_CreatePBEV2AlgorithmID(pbe_algorithm, + cipher_algorithm, + prf_algorithm, + keysize, + NSS_PBE_DEFAULT_ITERATION_COUNT, + NULL); + if (pbe_algid == NULL) { + rv = SECFailure; + } else { + rv = SECOID_CopyAlgorithmID(cinfo->poolp, algid, pbe_algid); + SECOID_DestroyAlgorithmID(pbe_algid, PR_TRUE); + } + + if (rv != SECSuccess) { + SEC_PKCS7DestroyContentInfo(cinfo); + return NULL; + } + + rv = sec_pkcs7_init_encrypted_content_info(&(enc_data->encContentInfo), + cinfo->poolp, + SEC_OID_PKCS7_DATA, PR_FALSE, + cipher_algorithm, keysize); + if (rv != SECSuccess) { + SEC_PKCS7DestroyContentInfo(cinfo); + return NULL; + } + + return cinfo; +} diff --git a/security/nss/lib/pkcs7/p7decode.c b/security/nss/lib/pkcs7/p7decode.c new file mode 100644 index 0000000000..641d201e5a --- /dev/null +++ b/security/nss/lib/pkcs7/p7decode.c @@ -0,0 +1,1919 @@ +/* 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/. */ + +/* + * PKCS7 decoding, verification. + */ + +#include "p7local.h" + +#include "cert.h" +/* XXX do not want to have to include */ +#include "certdb.h" /* certdb.h -- the trust stuff needed by */ + /* the add certificate code needs to get */ + /* rewritten/abstracted and then this */ + /* include should be removed! */ +/*#include "cdbhdl.h" */ +#include "cryptohi.h" +#include "keyhi.h" +#include "secasn1.h" +#include "secitem.h" +#include "secoid.h" +#include "pk11func.h" +#include "prtime.h" +#include "secerr.h" +#include "sechash.h" /* for HASH_GetHashObject() */ +#include "secder.h" +#include "secpkcs5.h" + +struct sec_pkcs7_decoder_worker { + int depth; + int digcnt; + void **digcxs; + const SECHashObject **digobjs; + sec_PKCS7CipherObject *decryptobj; + PRBool saw_contents; +}; + +struct SEC_PKCS7DecoderContextStr { + SEC_ASN1DecoderContext *dcx; + SEC_PKCS7ContentInfo *cinfo; + SEC_PKCS7DecoderContentCallback cb; + void *cb_arg; + SECKEYGetPasswordKey pwfn; + void *pwfn_arg; + struct sec_pkcs7_decoder_worker worker; + PLArenaPool *tmp_poolp; + int error; + SEC_PKCS7GetDecryptKeyCallback dkcb; + void *dkcb_arg; + SEC_PKCS7DecryptionAllowedCallback decrypt_allowed_cb; +}; + +/* + * Handle one worker, decrypting and digesting the data as necessary. + * + * XXX If/when we support nested contents, this probably needs to be + * revised somewhat to get passed the content-info (which unfortunately + * can be two different types depending on whether it is encrypted or not) + * corresponding to the given worker. + */ +static void +sec_pkcs7_decoder_work_data(SEC_PKCS7DecoderContext *p7dcx, + struct sec_pkcs7_decoder_worker *worker, + const unsigned char *data, unsigned long len, + PRBool final) +{ + unsigned char *buf = NULL; + SECStatus rv; + int i; + + /* + * We should really have data to process, or we should be trying + * to finish/flush the last block. (This is an overly paranoid + * check since all callers are in this file and simple inspection + * proves they do it right. But it could find a bug in future + * modifications/development, that is why it is here.) + */ + PORT_Assert((data != NULL && len) || final); + + /* + * Decrypt this chunk. + * + * XXX If we get an error, we do not want to do the digest or callback, + * but we want to keep decoding. Or maybe we want to stop decoding + * altogether if there is a callback, because obviously we are not + * sending the data back and they want to know that. + */ + if (worker->decryptobj != NULL) { + /* XXX the following lengths should all be longs? */ + unsigned int inlen; /* length of data being decrypted */ + unsigned int outlen; /* length of decrypted data */ + unsigned int buflen; /* length available for decrypted data */ + SECItem *plain; + + inlen = len; + buflen = sec_PKCS7DecryptLength(worker->decryptobj, inlen, final); + if (buflen == 0) { + if (inlen == 0) /* no input and no output */ + return; + /* + * No output is expected, but the input data may be buffered + * so we still have to call Decrypt. + */ + rv = sec_PKCS7Decrypt(worker->decryptobj, NULL, NULL, 0, + data, inlen, final); + if (rv != SECSuccess) { + p7dcx->error = PORT_GetError(); + return; /* XXX indicate error? */ + } + return; + } + + if (p7dcx->cb != NULL) { + buf = (unsigned char *)PORT_Alloc(buflen); + plain = NULL; + } else { + unsigned long oldlen; + + /* + * XXX This assumes one level of content only. + * See comment above about nested content types. + * XXX Also, it should work for signedAndEnvelopedData, too! + */ + plain = &(p7dcx->cinfo->content.envelopedData->encContentInfo.plainContent); + + oldlen = plain->len; + if (oldlen == 0) { + buf = (unsigned char *)PORT_ArenaAlloc(p7dcx->cinfo->poolp, + buflen); + } else { + buf = (unsigned char *)PORT_ArenaGrow(p7dcx->cinfo->poolp, + plain->data, + oldlen, oldlen + buflen); + if (buf != NULL) + buf += oldlen; + } + plain->data = buf; + } + if (buf == NULL) { + p7dcx->error = SEC_ERROR_NO_MEMORY; + return; /* XXX indicate error? */ + } + rv = sec_PKCS7Decrypt(worker->decryptobj, buf, &outlen, buflen, + data, inlen, final); + if (rv != SECSuccess) { + p7dcx->error = PORT_GetError(); + return; /* XXX indicate error? */ + } + if (plain != NULL) { + PORT_Assert(final || outlen == buflen); + plain->len += outlen; + } + data = buf; + len = outlen; + } + + /* + * Update the running digests. + */ + if (len) { + for (i = 0; i < worker->digcnt; i++) { + (*worker->digobjs[i]->update)(worker->digcxs[i], data, len); + } + } + + /* + * Pass back the contents bytes, and free the temporary buffer. + */ + if (p7dcx->cb != NULL) { + if (len) + (*p7dcx->cb)(p7dcx->cb_arg, (const char *)data, len); + if (worker->decryptobj != NULL) { + PORT_Assert(buf != NULL); + PORT_Free(buf); + } + } +} + +static void +sec_pkcs7_decoder_filter(void *arg, const char *data, unsigned long len, + int depth, SEC_ASN1EncodingPart data_kind) +{ + SEC_PKCS7DecoderContext *p7dcx; + struct sec_pkcs7_decoder_worker *worker; + + /* + * Since we do not handle any nested contents, the only bytes we + * are really interested in are the actual contents bytes (not + * the identifier, length, or end-of-contents bytes). If we were + * handling nested types we would probably need to do something + * smarter based on depth and data_kind. + */ + if (data_kind != SEC_ASN1_Contents) + return; + + /* + * The ASN.1 decoder should not even call us with a length of 0. + * Just being paranoid. + */ + PORT_Assert(len); + if (len == 0) + return; + + p7dcx = (SEC_PKCS7DecoderContext *)arg; + + /* + * Handling nested contents would mean that there is a chain + * of workers -- one per each level of content. The following + * would start with the first worker and loop over them. + */ + worker = &(p7dcx->worker); + + worker->saw_contents = PR_TRUE; + + sec_pkcs7_decoder_work_data(p7dcx, worker, + (const unsigned char *)data, len, PR_FALSE); +} + +/* + * Create digest contexts for each algorithm in "digestalgs". + * No algorithms is not an error, we just do not do anything. + * An error (like trouble allocating memory), marks the error + * in "p7dcx" and returns SECFailure, which means that our caller + * should just give up altogether. + */ +static SECStatus +sec_pkcs7_decoder_start_digests(SEC_PKCS7DecoderContext *p7dcx, int depth, + SECAlgorithmID **digestalgs) +{ + int i, digcnt; + + if (digestalgs == NULL) + return SECSuccess; + + /* + * Count the algorithms. + */ + digcnt = 0; + while (digestalgs[digcnt] != NULL) + digcnt++; + + /* + * No algorithms means no work to do. + * Just act as if there were no algorithms specified. + */ + if (digcnt == 0) + return SECSuccess; + + p7dcx->worker.digcxs = (void **)PORT_ArenaAlloc(p7dcx->tmp_poolp, + digcnt * sizeof(void *)); + p7dcx->worker.digobjs = (const SECHashObject **)PORT_ArenaAlloc(p7dcx->tmp_poolp, + digcnt * sizeof(SECHashObject *)); + if (p7dcx->worker.digcxs == NULL || p7dcx->worker.digobjs == NULL) { + p7dcx->error = SEC_ERROR_NO_MEMORY; + return SECFailure; + } + + p7dcx->worker.depth = depth; + p7dcx->worker.digcnt = 0; + + /* + * Create a digest context for each algorithm. + */ + for (i = 0; i < digcnt; i++) { + SECAlgorithmID *algid = digestalgs[i]; + SECOidTag oidTag = SECOID_FindOIDTag(&(algid->algorithm)); + const SECHashObject *digobj = HASH_GetHashObjectByOidTag(oidTag); + void *digcx; + + /* + * Skip any algorithm we do not even recognize; obviously, + * this could be a problem, but if it is critical then the + * result will just be that the signature does not verify. + * We do not necessarily want to error out here, because + * the particular algorithm may not actually be important, + * but we cannot know that until later. + */ + if (digobj == NULL) { + p7dcx->worker.digcnt--; + continue; + } + + digcx = (*digobj->create)(); + if (digcx != NULL) { + (*digobj->begin)(digcx); + p7dcx->worker.digobjs[p7dcx->worker.digcnt] = digobj; + p7dcx->worker.digcxs[p7dcx->worker.digcnt] = digcx; + p7dcx->worker.digcnt++; + } + } + + if (p7dcx->worker.digcnt != 0) + SEC_ASN1DecoderSetFilterProc(p7dcx->dcx, + sec_pkcs7_decoder_filter, + p7dcx, + (PRBool)(p7dcx->cb != NULL)); + return SECSuccess; +} + +/* + * Close out all of the digest contexts, storing the results in "digestsp". + */ +static SECStatus +sec_pkcs7_decoder_finish_digests(SEC_PKCS7DecoderContext *p7dcx, + PLArenaPool *poolp, + SECItem ***digestsp) +{ + struct sec_pkcs7_decoder_worker *worker; + const SECHashObject *digobj; + void *digcx; + SECItem **digests, *digest; + int i; + void *mark; + + /* + * XXX Handling nested contents would mean that there is a chain + * of workers -- one per each level of content. The following + * would want to find the last worker in the chain. + */ + worker = &(p7dcx->worker); + + /* + * If no digests, then we have nothing to do. + */ + if (worker->digcnt == 0) + return SECSuccess; + + /* + * No matter what happens after this, we want to stop filtering. + * XXX If we handle nested contents, we only want to stop filtering + * if we are finishing off the *last* worker. + */ + SEC_ASN1DecoderClearFilterProc(p7dcx->dcx); + + /* + * If we ended up with no contents, just destroy each + * digest context -- they are meaningless and potentially + * confusing, because their presence would imply some content + * was digested. + */ + if (!worker->saw_contents) { + for (i = 0; i < worker->digcnt; i++) { + digcx = worker->digcxs[i]; + digobj = worker->digobjs[i]; + (*digobj->destroy)(digcx, PR_TRUE); + } + return SECSuccess; + } + + mark = PORT_ArenaMark(poolp); + + /* + * Close out each digest context, saving digest away. + */ + digests = + (SECItem **)PORT_ArenaAlloc(poolp, (worker->digcnt + 1) * sizeof(SECItem *)); + digest = (SECItem *)PORT_ArenaAlloc(poolp, worker->digcnt * sizeof(SECItem)); + if (digests == NULL || digest == NULL) { + p7dcx->error = PORT_GetError(); + PORT_ArenaRelease(poolp, mark); + return SECFailure; + } + + for (i = 0; i < worker->digcnt; i++, digest++) { + digcx = worker->digcxs[i]; + digobj = worker->digobjs[i]; + + digest->data = (unsigned char *)PORT_ArenaAlloc(poolp, digobj->length); + if (digest->data == NULL) { + p7dcx->error = PORT_GetError(); + PORT_ArenaRelease(poolp, mark); + return SECFailure; + } + + digest->len = digobj->length; + (*digobj->end)(digcx, digest->data, &(digest->len), digest->len); + (*digobj->destroy)(digcx, PR_TRUE); + + digests[i] = digest; + } + digests[i] = NULL; + *digestsp = digests; + + PORT_ArenaUnmark(poolp, mark); + return SECSuccess; +} + +/* + * XXX Need comment explaining following helper function (which is used + * by sec_pkcs7_decoder_start_decrypt). + */ + +static PK11SymKey * +sec_pkcs7_decoder_get_recipient_key(SEC_PKCS7DecoderContext *p7dcx, + SEC_PKCS7RecipientInfo **recipientinfos, + SEC_PKCS7EncryptedContentInfo *enccinfo) +{ + SEC_PKCS7RecipientInfo *ri; + CERTCertificate *cert = NULL; + SECKEYPrivateKey *privkey = NULL; + PK11SymKey *bulkkey = NULL; + SECOidTag keyalgtag, bulkalgtag, encalgtag; + PK11SlotInfo *slot = NULL; + + if (recipientinfos == NULL || recipientinfos[0] == NULL) { + p7dcx->error = SEC_ERROR_NOT_A_RECIPIENT; + goto no_key_found; + } + + cert = PK11_FindCertAndKeyByRecipientList(&slot, recipientinfos, &ri, + &privkey, p7dcx->pwfn_arg); + if (cert == NULL) { + p7dcx->error = SEC_ERROR_NOT_A_RECIPIENT; + goto no_key_found; + } + + ri->cert = cert; /* so we can find it later */ + PORT_Assert(privkey != NULL); + + keyalgtag = SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm)); + encalgtag = SECOID_GetAlgorithmTag(&(ri->keyEncAlg)); + if (keyalgtag != encalgtag) { + p7dcx->error = SEC_ERROR_PKCS7_KEYALG_MISMATCH; + goto no_key_found; + } + bulkalgtag = SECOID_GetAlgorithmTag(&(enccinfo->contentEncAlg)); + + switch (encalgtag) { + case SEC_OID_PKCS1_RSA_ENCRYPTION: + bulkkey = PK11_PubUnwrapSymKey(privkey, &ri->encKey, + PK11_AlgtagToMechanism(bulkalgtag), + CKA_DECRYPT, 0); + if (bulkkey == NULL) { + p7dcx->error = PORT_GetError(); + PORT_SetError(0); + goto no_key_found; + } + break; + default: + p7dcx->error = SEC_ERROR_UNSUPPORTED_KEYALG; + break; + } + +no_key_found: + if (privkey != NULL) + SECKEY_DestroyPrivateKey(privkey); + if (slot != NULL) + PK11_FreeSlot(slot); + + return bulkkey; +} + +/* + * XXX The following comment is old -- the function used to only handle + * EnvelopedData or SignedAndEnvelopedData but now handles EncryptedData + * as well (and it had all of the code of the helper function above + * built into it), though the comment was left as is. Fix it... + * + * We are just about to decode the content of an EnvelopedData. + * Set up a decryption context so we can decrypt as we go. + * Presumably we are one of the recipients listed in "recipientinfos". + * (XXX And if we are not, or if we have trouble, what should we do? + * It would be nice to let the decoding still work. Maybe it should + * be an error if there is a content callback, but not an error otherwise?) + * The encryption key and related information can be found in "enccinfo". + */ +static SECStatus +sec_pkcs7_decoder_start_decrypt(SEC_PKCS7DecoderContext *p7dcx, int depth, + SEC_PKCS7RecipientInfo **recipientinfos, + SEC_PKCS7EncryptedContentInfo *enccinfo, + PK11SymKey **copy_key_for_signature) +{ + PK11SymKey *bulkkey = NULL; + sec_PKCS7CipherObject *decryptobj; + + /* + * If a callback is supplied to retrieve the encryption key, + * for instance, for Encrypted Content infos, then retrieve + * the bulkkey from the callback. Otherwise, assume that + * we are processing Enveloped or SignedAndEnveloped data + * content infos. + * + * XXX Put an assert here? + */ + if (SEC_PKCS7ContentType(p7dcx->cinfo) == SEC_OID_PKCS7_ENCRYPTED_DATA) { + if (p7dcx->dkcb != NULL) { + bulkkey = (*p7dcx->dkcb)(p7dcx->dkcb_arg, + &(enccinfo->contentEncAlg)); + } + enccinfo->keysize = 0; + } else { + bulkkey = sec_pkcs7_decoder_get_recipient_key(p7dcx, recipientinfos, + enccinfo); + if (bulkkey == NULL) + goto no_decryption; + enccinfo->keysize = PK11_GetKeyStrength(bulkkey, + &(enccinfo->contentEncAlg)); + } + + /* + * XXX I think following should set error in p7dcx and clear set error + * (as used to be done here, or as is done in get_receipient_key above. + */ + if (bulkkey == NULL) { + goto no_decryption; + } + + /* + * We want to make sure decryption is allowed. This is done via + * a callback specified in SEC_PKCS7DecoderStart(). + */ + if (p7dcx->decrypt_allowed_cb) { + if ((*p7dcx->decrypt_allowed_cb)(&(enccinfo->contentEncAlg), + bulkkey) == PR_FALSE) { + p7dcx->error = SEC_ERROR_DECRYPTION_DISALLOWED; + goto no_decryption; + } + } else { + p7dcx->error = SEC_ERROR_DECRYPTION_DISALLOWED; + goto no_decryption; + } + + /* + * When decrypting a signedAndEnvelopedData, the signature also has + * to be decrypted with the bulk encryption key; to avoid having to + * get it all over again later (and do another potentially expensive + * RSA operation), copy it for later signature verification to use. + */ + if (copy_key_for_signature != NULL) + *copy_key_for_signature = PK11_ReferenceSymKey(bulkkey); + + /* + * Now we have the bulk encryption key (in bulkkey) and the + * the algorithm (in enccinfo->contentEncAlg). Using those, + * create a decryption context. + */ + decryptobj = sec_PKCS7CreateDecryptObject(bulkkey, + &(enccinfo->contentEncAlg)); + + /* + * We are done with (this) bulkkey now. + */ + PK11_FreeSymKey(bulkkey); + + if (decryptobj == NULL) { + p7dcx->error = PORT_GetError(); + PORT_SetError(0); + goto no_decryption; + } + + SEC_ASN1DecoderSetFilterProc(p7dcx->dcx, + sec_pkcs7_decoder_filter, + p7dcx, + (PRBool)(p7dcx->cb != NULL)); + + p7dcx->worker.depth = depth; + p7dcx->worker.decryptobj = decryptobj; + + return SECSuccess; + +no_decryption: + PK11_FreeSymKey(bulkkey); + /* + * For some reason (error set already, if appropriate), we cannot + * decrypt the content. I am not sure what exactly is the right + * thing to do here; in some cases we want to just stop, and in + * others we want to let the decoding finish even though we cannot + * decrypt the content. My current thinking is that if the caller + * set up a content callback, then they are really interested in + * getting (decrypted) content, and if they cannot they will want + * to know about it. However, if no callback was specified, then + * maybe it is not important that the decryption failed. + */ + if (p7dcx->cb != NULL) + return SECFailure; + else + return SECSuccess; /* Let the decoding continue. */ +} + +static SECStatus +sec_pkcs7_decoder_finish_decrypt(SEC_PKCS7DecoderContext *p7dcx, + PLArenaPool *poolp, + SEC_PKCS7EncryptedContentInfo *enccinfo) +{ + struct sec_pkcs7_decoder_worker *worker; + + /* + * XXX Handling nested contents would mean that there is a chain + * of workers -- one per each level of content. The following + * would want to find the last worker in the chain. + */ + worker = &(p7dcx->worker); + + /* + * If no decryption context, then we have nothing to do. + */ + if (worker->decryptobj == NULL) + return SECSuccess; + + /* + * No matter what happens after this, we want to stop filtering. + * XXX If we handle nested contents, we only want to stop filtering + * if we are finishing off the *last* worker. + */ + SEC_ASN1DecoderClearFilterProc(p7dcx->dcx); + + /* + * Handle the last block. + */ + sec_pkcs7_decoder_work_data(p7dcx, worker, NULL, 0, PR_TRUE); + + /* + * All done, destroy it. + */ + sec_PKCS7DestroyDecryptObject(worker->decryptobj); + worker->decryptobj = NULL; + + return SECSuccess; +} + +static void +sec_pkcs7_decoder_notify(void *arg, PRBool before, void *dest, int depth) +{ + SEC_PKCS7DecoderContext *p7dcx; + SEC_PKCS7ContentInfo *cinfo; + SEC_PKCS7SignedData *sigd; + SEC_PKCS7EnvelopedData *envd; + SEC_PKCS7SignedAndEnvelopedData *saed; + SEC_PKCS7EncryptedData *encd; + SEC_PKCS7DigestedData *digd; + PRBool after; + SECStatus rv; + + /* + * Just to make the code easier to read, create an "after" variable + * that is equivalent to "not before". + * (This used to be just the statement "after = !before", but that + * causes a warning on the mac; to avoid that, we do it the long way.) + */ + if (before) + after = PR_FALSE; + else + after = PR_TRUE; + + p7dcx = (SEC_PKCS7DecoderContext *)arg; + if (!p7dcx) { + return; + } + + cinfo = p7dcx->cinfo; + + if (!cinfo) { + return; + } + + if (cinfo->contentTypeTag == NULL) { + if (after && dest == &(cinfo->contentType)) + cinfo->contentTypeTag = SECOID_FindOID(&(cinfo->contentType)); + return; + } + + switch (cinfo->contentTypeTag->offset) { + case SEC_OID_PKCS7_SIGNED_DATA: + sigd = cinfo->content.signedData; + if (sigd == NULL) + break; + + if (sigd->contentInfo.contentTypeTag == NULL) { + if (after && dest == &(sigd->contentInfo.contentType)) + sigd->contentInfo.contentTypeTag = + SECOID_FindOID(&(sigd->contentInfo.contentType)); + break; + } + + /* + * We only set up a filtering digest if the content is + * plain DATA; anything else needs more work because a + * second pass is required to produce a DER encoding from + * an input that can be BER encoded. (This is a requirement + * of PKCS7 that is unfortunate, but there you have it.) + * + * XXX Also, since we stop here if this is not DATA, the + * inner content is not getting processed at all. Someday + * we may want to fix that. + */ + if (sigd->contentInfo.contentTypeTag->offset != SEC_OID_PKCS7_DATA) { + /* XXX Set an error in p7dcx->error */ + SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx); + break; + } + + /* + * Just before the content, we want to set up a digest context + * for each digest algorithm listed, and start a filter which + * will run all of the contents bytes through that digest. + */ + if (before && dest == &(sigd->contentInfo.content)) { + rv = sec_pkcs7_decoder_start_digests(p7dcx, depth, + sigd->digestAlgorithms); + if (rv != SECSuccess) + SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx); + + break; + } + + /* + * XXX To handle nested types, here is where we would want + * to check for inner boundaries that need handling. + */ + + /* + * Are we done? + */ + if (after && dest == &(sigd->contentInfo.content)) { + /* + * Close out the digest contexts. We ignore any error + * because we are stopping anyway; the error status left + * behind in p7dcx will be seen by outer functions. + */ + (void)sec_pkcs7_decoder_finish_digests(p7dcx, cinfo->poolp, + &(sigd->digests)); + + /* + * XXX To handle nested contents, we would need to remove + * the worker from the chain (and free it). + */ + + /* + * Stop notify. + */ + SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx); + } + break; + + case SEC_OID_PKCS7_ENVELOPED_DATA: + envd = cinfo->content.envelopedData; + if (envd == NULL) + break; + + if (envd->encContentInfo.contentTypeTag == NULL) { + if (after && dest == &(envd->encContentInfo.contentType)) + envd->encContentInfo.contentTypeTag = + SECOID_FindOID(&(envd->encContentInfo.contentType)); + break; + } + + /* + * Just before the content, we want to set up a decryption + * context, and start a filter which will run all of the + * contents bytes through it to determine the plain content. + */ + if (before && dest == &(envd->encContentInfo.encContent)) { + rv = sec_pkcs7_decoder_start_decrypt(p7dcx, depth, + envd->recipientInfos, + &(envd->encContentInfo), + NULL); + if (rv != SECSuccess) + SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx); + + break; + } + + /* + * Are we done? + */ + if (after && dest == &(envd->encContentInfo.encContent)) { + /* + * Close out the decryption context. We ignore any error + * because we are stopping anyway; the error status left + * behind in p7dcx will be seen by outer functions. + */ + (void)sec_pkcs7_decoder_finish_decrypt(p7dcx, cinfo->poolp, + &(envd->encContentInfo)); + + /* + * XXX To handle nested contents, we would need to remove + * the worker from the chain (and free it). + */ + + /* + * Stop notify. + */ + SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx); + } + break; + + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: + saed = cinfo->content.signedAndEnvelopedData; + if (saed == NULL) + break; + + if (saed->encContentInfo.contentTypeTag == NULL) { + if (after && dest == &(saed->encContentInfo.contentType)) + saed->encContentInfo.contentTypeTag = + SECOID_FindOID(&(saed->encContentInfo.contentType)); + break; + } + + /* + * Just before the content, we want to set up a decryption + * context *and* digest contexts, and start a filter which + * will run all of the contents bytes through both. + */ + if (before && dest == &(saed->encContentInfo.encContent)) { + rv = sec_pkcs7_decoder_start_decrypt(p7dcx, depth, + saed->recipientInfos, + &(saed->encContentInfo), + &(saed->sigKey)); + if (rv == SECSuccess) + rv = sec_pkcs7_decoder_start_digests(p7dcx, depth, + saed->digestAlgorithms); + if (rv != SECSuccess) + SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx); + + break; + } + + /* + * Are we done? + */ + if (after && dest == &(saed->encContentInfo.encContent)) { + /* + * Close out the decryption and digests contexts. + * We ignore any errors because we are stopping anyway; + * the error status left behind in p7dcx will be seen by + * outer functions. + * + * Note that the decrypt stuff must be called first; + * it may have a last buffer to do which in turn has + * to be added to the digest. + */ + (void)sec_pkcs7_decoder_finish_decrypt(p7dcx, cinfo->poolp, + &(saed->encContentInfo)); + (void)sec_pkcs7_decoder_finish_digests(p7dcx, cinfo->poolp, + &(saed->digests)); + + /* + * XXX To handle nested contents, we would need to remove + * the worker from the chain (and free it). + */ + + /* + * Stop notify. + */ + SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx); + } + break; + + case SEC_OID_PKCS7_DIGESTED_DATA: + digd = cinfo->content.digestedData; + + /* + * XXX Want to do the digest or not? Maybe future enhancement... + */ + if (before && dest == &(digd->contentInfo.content.data)) { + SEC_ASN1DecoderSetFilterProc(p7dcx->dcx, sec_pkcs7_decoder_filter, + p7dcx, + (PRBool)(p7dcx->cb != NULL)); + break; + } + + /* + * Are we done? + */ + if (after && dest == &(digd->contentInfo.content.data)) { + SEC_ASN1DecoderClearFilterProc(p7dcx->dcx); + } + break; + + case SEC_OID_PKCS7_ENCRYPTED_DATA: + encd = cinfo->content.encryptedData; + + if (!encd) { + break; + } + + /* + * XXX If the decryption key callback is set, we want to start + * the decryption. If the callback is not set, we will treat the + * content as plain data, since we do not have the key. + * + * Is this the proper thing to do? + */ + if (before && dest == &(encd->encContentInfo.encContent)) { + /* + * Start the encryption process if the decryption key callback + * is present. Otherwise, treat the content like plain data. + */ + rv = SECSuccess; + if (p7dcx->dkcb != NULL) { + rv = sec_pkcs7_decoder_start_decrypt(p7dcx, depth, NULL, + &(encd->encContentInfo), + NULL); + } + + if (rv != SECSuccess) + SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx); + + break; + } + + /* + * Are we done? + */ + if (after && dest == &(encd->encContentInfo.encContent)) { + /* + * Close out the decryption context. We ignore any error + * because we are stopping anyway; the error status left + * behind in p7dcx will be seen by outer functions. + */ + (void)sec_pkcs7_decoder_finish_decrypt(p7dcx, cinfo->poolp, + &(encd->encContentInfo)); + + /* + * Stop notify. + */ + SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx); + } + break; + + case SEC_OID_PKCS7_DATA: + /* + * If a output callback has been specified, we want to set the filter + * to call the callback. This is taken care of in + * sec_pkcs7_decoder_start_decrypt() or + * sec_pkcs7_decoder_start_digests() for the other content types. + */ + + if (before && dest == &(cinfo->content.data)) { + + /* + * Set the filter proc up. + */ + SEC_ASN1DecoderSetFilterProc(p7dcx->dcx, + sec_pkcs7_decoder_filter, + p7dcx, + (PRBool)(p7dcx->cb != NULL)); + break; + } + + if (after && dest == &(cinfo->content.data)) { + /* + * Time to clean up after ourself, stop the Notify and Filter + * procedures. + */ + SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx); + SEC_ASN1DecoderClearFilterProc(p7dcx->dcx); + } + break; + + default: + SEC_ASN1DecoderClearNotifyProc(p7dcx->dcx); + break; + } +} + +SEC_PKCS7DecoderContext * +SEC_PKCS7DecoderStart(SEC_PKCS7DecoderContentCallback cb, void *cb_arg, + SECKEYGetPasswordKey pwfn, void *pwfn_arg, + SEC_PKCS7GetDecryptKeyCallback decrypt_key_cb, + void *decrypt_key_cb_arg, + SEC_PKCS7DecryptionAllowedCallback decrypt_allowed_cb) +{ + SEC_PKCS7DecoderContext *p7dcx; + SEC_ASN1DecoderContext *dcx; + SEC_PKCS7ContentInfo *cinfo; + PLArenaPool *poolp; + + poolp = PORT_NewArena(1024); /* XXX what is right value? */ + if (poolp == NULL) + return NULL; + + cinfo = (SEC_PKCS7ContentInfo *)PORT_ArenaZAlloc(poolp, sizeof(*cinfo)); + if (cinfo == NULL) { + PORT_FreeArena(poolp, PR_FALSE); + return NULL; + } + + cinfo->poolp = poolp; + cinfo->pwfn = pwfn; + cinfo->pwfn_arg = pwfn_arg; + cinfo->created = PR_FALSE; + cinfo->refCount = 1; + + p7dcx = + (SEC_PKCS7DecoderContext *)PORT_ZAlloc(sizeof(SEC_PKCS7DecoderContext)); + if (p7dcx == NULL) { + PORT_FreeArena(poolp, PR_FALSE); + return NULL; + } + + p7dcx->tmp_poolp = PORT_NewArena(1024); /* XXX what is right value? */ + if (p7dcx->tmp_poolp == NULL) { + PORT_Free(p7dcx); + PORT_FreeArena(poolp, PR_FALSE); + return NULL; + } + + dcx = SEC_ASN1DecoderStart(poolp, cinfo, sec_PKCS7ContentInfoTemplate); + if (dcx == NULL) { + PORT_FreeArena(p7dcx->tmp_poolp, PR_FALSE); + PORT_Free(p7dcx); + PORT_FreeArena(poolp, PR_FALSE); + return NULL; + } + + SEC_ASN1DecoderSetNotifyProc(dcx, sec_pkcs7_decoder_notify, p7dcx); + + p7dcx->dcx = dcx; + p7dcx->cinfo = cinfo; + p7dcx->cb = cb; + p7dcx->cb_arg = cb_arg; + p7dcx->pwfn = pwfn; + p7dcx->pwfn_arg = pwfn_arg; + p7dcx->dkcb = decrypt_key_cb; + p7dcx->dkcb_arg = decrypt_key_cb_arg; + p7dcx->decrypt_allowed_cb = decrypt_allowed_cb; + + return p7dcx; +} + +/* + * Do the next chunk of PKCS7 decoding. If there is a problem, set + * an error and return a failure status. Note that in the case of + * an error, this routine is still prepared to be called again and + * again in case that is the easiest route for our caller to take. + * We simply detect it and do not do anything except keep setting + * that error in case our caller has not noticed it yet... + */ +SECStatus +SEC_PKCS7DecoderUpdate(SEC_PKCS7DecoderContext *p7dcx, + const char *buf, unsigned long len) +{ + if (!p7dcx) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (p7dcx->cinfo != NULL && p7dcx->dcx != NULL) { + PORT_Assert(p7dcx->error == 0); + if (p7dcx->error == 0) { + if (SEC_ASN1DecoderUpdate(p7dcx->dcx, buf, len) != SECSuccess) { + p7dcx->error = PORT_GetError(); + PORT_Assert(p7dcx->error); + if (p7dcx->error == 0) + p7dcx->error = -1; + } + } + } + + if (p7dcx->error) { + if (p7dcx->dcx != NULL) { + (void)SEC_ASN1DecoderFinish(p7dcx->dcx); + p7dcx->dcx = NULL; + } + if (p7dcx->cinfo != NULL) { + SEC_PKCS7DestroyContentInfo(p7dcx->cinfo); + p7dcx->cinfo = NULL; + } + PORT_SetError(p7dcx->error); + return SECFailure; + } + + return SECSuccess; +} + +SEC_PKCS7ContentInfo * +SEC_PKCS7DecoderFinish(SEC_PKCS7DecoderContext *p7dcx) +{ + SEC_PKCS7ContentInfo *cinfo; + + cinfo = p7dcx->cinfo; + if (p7dcx->dcx != NULL) { + if (SEC_ASN1DecoderFinish(p7dcx->dcx) != SECSuccess) { + SEC_PKCS7DestroyContentInfo(cinfo); + cinfo = NULL; + } + } + /* free any NSS data structures */ + if (p7dcx->worker.decryptobj) { + sec_PKCS7DestroyDecryptObject(p7dcx->worker.decryptobj); + } + PORT_FreeArena(p7dcx->tmp_poolp, PR_FALSE); + PORT_Free(p7dcx); + return cinfo; +} + +SEC_PKCS7ContentInfo * +SEC_PKCS7DecodeItem(SECItem *p7item, + SEC_PKCS7DecoderContentCallback cb, void *cb_arg, + SECKEYGetPasswordKey pwfn, void *pwfn_arg, + SEC_PKCS7GetDecryptKeyCallback decrypt_key_cb, + void *decrypt_key_cb_arg, + SEC_PKCS7DecryptionAllowedCallback decrypt_allowed_cb) +{ + SEC_PKCS7DecoderContext *p7dcx; + + p7dcx = SEC_PKCS7DecoderStart(cb, cb_arg, pwfn, pwfn_arg, decrypt_key_cb, + decrypt_key_cb_arg, decrypt_allowed_cb); + if (!p7dcx) { + /* error code is set */ + return NULL; + } + (void)SEC_PKCS7DecoderUpdate(p7dcx, (char *)p7item->data, p7item->len); + return SEC_PKCS7DecoderFinish(p7dcx); +} + +/* + * Abort the ASN.1 stream. Used by pkcs 12 + */ +void +SEC_PKCS7DecoderAbort(SEC_PKCS7DecoderContext *p7dcx, int error) +{ + PORT_Assert(p7dcx); + SEC_ASN1DecoderAbort(p7dcx->dcx, error); +} + +/* + * If the thing contains any certs or crls return true; false otherwise. + */ +PRBool +SEC_PKCS7ContainsCertsOrCrls(SEC_PKCS7ContentInfo *cinfo) +{ + SECOidTag kind; + SECItem **certs; + CERTSignedCrl **crls; + + kind = SEC_PKCS7ContentType(cinfo); + switch (kind) { + default: + case SEC_OID_PKCS7_DATA: + case SEC_OID_PKCS7_DIGESTED_DATA: + case SEC_OID_PKCS7_ENVELOPED_DATA: + case SEC_OID_PKCS7_ENCRYPTED_DATA: + return PR_FALSE; + case SEC_OID_PKCS7_SIGNED_DATA: + certs = cinfo->content.signedData->rawCerts; + crls = cinfo->content.signedData->crls; + break; + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: + certs = cinfo->content.signedAndEnvelopedData->rawCerts; + crls = cinfo->content.signedAndEnvelopedData->crls; + break; + } + + /* + * I know this could be collapsed, but I was in a mood to be explicit. + */ + if (certs != NULL && certs[0] != NULL) + return PR_TRUE; + else if (crls != NULL && crls[0] != NULL) + return PR_TRUE; + else + return PR_FALSE; +} + +/* return the content length...could use GetContent, however we + * need the encrypted content length + */ +PRBool +SEC_PKCS7IsContentEmpty(SEC_PKCS7ContentInfo *cinfo, unsigned int minLen) +{ + SECItem *item = NULL; + + if (cinfo == NULL) { + return PR_TRUE; + } + + switch (SEC_PKCS7ContentType(cinfo)) { + case SEC_OID_PKCS7_DATA: + item = cinfo->content.data; + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + item = &cinfo->content.encryptedData->encContentInfo.encContent; + break; + default: + /* add other types */ + return PR_FALSE; + } + + if (!item) { + return PR_TRUE; + } else if (item->len <= minLen) { + return PR_TRUE; + } + + return PR_FALSE; +} + +PRBool +SEC_PKCS7ContentIsEncrypted(SEC_PKCS7ContentInfo *cinfo) +{ + SECOidTag kind; + + kind = SEC_PKCS7ContentType(cinfo); + switch (kind) { + default: + case SEC_OID_PKCS7_DATA: + case SEC_OID_PKCS7_DIGESTED_DATA: + case SEC_OID_PKCS7_SIGNED_DATA: + return PR_FALSE; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + case SEC_OID_PKCS7_ENVELOPED_DATA: + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: + return PR_TRUE; + } +} + +/* + * If the PKCS7 content has a signature (not just *could* have a signature) + * return true; false otherwise. This can/should be called before calling + * VerifySignature, which will always indicate failure if no signature is + * present, but that does not mean there even was a signature! + * Note that the content itself can be empty (detached content was sent + * another way); it is the presence of the signature that matters. + */ +PRBool +SEC_PKCS7ContentIsSigned(SEC_PKCS7ContentInfo *cinfo) +{ + SECOidTag kind; + SEC_PKCS7SignerInfo **signerinfos; + + kind = SEC_PKCS7ContentType(cinfo); + switch (kind) { + default: + case SEC_OID_PKCS7_DATA: + case SEC_OID_PKCS7_DIGESTED_DATA: + case SEC_OID_PKCS7_ENVELOPED_DATA: + case SEC_OID_PKCS7_ENCRYPTED_DATA: + return PR_FALSE; + case SEC_OID_PKCS7_SIGNED_DATA: + signerinfos = cinfo->content.signedData->signerInfos; + break; + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: + signerinfos = cinfo->content.signedAndEnvelopedData->signerInfos; + break; + } + + /* + * I know this could be collapsed; but I kind of think it will get + * more complicated before I am finished, so... + */ + if (signerinfos != NULL && signerinfos[0] != NULL) + return PR_TRUE; + else + return PR_FALSE; +} + +/* + * sec_pkcs7_verify_signature + * + * Look at a PKCS7 contentInfo and check if the signature is good. + * The digest was either calculated earlier (and is stored in the + * contentInfo itself) or is passed in via "detached_digest". + * + * The verification checks that the signing cert is valid and trusted + * for the purpose specified by "certusage" at + * - "*atTime" if "atTime" is not null, or + * - the signing time if the signing time is available in "cinfo", or + * - the current time (as returned by PR_Now). + * + * In addition, if "keepcerts" is true, add any new certificates found + * into our local database. + * + * XXX Each place which returns PR_FALSE should be sure to have a good + * error set for inspection by the caller. Alternatively, we could create + * an enumeration of success and each type of failure and return that + * instead of a boolean. For now, the default in a bad situation is to + * set the error to SEC_ERROR_PKCS7_BAD_SIGNATURE. But this should be + * reviewed; better (more specific) errors should be possible (to distinguish + * a signature failure from a badly-formed pkcs7 signedData, for example). + * Some of the errors should probably just be SEC_ERROR_BAD_SIGNATURE, + * but that has a less helpful error string associated with it right now; + * if/when that changes, review and change these as needed. + * + * XXX This is broken wrt signedAndEnvelopedData. In that case, the + * message digest is doubly encrypted -- first encrypted with the signer + * private key but then again encrypted with the bulk encryption key used + * to encrypt the content. So before we can pass the digest to VerifyDigest, + * we need to decrypt it with the bulk encryption key. Also, in this case, + * there should be NO authenticatedAttributes (signerinfo->authAttr should + * be NULL). + */ +static PRBool +sec_pkcs7_verify_signature(SEC_PKCS7ContentInfo *cinfo, + SECCertUsage certusage, + const SECItem *detached_digest, + HASH_HashType digest_type, + PRBool keepcerts, + const PRTime *atTime) +{ + SECAlgorithmID **digestalgs, *bulkid; + const SECItem *digest; + SECItem **digests; + SECItem **rawcerts; + SEC_PKCS7SignerInfo **signerinfos, *signerinfo; + CERTCertificate *cert, **certs; + PRBool goodsig; + CERTCertDBHandle *certdb, *defaultdb; + SECOidTag encTag, digestTag; + HASH_HashType found_type; + int i, certcount; + SECKEYPublicKey *publickey; + SECItem *content_type; + PK11SymKey *sigkey; + SECItem *encoded_stime; + PRTime stime; + PRTime verificationTime; + SECStatus rv; + + /* + * Everything needed in order to "goto done" safely. + */ + goodsig = PR_FALSE; + certcount = 0; + cert = NULL; + certs = NULL; + certdb = NULL; + defaultdb = CERT_GetDefaultCertDB(); + publickey = NULL; + + if (!SEC_PKCS7ContentIsSigned(cinfo)) { + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + goto done; + } + + PORT_Assert(cinfo->contentTypeTag != NULL); + + switch (cinfo->contentTypeTag->offset) { + default: + case SEC_OID_PKCS7_DATA: + case SEC_OID_PKCS7_DIGESTED_DATA: + case SEC_OID_PKCS7_ENVELOPED_DATA: + case SEC_OID_PKCS7_ENCRYPTED_DATA: + /* Could only get here if SEC_PKCS7ContentIsSigned is broken. */ + PORT_Assert(0); + case SEC_OID_PKCS7_SIGNED_DATA: { + SEC_PKCS7SignedData *sdp; + + sdp = cinfo->content.signedData; + digestalgs = sdp->digestAlgorithms; + digests = sdp->digests; + rawcerts = sdp->rawCerts; + signerinfos = sdp->signerInfos; + content_type = &(sdp->contentInfo.contentType); + sigkey = NULL; + bulkid = NULL; + } break; + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: { + SEC_PKCS7SignedAndEnvelopedData *saedp; + + saedp = cinfo->content.signedAndEnvelopedData; + digestalgs = saedp->digestAlgorithms; + digests = saedp->digests; + rawcerts = saedp->rawCerts; + signerinfos = saedp->signerInfos; + content_type = &(saedp->encContentInfo.contentType); + sigkey = saedp->sigKey; + bulkid = &(saedp->encContentInfo.contentEncAlg); + } break; + } + + if ((signerinfos == NULL) || (signerinfos[0] == NULL)) { + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + goto done; + } + + /* + * XXX Need to handle multiple signatures; checking them is easy, + * but what should be the semantics here (like, return value)? + */ + if (signerinfos[1] != NULL) { + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + goto done; + } + + signerinfo = signerinfos[0]; + + /* + * XXX I would like to just pass the issuerAndSN, along with the rawcerts + * and crls, to some function that did all of this certificate stuff + * (open/close the database if necessary, verifying the certs, etc.) + * and gave me back a cert pointer if all was good. + */ + certdb = defaultdb; + if (certdb == NULL) { + goto done; + } + + certcount = 0; + if (rawcerts != NULL) { + for (; rawcerts[certcount] != NULL; certcount++) { + /* just counting */ + } + } + + /* + * Note that the result of this is that each cert in "certs" + * needs to be destroyed. + */ + rv = CERT_ImportCerts(certdb, certusage, certcount, rawcerts, &certs, + keepcerts, PR_FALSE, NULL); + if (rv != SECSuccess) { + goto done; + } + + /* + * This cert will also need to be freed, but since we save it + * in signerinfo for later, we do not want to destroy it when + * we leave this function -- we let the clean-up of the entire + * cinfo structure later do the destroy of this cert. + */ + cert = CERT_FindCertByIssuerAndSN(certdb, signerinfo->issuerAndSN); + if (cert == NULL) { + goto done; + } + + signerinfo->cert = cert; + + /* + * Get and convert the signing time; if available, it will be used + * both on the cert verification and for importing the sender + * email profile. + */ + encoded_stime = SEC_PKCS7GetSigningTime(cinfo); + if (encoded_stime != NULL) { + if (DER_DecodeTimeChoice(&stime, encoded_stime) != SECSuccess) + encoded_stime = NULL; /* conversion failed, so pretend none */ + } + + /* + * XXX This uses the signing time, if available. Additionally, we + * might want to, if there is no signing time, get the message time + * from the mail header itself, and use that. That would require + * a change to our interface though, and for S/MIME callers to pass + * in a time (and for non-S/MIME callers to pass in nothing, or + * maybe make them pass in the current time, always?). + */ + if (atTime) { + verificationTime = *atTime; + } else if (encoded_stime != NULL) { + verificationTime = stime; + } else { + verificationTime = PR_Now(); + } + if (CERT_VerifyCert(certdb, cert, PR_TRUE, certusage, verificationTime, + cinfo->pwfn_arg, NULL) != SECSuccess) { + /* + * XXX Give the user an option to check the signature anyway? + * If we want to do this, need to give a way to leave and display + * some dialog and get the answer and come back through (or do + * the rest of what we do below elsewhere, maybe by putting it + * in a function that we call below and could call from a dialog + * finish handler). + */ + goto savecert; + } + + publickey = CERT_ExtractPublicKey(cert); + if (publickey == NULL) + goto done; + + /* + * XXX No! If digests is empty, see if we can create it now by + * digesting the contents. This is necessary if we want to allow + * somebody to do a simple decode (without filtering, etc.) and + * then later call us here to do the verification. + * OR, we can just specify that the interface to this routine + * *requires* that the digest(s) be done before calling and either + * stashed in the struct itself or passed in explicitly (as would + * be done for detached contents). + */ + if ((digests == NULL || digests[0] == NULL) && (detached_digest == NULL || detached_digest->data == NULL)) + goto done; + + /* + * Find and confirm digest algorithm. + */ + digestTag = SECOID_FindOIDTag(&(signerinfo->digestAlg.algorithm)); + + /* make sure we understand the digest type first */ + found_type = HASH_GetHashTypeByOidTag(digestTag); + if ((digestTag == SEC_OID_UNKNOWN) || (found_type == HASH_AlgNULL)) { + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + goto done; + } + + if (detached_digest != NULL) { + unsigned int hashLen = HASH_ResultLen(found_type); + + if (digest_type != found_type || + detached_digest->len != hashLen) { + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + goto done; + } + digest = detached_digest; + } else { + PORT_Assert(digestalgs != NULL && digestalgs[0] != NULL); + if (digestalgs == NULL || digestalgs[0] == NULL) { + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + goto done; + } + + /* + * pick digest matching signerinfo->digestAlg from digests + */ + for (i = 0; digestalgs[i] != NULL; i++) { + if (SECOID_FindOIDTag(&(digestalgs[i]->algorithm)) == digestTag) + break; + } + if (digestalgs[i] == NULL) { + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + goto done; + } + + digest = digests[i]; + } + + encTag = SECOID_FindOIDTag(&(signerinfo->digestEncAlg.algorithm)); + if (encTag == SEC_OID_UNKNOWN) { + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + goto done; + } + + if (signerinfo->authAttr != NULL) { + SEC_PKCS7Attribute *attr; + SECItem *value; + SECItem encoded_attrs; + + /* + * We have a sigkey only for signedAndEnvelopedData, which is + * not supposed to have any authenticated attributes. + */ + if (sigkey != NULL) { + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + goto done; + } + + /* + * PKCS #7 says that if there are any authenticated attributes, + * then there must be one for content type which matches the + * content type of the content being signed, and there must + * be one for message digest which matches our message digest. + * So check these things first. + * XXX Might be nice to have a compare-attribute-value function + * which could collapse the following nicely. + */ + attr = sec_PKCS7FindAttribute(signerinfo->authAttr, + SEC_OID_PKCS9_CONTENT_TYPE, PR_TRUE); + value = sec_PKCS7AttributeValue(attr); + if (value == NULL || value->len != content_type->len) { + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + goto done; + } + if (PORT_Memcmp(value->data, content_type->data, value->len) != 0) { + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + goto done; + } + + attr = sec_PKCS7FindAttribute(signerinfo->authAttr, + SEC_OID_PKCS9_MESSAGE_DIGEST, PR_TRUE); + value = sec_PKCS7AttributeValue(attr); + if (value == NULL || value->len != digest->len) { + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + goto done; + } + if (PORT_Memcmp(value->data, digest->data, value->len) != 0) { + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + goto done; + } + + /* + * Okay, we met the constraints of the basic attributes. + * Now check the signature, which is based on a digest of + * the DER-encoded authenticated attributes. So, first we + * encode and then we digest/verify. + */ + encoded_attrs.data = NULL; + encoded_attrs.len = 0; + if (sec_PKCS7EncodeAttributes(NULL, &encoded_attrs, + &(signerinfo->authAttr)) == NULL) + goto done; + + if (encoded_attrs.data == NULL || encoded_attrs.len == 0) { + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + goto done; + } + + goodsig = (PRBool)(VFY_VerifyDataDirect(encoded_attrs.data, + encoded_attrs.len, + publickey, &(signerinfo->encDigest), + encTag, digestTag, NULL, + cinfo->pwfn_arg) == SECSuccess); + PORT_Free(encoded_attrs.data); + } else { + SECItem *sig; + SECItem holder; + + /* + * No authenticated attributes. + * The signature is based on the plain message digest. + */ + + sig = &(signerinfo->encDigest); + if (sig->len == 0) { /* bad signature */ + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + goto done; + } + + if (sigkey != NULL) { + sec_PKCS7CipherObject *decryptobj; + unsigned int buflen; + + /* + * For signedAndEnvelopedData, we first must decrypt the encrypted + * digest with the bulk encryption key. The result is the normal + * encrypted digest (aka the signature). + */ + decryptobj = sec_PKCS7CreateDecryptObject(sigkey, bulkid); + if (decryptobj == NULL) + goto done; + + buflen = sec_PKCS7DecryptLength(decryptobj, sig->len, PR_TRUE); + PORT_Assert(buflen); + if (buflen == 0) { /* something is wrong */ + sec_PKCS7DestroyDecryptObject(decryptobj); + goto done; + } + + holder.data = (unsigned char *)PORT_Alloc(buflen); + if (holder.data == NULL) { + sec_PKCS7DestroyDecryptObject(decryptobj); + goto done; + } + + rv = sec_PKCS7Decrypt(decryptobj, holder.data, &holder.len, buflen, + sig->data, sig->len, PR_TRUE); + sec_PKCS7DestroyDecryptObject(decryptobj); + if (rv != SECSuccess) { + goto done; + } + + sig = &holder; + } + + goodsig = (PRBool)(VFY_VerifyDigestDirect(digest, publickey, sig, + encTag, digestTag, cinfo->pwfn_arg) == SECSuccess); + + if (sigkey != NULL) { + PORT_Assert(sig == &holder); + PORT_ZFree(holder.data, holder.len); + } + } + + if (!goodsig) { + /* + * XXX Change the generic error into our specific one, because + * in that case we get a better explanation out of the Security + * Advisor. This is really a bug in our error strings (the + * "generic" error has a lousy/wrong message associated with it + * which assumes the signature verification was done for the + * purposes of checking the issuer signature on a certificate) + * but this is at least an easy workaround and/or in the + * Security Advisor, which specifically checks for the error + * SEC_ERROR_PKCS7_BAD_SIGNATURE and gives more explanation + * in that case but does not similarly check for + * SEC_ERROR_BAD_SIGNATURE. It probably should, but then would + * probably say the wrong thing in the case that it *was* the + * certificate signature check that failed during the cert + * verification done above. Our error handling is really a mess. + */ + if (PORT_GetError() == SEC_ERROR_BAD_SIGNATURE) + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + } + +savecert: + /* + * Only save the smime profile if we are checking an email message and + * the cert has an email address in it. + */ + if (cert->emailAddr && cert->emailAddr[0] && + ((certusage == certUsageEmailSigner) || + (certusage == certUsageEmailRecipient))) { + SECItem *profile = NULL; + int save_error; + + /* + * Remember the current error set because we do not care about + * anything set by the functions we are about to call. + */ + save_error = PORT_GetError(); + + if (goodsig && (signerinfo->authAttr != NULL)) { + /* + * If the signature is good, then we can save the S/MIME profile, + * if we have one. + */ + SEC_PKCS7Attribute *attr; + + attr = sec_PKCS7FindAttribute(signerinfo->authAttr, + SEC_OID_PKCS9_SMIME_CAPABILITIES, + PR_TRUE); + profile = sec_PKCS7AttributeValue(attr); + } + + rv = CERT_SaveSMimeProfile(cert, profile, encoded_stime); + + /* + * Restore the saved error in case the calls above set a new + * one that we do not actually care about. + */ + PORT_SetError(save_error); + + /* + * XXX Failure is not indicated anywhere -- the signature + * verification itself is unaffected by whether or not the + * profile was successfully saved. + */ + } + +done: + + /* + * See comment above about why we do not want to destroy cert + * itself here. + */ + + if (certs != NULL) + CERT_DestroyCertArray(certs, certcount); + + if (publickey != NULL) + SECKEY_DestroyPublicKey(publickey); + + return goodsig; +} + +/* + * SEC_PKCS7VerifySignature + * Look at a PKCS7 contentInfo and check if the signature is good. + * The verification checks that the signing cert is valid and trusted + * for the purpose specified by "certusage". + * + * In addition, if "keepcerts" is true, add any new certificates found + * into our local database. + */ +PRBool +SEC_PKCS7VerifySignature(SEC_PKCS7ContentInfo *cinfo, + SECCertUsage certusage, + PRBool keepcerts) +{ + return sec_pkcs7_verify_signature(cinfo, certusage, + NULL, HASH_AlgNULL, keepcerts, NULL); +} + +/* + * SEC_PKCS7VerifyDetachedSignature + * Look at a PKCS7 contentInfo and check if the signature matches + * a passed-in digest (calculated, supposedly, from detached contents). + * The verification checks that the signing cert is valid and trusted + * for the purpose specified by "certusage". + * + * In addition, if "keepcerts" is true, add any new certificates found + * into our local database. + */ +PRBool +SEC_PKCS7VerifyDetachedSignature(SEC_PKCS7ContentInfo *cinfo, + SECCertUsage certusage, + const SECItem *detached_digest, + HASH_HashType digest_type, + PRBool keepcerts) +{ + return sec_pkcs7_verify_signature(cinfo, certusage, + detached_digest, digest_type, + keepcerts, NULL); +} + +/* + * SEC_PKCS7VerifyDetachedSignatureAtTime + * Look at a PKCS7 contentInfo and check if the signature matches + * a passed-in digest (calculated, supposedly, from detached contents). + * The verification checks that the signing cert is valid and trusted + * for the purpose specified by "certusage" at time "atTime". + * + * In addition, if "keepcerts" is true, add any new certificates found + * into our local database. + */ +PRBool +SEC_PKCS7VerifyDetachedSignatureAtTime(SEC_PKCS7ContentInfo *cinfo, + SECCertUsage certusage, + const SECItem *detached_digest, + HASH_HashType digest_type, + PRBool keepcerts, + PRTime atTime) +{ + return sec_pkcs7_verify_signature(cinfo, certusage, + detached_digest, digest_type, + keepcerts, &atTime); +} + +/* + * Return the asked-for portion of the name of the signer of a PKCS7 + * signed object. + * + * Returns a pointer to allocated memory, which must be freed. + * A NULL return value is an error. + */ + +#define sec_common_name 1 +#define sec_email_address 2 + +static char * +sec_pkcs7_get_signer_cert_info(SEC_PKCS7ContentInfo *cinfo, int selector) +{ + SECOidTag kind; + SEC_PKCS7SignerInfo **signerinfos; + CERTCertificate *signercert; + char *container; + + kind = SEC_PKCS7ContentType(cinfo); + switch (kind) { + default: + case SEC_OID_PKCS7_DATA: + case SEC_OID_PKCS7_DIGESTED_DATA: + case SEC_OID_PKCS7_ENVELOPED_DATA: + case SEC_OID_PKCS7_ENCRYPTED_DATA: + PORT_Assert(0); + return NULL; + case SEC_OID_PKCS7_SIGNED_DATA: { + SEC_PKCS7SignedData *sdp; + + sdp = cinfo->content.signedData; + signerinfos = sdp->signerInfos; + } break; + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: { + SEC_PKCS7SignedAndEnvelopedData *saedp; + + saedp = cinfo->content.signedAndEnvelopedData; + signerinfos = saedp->signerInfos; + } break; + } + + if (signerinfos == NULL || signerinfos[0] == NULL) + return NULL; + + signercert = signerinfos[0]->cert; + + /* + * No cert there; see if we can find one by calling verify ourselves. + */ + if (signercert == NULL) { + /* + * The cert usage does not matter in this case, because we do not + * actually care about the verification itself, but we have to pick + * some valid usage to pass in. + */ + (void)sec_pkcs7_verify_signature(cinfo, certUsageEmailSigner, + NULL, HASH_AlgNULL, PR_FALSE, NULL); + signercert = signerinfos[0]->cert; + if (signercert == NULL) + return NULL; + } + + switch (selector) { + case sec_common_name: + container = CERT_GetCommonName(&signercert->subject); + break; + case sec_email_address: + if (signercert->emailAddr && signercert->emailAddr[0]) { + container = PORT_Strdup(signercert->emailAddr); + } else { + container = NULL; + } + break; + default: + PORT_Assert(0); + container = NULL; + break; + } + + return container; +} + +char * +SEC_PKCS7GetSignerCommonName(SEC_PKCS7ContentInfo *cinfo) +{ + return sec_pkcs7_get_signer_cert_info(cinfo, sec_common_name); +} + +char * +SEC_PKCS7GetSignerEmailAddress(SEC_PKCS7ContentInfo *cinfo) +{ + return sec_pkcs7_get_signer_cert_info(cinfo, sec_email_address); +} + +/* + * Return the signing time, in UTCTime format, of a PKCS7 contentInfo. + */ +SECItem * +SEC_PKCS7GetSigningTime(SEC_PKCS7ContentInfo *cinfo) +{ + SEC_PKCS7SignerInfo **signerinfos; + SEC_PKCS7Attribute *attr; + + if (SEC_PKCS7ContentType(cinfo) != SEC_OID_PKCS7_SIGNED_DATA) + return NULL; + + signerinfos = cinfo->content.signedData->signerInfos; + + /* + * No signature, or more than one, means no deal. + */ + if (signerinfos == NULL || signerinfos[0] == NULL || signerinfos[1] != NULL) + return NULL; + + attr = sec_PKCS7FindAttribute(signerinfos[0]->authAttr, + SEC_OID_PKCS9_SIGNING_TIME, PR_TRUE); + return sec_PKCS7AttributeValue(attr); +} diff --git a/security/nss/lib/pkcs7/p7encode.c b/security/nss/lib/pkcs7/p7encode.c new file mode 100644 index 0000000000..739e25f812 --- /dev/null +++ b/security/nss/lib/pkcs7/p7encode.c @@ -0,0 +1,1079 @@ +/* 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/. */ + +/* + * PKCS7 encoding. + */ + +#include "p7local.h" + +#include "cert.h" +#include "cryptohi.h" +#include "keyhi.h" +#include "secasn1.h" +#include "secoid.h" +#include "secitem.h" +#include "pk11func.h" +#include "secerr.h" +#include "sechash.h" /* for HASH_GetHashObject() */ + +struct sec_pkcs7_encoder_output { + SEC_PKCS7EncoderOutputCallback outputfn; + void *outputarg; +}; + +struct SEC_PKCS7EncoderContextStr { + SEC_ASN1EncoderContext *ecx; + SEC_PKCS7ContentInfo *cinfo; + struct sec_pkcs7_encoder_output output; + sec_PKCS7CipherObject *encryptobj; + const SECHashObject *digestobj; + void *digestcx; +}; + +/* + * The little output function that the ASN.1 encoder calls to hand + * us bytes which we in turn hand back to our caller (via the callback + * they gave us). + */ +static void +sec_pkcs7_encoder_out(void *arg, const char *buf, unsigned long len, + int depth, SEC_ASN1EncodingPart data_kind) +{ + struct sec_pkcs7_encoder_output *output; + + output = (struct sec_pkcs7_encoder_output *)arg; + output->outputfn(output->outputarg, buf, len); +} + +static sec_PKCS7CipherObject * +sec_pkcs7_encoder_start_encrypt(SEC_PKCS7ContentInfo *cinfo, + PK11SymKey *orig_bulkkey) +{ + SECOidTag kind; + sec_PKCS7CipherObject *encryptobj; + SEC_PKCS7RecipientInfo **recipientinfos, *ri; + SEC_PKCS7EncryptedContentInfo *enccinfo; + SECKEYPublicKey *publickey = NULL; + SECKEYPrivateKey *ourPrivKey = NULL; + PK11SymKey *bulkkey; + void *mark; + int i; + PLArenaPool *arena = NULL; + + kind = SEC_PKCS7ContentType(cinfo); + switch (kind) { + default: + case SEC_OID_PKCS7_DATA: + case SEC_OID_PKCS7_DIGESTED_DATA: + case SEC_OID_PKCS7_SIGNED_DATA: + recipientinfos = NULL; + enccinfo = NULL; + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: { + SEC_PKCS7EncryptedData *encdp; + + /* To do EncryptedData we *must* be given a bulk key. */ + PORT_Assert(orig_bulkkey != NULL); + if (orig_bulkkey == NULL) { + /* XXX error? */ + return NULL; + } + + encdp = cinfo->content.encryptedData; + recipientinfos = NULL; + enccinfo = &(encdp->encContentInfo); + } break; + case SEC_OID_PKCS7_ENVELOPED_DATA: { + SEC_PKCS7EnvelopedData *envdp; + + envdp = cinfo->content.envelopedData; + recipientinfos = envdp->recipientInfos; + enccinfo = &(envdp->encContentInfo); + } break; + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: { + SEC_PKCS7SignedAndEnvelopedData *saedp; + + saedp = cinfo->content.signedAndEnvelopedData; + recipientinfos = saedp->recipientInfos; + enccinfo = &(saedp->encContentInfo); + } break; + } + + if (enccinfo == NULL) + return NULL; + + bulkkey = orig_bulkkey; + if (bulkkey == NULL) { + CK_MECHANISM_TYPE type = PK11_AlgtagToMechanism(enccinfo->encalg); + PK11SlotInfo *slot; + + slot = PK11_GetBestSlot(type, cinfo->pwfn_arg); + if (slot == NULL) { + return NULL; + } + bulkkey = PK11_KeyGen(slot, type, NULL, enccinfo->keysize / 8, + cinfo->pwfn_arg); + PK11_FreeSlot(slot); + if (bulkkey == NULL) { + return NULL; + } + } + + encryptobj = NULL; + mark = PORT_ArenaMark(cinfo->poolp); + + /* + * Encrypt the bulk key with the public key of each recipient. + */ + for (i = 0; recipientinfos && (ri = recipientinfos[i]) != NULL; i++) { + CERTCertificate *cert; + SECOidTag certalgtag, encalgtag; + SECStatus rv; + int data_len; + SECItem *params = NULL; + + cert = ri->cert; + PORT_Assert(cert != NULL); + if (cert == NULL) + continue; + + /* + * XXX Want an interface that takes a cert and some data and + * fills in an algorithmID and encrypts the data with the public + * key from the cert. Or, give me two interfaces -- one which + * gets the algorithm tag from a cert (I should not have to go + * down into the subjectPublicKeyInfo myself) and another which + * takes a public key and algorithm tag and data and encrypts + * the data. Or something like that. The point is that all + * of the following hardwired RSA stuff should be done elsewhere. + */ + + certalgtag = SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm)); + + switch (certalgtag) { + case SEC_OID_PKCS1_RSA_ENCRYPTION: + encalgtag = certalgtag; + publickey = CERT_ExtractPublicKey(cert); + if (publickey == NULL) + goto loser; + + data_len = SECKEY_PublicKeyStrength(publickey); + ri->encKey.data = + (unsigned char *)PORT_ArenaAlloc(cinfo->poolp, data_len); + ri->encKey.len = data_len; + if (ri->encKey.data == NULL) + goto loser; + + rv = PK11_PubWrapSymKey(PK11_AlgtagToMechanism(certalgtag), publickey, + bulkkey, &ri->encKey); + + SECKEY_DestroyPublicKey(publickey); + publickey = NULL; + if (rv != SECSuccess) + goto loser; + params = NULL; /* paranoia */ + break; + default: + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + goto loser; + } + + rv = SECOID_SetAlgorithmID(cinfo->poolp, &ri->keyEncAlg, encalgtag, + params); + if (rv != SECSuccess) + goto loser; + if (arena) + PORT_FreeArena(arena, PR_FALSE); + arena = NULL; + } + + encryptobj = sec_PKCS7CreateEncryptObject(cinfo->poolp, bulkkey, + enccinfo->encalg, + &(enccinfo->contentEncAlg)); + if (encryptobj != NULL) { + PORT_ArenaUnmark(cinfo->poolp, mark); + mark = NULL; /* good one; do not want to release */ + } + /* fallthru */ + +loser: + if (arena) { + PORT_FreeArena(arena, PR_FALSE); + } + if (publickey) { + SECKEY_DestroyPublicKey(publickey); + } + if (ourPrivKey) { + SECKEY_DestroyPrivateKey(ourPrivKey); + } + if (mark != NULL) { + PORT_ArenaRelease(cinfo->poolp, mark); + } + if (orig_bulkkey == NULL) { + if (bulkkey) + PK11_FreeSymKey(bulkkey); + } + + return encryptobj; +} + +static void +sec_pkcs7_encoder_notify(void *arg, PRBool before, void *dest, int depth) +{ + SEC_PKCS7EncoderContext *p7ecx; + SEC_PKCS7ContentInfo *cinfo; + SECOidTag kind; + PRBool before_content; + + /* + * We want to notice just before the content field. After fields are + * not interesting to us. + */ + if (!before) + return; + + p7ecx = (SEC_PKCS7EncoderContext *)arg; + cinfo = p7ecx->cinfo; + + before_content = PR_FALSE; + + /* + * Watch for the content field, at which point we want to instruct + * the ASN.1 encoder to start taking bytes from the buffer. + * + * XXX The following assumes the inner content type is data; + * if/when we want to handle fully nested types, this will have + * to recurse until reaching the innermost data content. + */ + kind = SEC_PKCS7ContentType(cinfo); + switch (kind) { + default: + case SEC_OID_PKCS7_DATA: + if (dest == &(cinfo->content.data)) + before_content = PR_TRUE; + break; + + case SEC_OID_PKCS7_DIGESTED_DATA: { + SEC_PKCS7DigestedData *digd; + + digd = cinfo->content.digestedData; + if (digd == NULL) + break; + + if (dest == &(digd->contentInfo.content)) + before_content = PR_TRUE; + } break; + + case SEC_OID_PKCS7_ENCRYPTED_DATA: { + SEC_PKCS7EncryptedData *encd; + + encd = cinfo->content.encryptedData; + if (encd == NULL) + break; + + if (dest == &(encd->encContentInfo.encContent)) + before_content = PR_TRUE; + } break; + + case SEC_OID_PKCS7_ENVELOPED_DATA: { + SEC_PKCS7EnvelopedData *envd; + + envd = cinfo->content.envelopedData; + if (envd == NULL) + break; + + if (dest == &(envd->encContentInfo.encContent)) + before_content = PR_TRUE; + } break; + + case SEC_OID_PKCS7_SIGNED_DATA: { + SEC_PKCS7SignedData *sigd; + + sigd = cinfo->content.signedData; + if (sigd == NULL) + break; + + if (dest == &(sigd->contentInfo.content)) + before_content = PR_TRUE; + } break; + + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: { + SEC_PKCS7SignedAndEnvelopedData *saed; + + saed = cinfo->content.signedAndEnvelopedData; + if (saed == NULL) + break; + + if (dest == &(saed->encContentInfo.encContent)) + before_content = PR_TRUE; + } break; + } + + if (before_content) { + /* + * This will cause the next SEC_ASN1EncoderUpdate to take the + * contents bytes from the passed-in buffer. + */ + SEC_ASN1EncoderSetTakeFromBuf(p7ecx->ecx); + /* + * And that is all we needed this notify function for. + */ + SEC_ASN1EncoderClearNotifyProc(p7ecx->ecx); + } +} + +static SEC_PKCS7EncoderContext * +sec_pkcs7_encoder_start_contexts(SEC_PKCS7ContentInfo *cinfo, + PK11SymKey *bulkkey) +{ + SEC_PKCS7EncoderContext *p7ecx; + SECOidTag kind; + PRBool encrypt; + SECItem **digests; + SECAlgorithmID *digestalg, **digestalgs; + + p7ecx = + (SEC_PKCS7EncoderContext *)PORT_ZAlloc(sizeof(SEC_PKCS7EncoderContext)); + if (p7ecx == NULL) + return NULL; + + digests = NULL; + digestalg = NULL; + digestalgs = NULL; + encrypt = PR_FALSE; + + kind = SEC_PKCS7ContentType(cinfo); + switch (kind) { + default: + case SEC_OID_PKCS7_DATA: + break; + case SEC_OID_PKCS7_DIGESTED_DATA: + digestalg = &(cinfo->content.digestedData->digestAlg); + break; + case SEC_OID_PKCS7_SIGNED_DATA: + digests = cinfo->content.signedData->digests; + digestalgs = cinfo->content.signedData->digestAlgorithms; + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + case SEC_OID_PKCS7_ENVELOPED_DATA: + encrypt = PR_TRUE; + break; + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: + digests = cinfo->content.signedAndEnvelopedData->digests; + digestalgs = cinfo->content.signedAndEnvelopedData->digestAlgorithms; + encrypt = PR_TRUE; + break; + } + + if (encrypt) { + p7ecx->encryptobj = sec_pkcs7_encoder_start_encrypt(cinfo, bulkkey); + if (p7ecx->encryptobj == NULL) { + PORT_Free(p7ecx); + return NULL; + } + } + + if (digestalgs != NULL) { + if (digests != NULL) { + /* digests already created (probably for detached data) */ + digestalg = NULL; + } else { + /* + * XXX Some day we should handle multiple digests; for now, + * assume only one will be done. + */ + PORT_Assert(digestalgs[0] != NULL && digestalgs[1] == NULL); + digestalg = digestalgs[0]; + } + } + + if (digestalg != NULL) { + SECOidTag oidTag = SECOID_FindOIDTag(&(digestalg->algorithm)); + + p7ecx->digestobj = HASH_GetHashObjectByOidTag(oidTag); + if (p7ecx->digestobj != NULL) { + p7ecx->digestcx = (*p7ecx->digestobj->create)(); + if (p7ecx->digestcx == NULL) + p7ecx->digestobj = NULL; + else + (*p7ecx->digestobj->begin)(p7ecx->digestcx); + } + if (p7ecx->digestobj == NULL) { + if (p7ecx->encryptobj != NULL) + sec_PKCS7DestroyEncryptObject(p7ecx->encryptobj); + PORT_Free(p7ecx); + return NULL; + } + } + + p7ecx->cinfo = cinfo; + return p7ecx; +} + +SEC_PKCS7EncoderContext * +SEC_PKCS7EncoderStart(SEC_PKCS7ContentInfo *cinfo, + SEC_PKCS7EncoderOutputCallback outputfn, + void *outputarg, + PK11SymKey *bulkkey) +{ + SEC_PKCS7EncoderContext *p7ecx; + SECStatus rv; + + p7ecx = sec_pkcs7_encoder_start_contexts(cinfo, bulkkey); + if (p7ecx == NULL) + return NULL; + + p7ecx->output.outputfn = outputfn; + p7ecx->output.outputarg = outputarg; + + /* + * Initialize the BER encoder. + */ + p7ecx->ecx = SEC_ASN1EncoderStart(cinfo, sec_PKCS7ContentInfoTemplate, + sec_pkcs7_encoder_out, &(p7ecx->output)); + if (p7ecx->ecx == NULL) { + PORT_Free(p7ecx); + return NULL; + } + + /* + * Indicate that we are streaming. We will be streaming until we + * get past the contents bytes. + */ + SEC_ASN1EncoderSetStreaming(p7ecx->ecx); + + /* + * The notify function will watch for the contents field. + */ + SEC_ASN1EncoderSetNotifyProc(p7ecx->ecx, sec_pkcs7_encoder_notify, p7ecx); + + /* + * This will encode everything up to the content bytes. (The notify + * function will then cause the encoding to stop there.) Then our + * caller can start passing contents bytes to our Update, which we + * will pass along. + */ + rv = SEC_ASN1EncoderUpdate(p7ecx->ecx, NULL, 0); + if (rv != SECSuccess) { + PORT_Free(p7ecx); + return NULL; + } + + return p7ecx; +} + +/* + * XXX If/when we support nested contents, this needs to be revised. + */ +static SECStatus +sec_pkcs7_encoder_work_data(SEC_PKCS7EncoderContext *p7ecx, SECItem *dest, + const unsigned char *data, unsigned long len, + PRBool final) +{ + unsigned char *buf = NULL; + SECStatus rv; + + rv = SECSuccess; /* may as well be optimistic */ + + /* + * We should really have data to process, or we should be trying + * to finish/flush the last block. (This is an overly paranoid + * check since all callers are in this file and simple inspection + * proves they do it right. But it could find a bug in future + * modifications/development, that is why it is here.) + */ + PORT_Assert((data != NULL && len) || final); + + /* + * Update the running digest. + * XXX This needs modification if/when we handle multiple digests. + */ + if (len && p7ecx->digestobj != NULL) { + (*p7ecx->digestobj->update)(p7ecx->digestcx, data, len); + } + + /* + * Encrypt this chunk. + */ + if (p7ecx->encryptobj != NULL) { + /* XXX the following lengths should all be longs? */ + unsigned int inlen; /* length of data being encrypted */ + unsigned int outlen; /* length of encrypted data */ + unsigned int buflen; /* length available for encrypted data */ + + inlen = len; + buflen = sec_PKCS7EncryptLength(p7ecx->encryptobj, inlen, final); + if (buflen == 0) { + /* + * No output is expected, but the input data may be buffered + * so we still have to call Encrypt. + */ + rv = sec_PKCS7Encrypt(p7ecx->encryptobj, NULL, &outlen, 0, + data, inlen, final); + if (final) { + len = 0; + goto done; + } + return rv; + } + + if (dest != NULL) + buf = (unsigned char *)PORT_ArenaAlloc(p7ecx->cinfo->poolp, buflen); + else + buf = (unsigned char *)PORT_Alloc(buflen); + + if (buf == NULL) { + rv = SECFailure; + } else { + rv = sec_PKCS7Encrypt(p7ecx->encryptobj, buf, &outlen, buflen, + data, inlen, final); + data = buf; + len = outlen; + } + if (rv != SECSuccess) { + if (final) + goto done; + return rv; + } + } + + if (p7ecx->ecx != NULL) { + /* + * Encode the contents bytes. + */ + if (len) { + rv = SEC_ASN1EncoderUpdate(p7ecx->ecx, (const char *)data, len); + } + } + +done: + if (p7ecx->encryptobj != NULL) { + if (final) + sec_PKCS7DestroyEncryptObject(p7ecx->encryptobj); + if (dest != NULL) { + dest->data = buf; + dest->len = len; + } else if (buf != NULL) { + PORT_Free(buf); + } + } + + if (final && p7ecx->digestobj != NULL) { + SECItem *digest, **digests, ***digestsp; + unsigned char *digdata; + SECOidTag kind; + + kind = SEC_PKCS7ContentType(p7ecx->cinfo); + switch (kind) { + default: + PORT_Assert(0); + return SECFailure; + case SEC_OID_PKCS7_DIGESTED_DATA: + digest = &(p7ecx->cinfo->content.digestedData->digest); + digestsp = NULL; + break; + case SEC_OID_PKCS7_SIGNED_DATA: + digest = NULL; + digestsp = &(p7ecx->cinfo->content.signedData->digests); + break; + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: + digest = NULL; + digestsp = &(p7ecx->cinfo->content.signedAndEnvelopedData->digests); + break; + } + + digdata = (unsigned char *)PORT_ArenaAlloc(p7ecx->cinfo->poolp, + p7ecx->digestobj->length); + if (digdata == NULL) + return SECFailure; + + if (digestsp != NULL) { + PORT_Assert(digest == NULL); + + digest = (SECItem *)PORT_ArenaAlloc(p7ecx->cinfo->poolp, + sizeof(SECItem)); + digests = (SECItem **)PORT_ArenaAlloc(p7ecx->cinfo->poolp, + 2 * sizeof(SECItem *)); + if (digests == NULL || digest == NULL) + return SECFailure; + + digests[0] = digest; + digests[1] = NULL; + + *digestsp = digests; + } + + PORT_Assert(digest != NULL); + + digest->data = digdata; + digest->len = p7ecx->digestobj->length; + + (*p7ecx->digestobj->end)(p7ecx->digestcx, digest->data, + &(digest->len), digest->len); + (*p7ecx->digestobj->destroy)(p7ecx->digestcx, PR_TRUE); + } + + return rv; +} + +SECStatus +SEC_PKCS7EncoderUpdate(SEC_PKCS7EncoderContext *p7ecx, + const char *data, unsigned long len) +{ + /* XXX Error handling needs help. Return what? Do "Finish" on failure? */ + return sec_pkcs7_encoder_work_data(p7ecx, NULL, + (const unsigned char *)data, len, + PR_FALSE); +} + +static SECStatus +sec_pkcs7_encoder_sig_and_certs(SEC_PKCS7ContentInfo *cinfo, + SECKEYGetPasswordKey pwfn, void *pwfnarg) +{ + SECOidTag kind; + CERTCertificate **certs; + CERTCertificateList **certlists; + SECAlgorithmID **digestalgs; + SECItem **digests; + SEC_PKCS7SignerInfo *signerinfo, **signerinfos; + SECItem **rawcerts, ***rawcertsp; + PLArenaPool *poolp; + int certcount; + int ci, cli, rci, si; + + kind = SEC_PKCS7ContentType(cinfo); + switch (kind) { + default: + case SEC_OID_PKCS7_DATA: + case SEC_OID_PKCS7_DIGESTED_DATA: + case SEC_OID_PKCS7_ENCRYPTED_DATA: + case SEC_OID_PKCS7_ENVELOPED_DATA: + certs = NULL; + certlists = NULL; + digestalgs = NULL; + digests = NULL; + signerinfos = NULL; + rawcertsp = NULL; + break; + case SEC_OID_PKCS7_SIGNED_DATA: { + SEC_PKCS7SignedData *sdp; + + sdp = cinfo->content.signedData; + certs = sdp->certs; + certlists = sdp->certLists; + digestalgs = sdp->digestAlgorithms; + digests = sdp->digests; + signerinfos = sdp->signerInfos; + rawcertsp = &(sdp->rawCerts); + } break; + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: { + SEC_PKCS7SignedAndEnvelopedData *saedp; + + saedp = cinfo->content.signedAndEnvelopedData; + certs = saedp->certs; + certlists = saedp->certLists; + digestalgs = saedp->digestAlgorithms; + digests = saedp->digests; + signerinfos = saedp->signerInfos; + rawcertsp = &(saedp->rawCerts); + } break; + } + + if (certs == NULL && certlists == NULL && signerinfos == NULL) + return SECSuccess; /* nothing for us to do! */ + + poolp = cinfo->poolp; + certcount = 0; + + if (signerinfos != NULL) { + SECOidTag digestalgtag; + int di; + SECStatus rv; + CERTCertificate *cert; + SECKEYPrivateKey *privkey; + SECItem signature; + SECOidTag signalgtag; + + PORT_Assert(digestalgs != NULL && digests != NULL); + + /* + * If one fails, we bail right then. If we want to continue and + * try to do subsequent signatures, this loop, and the departures + * from it, will need to be reworked. + */ + for (si = 0; signerinfos[si] != NULL; si++) { + + signerinfo = signerinfos[si]; + + /* find right digest */ + digestalgtag = SECOID_GetAlgorithmTag(&(signerinfo->digestAlg)); + for (di = 0; digestalgs[di] != NULL; di++) { + /* XXX Should I be comparing more than the tag? */ + if (digestalgtag == SECOID_GetAlgorithmTag(digestalgs[di])) + break; + } + if (digestalgs[di] == NULL) { + /* XXX oops; do what? set an error? */ + return SECFailure; + } + PORT_Assert(digests[di] != NULL); + + cert = signerinfo->cert; + privkey = PK11_FindKeyByAnyCert(cert, pwfnarg); + if (privkey == NULL) + return SECFailure; + + /* + * XXX I think there should be a cert-level interface for this, + * so that I do not have to know about subjectPublicKeyInfo... + */ + signalgtag = SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm)); + + if (signerinfo->authAttr != NULL) { + SEC_PKCS7Attribute *attr; + SECItem encoded_attrs; + SECItem *dummy; + SECOidTag algid; + + /* + * First, find and fill in the message digest attribute. + */ + attr = sec_PKCS7FindAttribute(signerinfo->authAttr, + SEC_OID_PKCS9_MESSAGE_DIGEST, + PR_TRUE); + PORT_Assert(attr != NULL); + if (attr == NULL) { + SECKEY_DestroyPrivateKey(privkey); + return SECFailure; + } + + /* + * XXX The second half of the following assertion prevents + * the encoder from being called twice on the same content. + * Either just remove the second half the assertion, or + * change the code to check if the value already there is + * the same as digests[di], whichever seems more right. + */ + PORT_Assert(attr->values != NULL && attr->values[0] == NULL); + attr->values[0] = digests[di]; + + /* + * Before encoding, reorder the attributes so that when they + * are encoded, they will be conforming DER, which is required + * to have a specific order and that is what must be used for + * the hash/signature. We do this here, rather than building + * it into EncodeAttributes, because we do not want to do + * such reordering on incoming messages (which also uses + * EncodeAttributes) or our old signatures (and other "broken" + * implementations) will not verify. So, we want to guarantee + * that we send out good DER encodings of attributes, but not + * to expect to receive them. + */ + rv = sec_PKCS7ReorderAttributes(signerinfo->authAttr); + if (rv != SECSuccess) { + SECKEY_DestroyPrivateKey(privkey); + return SECFailure; + } + + encoded_attrs.data = NULL; + encoded_attrs.len = 0; + dummy = sec_PKCS7EncodeAttributes(NULL, &encoded_attrs, + &(signerinfo->authAttr)); + if (dummy == NULL) { + SECKEY_DestroyPrivateKey(privkey); + return SECFailure; + } + + algid = SEC_GetSignatureAlgorithmOidTag(privkey->keyType, + digestalgtag); + if (algid == SEC_OID_UNKNOWN) { + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + SECKEY_DestroyPrivateKey(privkey); + return SECFailure; + } + rv = SEC_SignData(&signature, + encoded_attrs.data, encoded_attrs.len, + privkey, + algid); + SECITEM_FreeItem(&encoded_attrs, PR_FALSE); + } else { + rv = SGN_Digest(privkey, digestalgtag, &signature, + digests[di]); + } + + SECKEY_DestroyPrivateKey(privkey); + + if (rv != SECSuccess) + return rv; + + rv = SECITEM_CopyItem(poolp, &(signerinfo->encDigest), &signature); + if (rv != SECSuccess) + return rv; + + SECITEM_FreeItem(&signature, PR_FALSE); + + rv = SECOID_SetAlgorithmID(poolp, &(signerinfo->digestEncAlg), + signalgtag, NULL); + if (rv != SECSuccess) + return SECFailure; + + /* + * Count the cert chain for this signer. + */ + if (signerinfo->certList != NULL) + certcount += signerinfo->certList->len; + } + } + + if (certs != NULL) { + for (ci = 0; certs[ci] != NULL; ci++) + certcount++; + } + + if (certlists != NULL) { + for (cli = 0; certlists[cli] != NULL; cli++) + certcount += certlists[cli]->len; + } + + if (certcount == 0) + return SECSuccess; /* signing done; no certs */ + + /* + * Combine all of the certs and cert chains into rawcerts. + * Note: certcount is an upper bound; we may not need that many slots + * but we will allocate anyway to avoid having to do another pass. + * (The temporary space saving is not worth it.) + */ + rawcerts = (SECItem **)PORT_ArenaAlloc(poolp, + (certcount + 1) * sizeof(SECItem *)); + if (rawcerts == NULL) + return SECFailure; + + /* + * XXX Want to check for duplicates and not add *any* cert that is + * already in the set. This will be more important when we start + * dealing with larger sets of certs, dual-key certs (signing and + * encryption), etc. For the time being we can slide by... + */ + rci = 0; + if (signerinfos != NULL) { + for (si = 0; signerinfos[si] != NULL; si++) { + signerinfo = signerinfos[si]; + for (ci = 0; ci < signerinfo->certList->len; ci++) + rawcerts[rci++] = &(signerinfo->certList->certs[ci]); + } + } + + if (certs != NULL) { + for (ci = 0; certs[ci] != NULL; ci++) + rawcerts[rci++] = &(certs[ci]->derCert); + } + + if (certlists != NULL) { + for (cli = 0; certlists[cli] != NULL; cli++) { + for (ci = 0; ci < certlists[cli]->len; ci++) + rawcerts[rci++] = &(certlists[cli]->certs[ci]); + } + } + + rawcerts[rci] = NULL; + *rawcertsp = rawcerts; + + return SECSuccess; +} + +SECStatus +SEC_PKCS7EncoderFinish(SEC_PKCS7EncoderContext *p7ecx, + SECKEYGetPasswordKey pwfn, void *pwfnarg) +{ + SECStatus rv; + + /* + * Flush out any remaining data. + */ + rv = sec_pkcs7_encoder_work_data(p7ecx, NULL, NULL, 0, PR_TRUE); + + /* + * Turn off streaming stuff. + */ + SEC_ASN1EncoderClearTakeFromBuf(p7ecx->ecx); + SEC_ASN1EncoderClearStreaming(p7ecx->ecx); + + if (rv != SECSuccess) + goto loser; + + rv = sec_pkcs7_encoder_sig_and_certs(p7ecx->cinfo, pwfn, pwfnarg); + if (rv != SECSuccess) + goto loser; + + rv = SEC_ASN1EncoderUpdate(p7ecx->ecx, NULL, 0); + +loser: + SEC_ASN1EncoderFinish(p7ecx->ecx); + PORT_Free(p7ecx); + return rv; +} + +/* + * Abort the ASN.1 stream. Used by pkcs 12 + */ +void +SEC_PKCS7EncoderAbort(SEC_PKCS7EncoderContext *p7ecx, int error) +{ + PORT_Assert(p7ecx); + SEC_ASN1EncoderAbort(p7ecx->ecx, error); +} + +/* + * After this routine is called, the entire PKCS7 contentInfo is ready + * to be encoded. This is used internally, but can also be called from + * elsewhere for those who want to be able to just have pointers to + * the ASN1 template for pkcs7 contentInfo built into their own encodings. + */ +SECStatus +SEC_PKCS7PrepareForEncode(SEC_PKCS7ContentInfo *cinfo, + PK11SymKey *bulkkey, + SECKEYGetPasswordKey pwfn, + void *pwfnarg) +{ + SEC_PKCS7EncoderContext *p7ecx; + SECItem *content, *enc_content; + SECStatus rv; + + p7ecx = sec_pkcs7_encoder_start_contexts(cinfo, bulkkey); + if (p7ecx == NULL) + return SECFailure; + + content = SEC_PKCS7GetContent(cinfo); + + if (p7ecx->encryptobj != NULL) { + SECOidTag kind; + SEC_PKCS7EncryptedContentInfo *enccinfo; + + kind = SEC_PKCS7ContentType(p7ecx->cinfo); + switch (kind) { + default: + PORT_Assert(0); + rv = SECFailure; + goto loser; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + enccinfo = &(p7ecx->cinfo->content.encryptedData->encContentInfo); + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + enccinfo = &(p7ecx->cinfo->content.envelopedData->encContentInfo); + break; + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: + enccinfo = &(p7ecx->cinfo->content.signedAndEnvelopedData->encContentInfo); + break; + } + enc_content = &(enccinfo->encContent); + } else { + enc_content = NULL; + } + + if (content != NULL && content->data != NULL && content->len) { + rv = sec_pkcs7_encoder_work_data(p7ecx, enc_content, + content->data, content->len, PR_TRUE); + if (rv != SECSuccess) + goto loser; + } + + rv = sec_pkcs7_encoder_sig_and_certs(cinfo, pwfn, pwfnarg); + +loser: + PORT_Free(p7ecx); + return rv; +} + +/* + * Encode a PKCS7 object, in one shot. All necessary components + * of the object must already be specified. Either the data has + * already been included (via SetContent), or the data is detached, + * or there is no data at all (certs-only). + * + * "cinfo" specifies the object to be encoded. + * + * "outputfn" is where the encoded bytes will be passed. + * + * "outputarg" is an opaque argument to the above callback. + * + * "bulkkey" specifies the bulk encryption key to use. This argument + * can be NULL if no encryption is being done, or if the bulk key should + * be generated internally (usually the case for EnvelopedData but never + * for EncryptedData, which *must* provide a bulk encryption key). + * + * "pwfn" is a callback for getting the password which protects the + * private key of the signer. This argument can be NULL if it is known + * that no signing is going to be done. + * + * "pwfnarg" is an opaque argument to the above callback. + */ +SECStatus +SEC_PKCS7Encode(SEC_PKCS7ContentInfo *cinfo, + SEC_PKCS7EncoderOutputCallback outputfn, + void *outputarg, + PK11SymKey *bulkkey, + SECKEYGetPasswordKey pwfn, + void *pwfnarg) +{ + SECStatus rv; + + rv = SEC_PKCS7PrepareForEncode(cinfo, bulkkey, pwfn, pwfnarg); + if (rv == SECSuccess) { + struct sec_pkcs7_encoder_output outputcx; + + outputcx.outputfn = outputfn; + outputcx.outputarg = outputarg; + + rv = SEC_ASN1Encode(cinfo, sec_PKCS7ContentInfoTemplate, + sec_pkcs7_encoder_out, &outputcx); + } + + return rv; +} + +/* + * Encode a PKCS7 object, in one shot. All necessary components + * of the object must already be specified. Either the data has + * already been included (via SetContent), or the data is detached, + * or there is no data at all (certs-only). The output, rather than + * being passed to an output function as is done above, is all put + * into a SECItem. + * + * "pool" specifies a pool from which to allocate the result. + * It can be NULL, in which case memory is allocated generically. + * + * "dest" specifies a SECItem in which to put the result data. + * It can be NULL, in which case the entire item is allocated, too. + * + * "cinfo" specifies the object to be encoded. + * + * "bulkkey" specifies the bulk encryption key to use. This argument + * can be NULL if no encryption is being done, or if the bulk key should + * be generated internally (usually the case for EnvelopedData but never + * for EncryptedData, which *must* provide a bulk encryption key). + * + * "pwfn" is a callback for getting the password which protects the + * private key of the signer. This argument can be NULL if it is known + * that no signing is going to be done. + * + * "pwfnarg" is an opaque argument to the above callback. + */ +SECItem * +SEC_PKCS7EncodeItem(PLArenaPool *pool, + SECItem *dest, + SEC_PKCS7ContentInfo *cinfo, + PK11SymKey *bulkkey, + SECKEYGetPasswordKey pwfn, + void *pwfnarg) +{ + SECStatus rv; + + rv = SEC_PKCS7PrepareForEncode(cinfo, bulkkey, pwfn, pwfnarg); + if (rv != SECSuccess) + return NULL; + + return SEC_ASN1EncodeItem(pool, dest, cinfo, sec_PKCS7ContentInfoTemplate); +} diff --git a/security/nss/lib/pkcs7/p7local.c b/security/nss/lib/pkcs7/p7local.c new file mode 100644 index 0000000000..94086a684b --- /dev/null +++ b/security/nss/lib/pkcs7/p7local.c @@ -0,0 +1,1309 @@ +/* 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/. */ + +/* + * Support routines for PKCS7 implementation, none of which are exported. + * This file should only contain things that are needed by both the + * encoding/creation side *and* the decoding/decryption side. Anything + * else should be static routines in the appropriate file. + */ + +#include "p7local.h" + +#include "cryptohi.h" +#include "secasn1.h" +#include "secoid.h" +#include "secitem.h" +#include "pk11func.h" +#include "secpkcs5.h" +#include "secerr.h" + +/* + * ------------------------------------------------------------------- + * Cipher stuff. + */ + +typedef SECStatus (*sec_pkcs7_cipher_function)(void *, + unsigned char *, + unsigned *, + unsigned int, + const unsigned char *, + unsigned int); +typedef SECStatus (*sec_pkcs7_cipher_destroy)(void *, PRBool); + +#define BLOCK_SIZE 4096 + +struct sec_pkcs7_cipher_object { + void *cx; + sec_pkcs7_cipher_function doit; + sec_pkcs7_cipher_destroy destroy; + PRBool encrypt; + int block_size; + int pad_size; + int pending_count; + unsigned char pending_buf[BLOCK_SIZE]; +}; + +SEC_ASN1_MKSUB(CERT_IssuerAndSNTemplate) +SEC_ASN1_MKSUB(CERT_SetOfSignedCrlTemplate) +SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate) +SEC_ASN1_MKSUB(SEC_OctetStringTemplate) +SEC_ASN1_MKSUB(SEC_SetOfAnyTemplate) + +/* + * Create a cipher object to do decryption, based on the given bulk + * encryption key and algorithm identifier (which may include an iv). + * + * XXX This interface, or one similar, would be really nice available + * in general... I tried to keep the pkcs7-specific stuff (mostly + * having to do with padding) out of here. + * + * XXX Once both are working, it might be nice to combine this and the + * function below (for starting up encryption) into one routine, and just + * have two simple cover functions which call it. + */ +sec_PKCS7CipherObject * +sec_PKCS7CreateDecryptObject(PK11SymKey *key, SECAlgorithmID *algid) +{ + sec_PKCS7CipherObject *result; + SECOidTag algtag; + void *ciphercx; + CK_MECHANISM_TYPE cryptoMechType; + PK11SlotInfo *slot; + SECItem *param = NULL; + + result = (struct sec_pkcs7_cipher_object *) + PORT_ZAlloc(sizeof(struct sec_pkcs7_cipher_object)); + if (result == NULL) + return NULL; + + ciphercx = NULL; + algtag = SECOID_GetAlgorithmTag(algid); + + if (SEC_PKCS5IsAlgorithmPBEAlg(algid)) { + SECItem *pwitem; + + pwitem = (SECItem *)PK11_GetSymKeyUserData(key); + if (!pwitem) { + PORT_Free(result); + return NULL; + } + + cryptoMechType = PK11_GetPBECryptoMechanism(algid, ¶m, pwitem); + if (cryptoMechType == CKM_INVALID_MECHANISM) { + PORT_Free(result); + SECITEM_FreeItem(param, PR_TRUE); + return NULL; + } + } else { + cryptoMechType = PK11_AlgtagToMechanism(algtag); + param = PK11_ParamFromAlgid(algid); + if (param == NULL) { + PORT_Free(result); + return NULL; + } + } + + result->pad_size = PK11_GetBlockSize(cryptoMechType, param); + slot = PK11_GetSlotFromKey(key); + result->block_size = PK11_IsHW(slot) ? BLOCK_SIZE : result->pad_size; + PK11_FreeSlot(slot); + ciphercx = PK11_CreateContextBySymKey(cryptoMechType, CKA_DECRYPT, + key, param); + SECITEM_FreeItem(param, PR_TRUE); + if (ciphercx == NULL) { + PORT_Free(result); + return NULL; + } + + result->cx = ciphercx; + result->doit = (sec_pkcs7_cipher_function)PK11_CipherOp; + result->destroy = (sec_pkcs7_cipher_destroy)PK11_DestroyContext; + result->encrypt = PR_FALSE; + result->pending_count = 0; + + return result; +} + +/* + * Create a cipher object to do encryption, based on the given bulk + * encryption key and algorithm tag. Fill in the algorithm identifier + * (which may include an iv) appropriately. + * + * XXX This interface, or one similar, would be really nice available + * in general... I tried to keep the pkcs7-specific stuff (mostly + * having to do with padding) out of here. + * + * XXX Once both are working, it might be nice to combine this and the + * function above (for starting up decryption) into one routine, and just + * have two simple cover functions which call it. + */ +sec_PKCS7CipherObject * +sec_PKCS7CreateEncryptObject(PLArenaPool *poolp, PK11SymKey *key, + SECOidTag algtag, SECAlgorithmID *algid) +{ + sec_PKCS7CipherObject *result; + void *ciphercx; + SECStatus rv; + CK_MECHANISM_TYPE cryptoMechType; + PK11SlotInfo *slot; + SECItem *param = NULL; + PRBool needToEncodeAlgid = PR_FALSE; + + result = (struct sec_pkcs7_cipher_object *) + PORT_ZAlloc(sizeof(struct sec_pkcs7_cipher_object)); + if (result == NULL) + return NULL; + + ciphercx = NULL; + if (SEC_PKCS5IsAlgorithmPBEAlg(algid)) { + SECItem *pwitem; + + pwitem = (SECItem *)PK11_GetSymKeyUserData(key); + if (!pwitem) { + PORT_Free(result); + return NULL; + } + + cryptoMechType = PK11_GetPBECryptoMechanism(algid, ¶m, pwitem); + if (cryptoMechType == CKM_INVALID_MECHANISM) { + PORT_Free(result); + SECITEM_FreeItem(param, PR_TRUE); + return NULL; + } + } else { + cryptoMechType = PK11_AlgtagToMechanism(algtag); + param = PK11_GenerateNewParam(cryptoMechType, key); + if (param == NULL) { + PORT_Free(result); + return NULL; + } + needToEncodeAlgid = PR_TRUE; + } + + result->pad_size = PK11_GetBlockSize(cryptoMechType, param); + slot = PK11_GetSlotFromKey(key); + result->block_size = PK11_IsHW(slot) ? BLOCK_SIZE : result->pad_size; + PK11_FreeSlot(slot); + ciphercx = PK11_CreateContextBySymKey(cryptoMechType, CKA_ENCRYPT, + key, param); + if (ciphercx == NULL) { + PORT_Free(result); + SECITEM_FreeItem(param, PR_TRUE); + return NULL; + } + + /* + * These are placed after the CreateContextBySymKey() because some + * mechanisms have to generate their IVs from their card (i.e. FORTEZZA). + * Don't move it from here. + */ + if (needToEncodeAlgid) { + rv = PK11_ParamToAlgid(algtag, param, poolp, algid); + if (rv != SECSuccess) { + PORT_Free(result); + SECITEM_FreeItem(param, PR_TRUE); + PK11_DestroyContext(ciphercx, PR_TRUE); + return NULL; + } + } + SECITEM_FreeItem(param, PR_TRUE); + + result->cx = ciphercx; + result->doit = (sec_pkcs7_cipher_function)PK11_CipherOp; + result->destroy = (sec_pkcs7_cipher_destroy)PK11_DestroyContext; + result->encrypt = PR_TRUE; + result->pending_count = 0; + + return result; +} + +/* + * Destroy the cipher object. + */ +static void +sec_pkcs7_destroy_cipher(sec_PKCS7CipherObject *obj) +{ + (*obj->destroy)(obj->cx, PR_TRUE); + PORT_Free(obj); +} + +void +sec_PKCS7DestroyDecryptObject(sec_PKCS7CipherObject *obj) +{ + PORT_Assert(obj != NULL); + if (obj == NULL) + return; + PORT_Assert(!obj->encrypt); + sec_pkcs7_destroy_cipher(obj); +} + +void +sec_PKCS7DestroyEncryptObject(sec_PKCS7CipherObject *obj) +{ + PORT_Assert(obj != NULL); + if (obj == NULL) + return; + PORT_Assert(obj->encrypt); + sec_pkcs7_destroy_cipher(obj); +} + +/* + * XXX I think all of the following lengths should be longs instead + * of ints, but our current crypto interface uses ints, so I did too. + */ + +/* + * What will be the output length of the next call to decrypt? + * Result can be used to perform memory allocations. Note that the amount + * is exactly accurate only when not doing a block cipher or when final + * is false, otherwise it is an upper bound on the amount because until + * we see the data we do not know how many padding bytes there are + * (always between 1 and bsize). + * + * Note that this can return zero, which does not mean that the decrypt + * operation can be skipped! (It simply means that there are not enough + * bytes to make up an entire block; the bytes will be reserved until + * there are enough to encrypt/decrypt at least one block.) However, + * if zero is returned it *does* mean that no output buffer need be + * passed in to the subsequent decrypt operation, as no output bytes + * will be stored. + */ +unsigned int +sec_PKCS7DecryptLength(sec_PKCS7CipherObject *obj, unsigned int input_len, + PRBool final) +{ + int blocks, block_size; + + PORT_Assert(!obj->encrypt); + + block_size = obj->block_size; + + /* + * If this is not a block cipher, then we always have the same + * number of output bytes as we had input bytes. + */ + if (block_size == 0) + return input_len; + + /* + * On the final call, we will always use up all of the pending + * bytes plus all of the input bytes, *but*, there will be padding + * at the end and we cannot predict how many bytes of padding we + * will end up removing. The amount given here is actually known + * to be at least 1 byte too long (because we know we will have + * at least 1 byte of padding), but seemed clearer/better to me. + */ + if (final) + return obj->pending_count + input_len; + + /* + * Okay, this amount is exactly what we will output on the + * next cipher operation. We will always hang onto the last + * 1 - block_size bytes for non-final operations. That is, + * we will do as many complete blocks as we can *except* the + * last block (complete or partial). (This is because until + * we know we are at the end, we cannot know when to interpret + * and removing the padding byte(s), which are guaranteed to + * be there.) + */ + blocks = (obj->pending_count + input_len - 1) / block_size; + return blocks * block_size; +} + +/* + * What will be the output length of the next call to encrypt? + * Result can be used to perform memory allocations. + * + * Note that this can return zero, which does not mean that the encrypt + * operation can be skipped! (It simply means that there are not enough + * bytes to make up an entire block; the bytes will be reserved until + * there are enough to encrypt/decrypt at least one block.) However, + * if zero is returned it *does* mean that no output buffer need be + * passed in to the subsequent encrypt operation, as no output bytes + * will be stored. + */ +unsigned int +sec_PKCS7EncryptLength(sec_PKCS7CipherObject *obj, unsigned int input_len, + PRBool final) +{ + int blocks, block_size; + int pad_size; + + PORT_Assert(obj->encrypt); + + block_size = obj->block_size; + pad_size = obj->pad_size; + + /* + * If this is not a block cipher, then we always have the same + * number of output bytes as we had input bytes. + */ + if (block_size == 0) + return input_len; + + /* + * On the final call, we only send out what we need for + * remaining bytes plus the padding. (There is always padding, + * so even if we have an exact number of blocks as input, we + * will add another full block that is just padding.) + */ + if (final) { + if (pad_size == 0) { + return obj->pending_count + input_len; + } else { + blocks = (obj->pending_count + input_len) / pad_size; + blocks++; + return blocks * pad_size; + } + } + + /* + * Now, count the number of complete blocks of data we have. + */ + blocks = (obj->pending_count + input_len) / block_size; + + return blocks * block_size; +} + +/* + * Decrypt a given length of input buffer (starting at "input" and + * containing "input_len" bytes), placing the decrypted bytes in + * "output" and storing the output length in "*output_len_p". + * "obj" is the return value from sec_PKCS7CreateDecryptObject. + * When "final" is true, this is the last of the data to be decrypted. + * + * This is much more complicated than it sounds when the cipher is + * a block-type, meaning that the decryption function will only + * operate on whole blocks. But our caller is operating stream-wise, + * and can pass in any number of bytes. So we need to keep track + * of block boundaries. We save excess bytes between calls in "obj". + * We also need to determine which bytes are padding, and remove + * them from the output. We can only do this step when we know we + * have the final block of data. PKCS #7 specifies that the padding + * used for a block cipher is a string of bytes, each of whose value is + * the same as the length of the padding, and that all data is padded. + * (Even data that starts out with an exact multiple of blocks gets + * added to it another block, all of which is padding.) + */ +SECStatus +sec_PKCS7Decrypt(sec_PKCS7CipherObject *obj, unsigned char *output, + unsigned int *output_len_p, unsigned int max_output_len, + const unsigned char *input, unsigned int input_len, + PRBool final) +{ + unsigned int blocks, bsize, pcount, padsize; + unsigned int max_needed, ifraglen, ofraglen, output_len; + unsigned char *pbuf; + SECStatus rv; + + PORT_Assert(!obj->encrypt); + + /* + * Check that we have enough room for the output. Our caller should + * already handle this; failure is really an internal error (i.e. bug). + */ + max_needed = sec_PKCS7DecryptLength(obj, input_len, final); + PORT_Assert(max_output_len >= max_needed); + if (max_output_len < max_needed) { + /* PORT_SetError (XXX); */ + return SECFailure; + } + + /* + * hardware encryption does not like small decryption sizes here, so we + * allow both blocking and padding. + */ + bsize = obj->block_size; + padsize = obj->pad_size; + + /* + * When no blocking or padding work to do, we can simply call the + * cipher function and we are done. + */ + if (bsize == 0) { + return (*obj->doit)(obj->cx, output, output_len_p, max_output_len, + input, input_len); + } + + pcount = obj->pending_count; + pbuf = obj->pending_buf; + + output_len = 0; + + if (pcount) { + /* + * Try to fill in an entire block, starting with the bytes + * we already have saved away. + */ + while (input_len && pcount < bsize) { + pbuf[pcount++] = *input++; + input_len--; + } + /* + * If we have at most a whole block and this is not our last call, + * then we are done for now. (We do not try to decrypt a lone + * single block because we cannot interpret the padding bytes + * until we know we are handling the very last block of all input.) + */ + if (input_len == 0 && !final) { + obj->pending_count = pcount; + if (output_len_p) + *output_len_p = 0; + return SECSuccess; + } + /* + * Given the logic above, we expect to have a full block by now. + * If we do not, there is something wrong, either with our own + * logic or with (length of) the data given to us. + */ + if ((padsize != 0) && (pcount % padsize) != 0) { + PORT_Assert(final); + PORT_SetError(SEC_ERROR_BAD_DATA); + return SECFailure; + } + /* + * Decrypt the block. + */ + rv = (*obj->doit)(obj->cx, output, &ofraglen, max_output_len, + pbuf, pcount); + if (rv != SECSuccess) + return rv; + + /* + * For now anyway, all of our ciphers have the same number of + * bytes of output as they do input. If this ever becomes untrue, + * then sec_PKCS7DecryptLength needs to be made smarter! + */ + PORT_Assert(ofraglen == pcount); + + /* + * Account for the bytes now in output. + */ + max_output_len -= ofraglen; + output_len += ofraglen; + output += ofraglen; + } + + /* + * If this is our last call, we expect to have an exact number of + * blocks left to be decrypted; we will decrypt them all. + * + * If not our last call, we always save between 1 and bsize bytes + * until next time. (We must do this because we cannot be sure + * that none of the decrypted bytes are padding bytes until we + * have at least another whole block of data. You cannot tell by + * looking -- the data could be anything -- you can only tell by + * context, knowing you are looking at the last block.) We could + * decrypt a whole block now but it is easier if we just treat it + * the same way we treat partial block bytes. + */ + if (final) { + if (padsize) { + blocks = input_len / padsize; + ifraglen = blocks * padsize; + } else + ifraglen = input_len; + PORT_Assert(ifraglen == input_len); + + if (ifraglen != input_len) { + PORT_SetError(SEC_ERROR_BAD_DATA); + return SECFailure; + } + } else { + blocks = (input_len - 1) / bsize; + ifraglen = blocks * bsize; + PORT_Assert(ifraglen < input_len); + + pcount = input_len - ifraglen; + PORT_Memcpy(pbuf, input + ifraglen, pcount); + obj->pending_count = pcount; + } + + if (ifraglen) { + rv = (*obj->doit)(obj->cx, output, &ofraglen, max_output_len, + input, ifraglen); + if (rv != SECSuccess) + return rv; + + /* + * For now anyway, all of our ciphers have the same number of + * bytes of output as they do input. If this ever becomes untrue, + * then sec_PKCS7DecryptLength needs to be made smarter! + */ + PORT_Assert(ifraglen == ofraglen); + if (ifraglen != ofraglen) { + PORT_SetError(SEC_ERROR_BAD_DATA); + return SECFailure; + } + + output_len += ofraglen; + } else { + ofraglen = 0; + } + + /* + * If we just did our very last block, "remove" the padding by + * adjusting the output length. + */ + if (final && (padsize != 0)) { + unsigned int padlen = *(output + ofraglen - 1); + if (padlen == 0 || padlen > padsize) { + PORT_SetError(SEC_ERROR_BAD_DATA); + return SECFailure; + } + output_len -= padlen; + } + + PORT_Assert(output_len_p != NULL || output_len == 0); + if (output_len_p != NULL) + *output_len_p = output_len; + + return SECSuccess; +} + +/* + * Encrypt a given length of input buffer (starting at "input" and + * containing "input_len" bytes), placing the encrypted bytes in + * "output" and storing the output length in "*output_len_p". + * "obj" is the return value from sec_PKCS7CreateEncryptObject. + * When "final" is true, this is the last of the data to be encrypted. + * + * This is much more complicated than it sounds when the cipher is + * a block-type, meaning that the encryption function will only + * operate on whole blocks. But our caller is operating stream-wise, + * and can pass in any number of bytes. So we need to keep track + * of block boundaries. We save excess bytes between calls in "obj". + * We also need to add padding bytes at the end. PKCS #7 specifies + * that the padding used for a block cipher is a string of bytes, + * each of whose value is the same as the length of the padding, + * and that all data is padded. (Even data that starts out with + * an exact multiple of blocks gets added to it another block, + * all of which is padding.) + * + * XXX I would kind of like to combine this with the function above + * which does decryption, since they have a lot in common. But the + * tricky parts about padding and filling blocks would be much + * harder to read that way, so I left them separate. At least for + * now until it is clear that they are right. + */ +SECStatus +sec_PKCS7Encrypt(sec_PKCS7CipherObject *obj, unsigned char *output, + unsigned int *output_len_p, unsigned int max_output_len, + const unsigned char *input, unsigned int input_len, + PRBool final) +{ + int blocks, bsize, padlen, pcount, padsize; + unsigned int max_needed, ifraglen, ofraglen, output_len; + unsigned char *pbuf; + SECStatus rv; + + PORT_Assert(obj->encrypt); + + /* + * Check that we have enough room for the output. Our caller should + * already handle this; failure is really an internal error (i.e. bug). + */ + max_needed = sec_PKCS7EncryptLength(obj, input_len, final); + PORT_Assert(max_output_len >= max_needed); + if (max_output_len < max_needed) { + /* PORT_SetError (XXX); */ + return SECFailure; + } + + bsize = obj->block_size; + padsize = obj->pad_size; + + /* + * When no blocking and padding work to do, we can simply call the + * cipher function and we are done. + */ + if (bsize == 0) { + return (*obj->doit)(obj->cx, output, output_len_p, max_output_len, + input, input_len); + } + + pcount = obj->pending_count; + pbuf = obj->pending_buf; + + output_len = 0; + + if (pcount) { + /* + * Try to fill in an entire block, starting with the bytes + * we already have saved away. + */ + while (input_len && pcount < bsize) { + pbuf[pcount++] = *input++; + input_len--; + } + /* + * If we do not have a full block and we know we will be + * called again, then we are done for now. + */ + if (pcount < bsize && !final) { + obj->pending_count = pcount; + if (output_len_p != NULL) + *output_len_p = 0; + return SECSuccess; + } + /* + * If we have a whole block available, encrypt it. + */ + if ((padsize == 0) || (pcount % padsize) == 0) { + rv = (*obj->doit)(obj->cx, output, &ofraglen, max_output_len, + pbuf, pcount); + if (rv != SECSuccess) + return rv; + + /* + * For now anyway, all of our ciphers have the same number of + * bytes of output as they do input. If this ever becomes untrue, + * then sec_PKCS7EncryptLength needs to be made smarter! + */ + PORT_Assert(ofraglen == pcount); + + /* + * Account for the bytes now in output. + */ + max_output_len -= ofraglen; + output_len += ofraglen; + output += ofraglen; + + pcount = 0; + } + } + + if (input_len) { + PORT_Assert(pcount == 0); + + blocks = input_len / bsize; + ifraglen = blocks * bsize; + + if (ifraglen) { + rv = (*obj->doit)(obj->cx, output, &ofraglen, max_output_len, + input, ifraglen); + if (rv != SECSuccess) + return rv; + + /* + * For now anyway, all of our ciphers have the same number of + * bytes of output as they do input. If this ever becomes untrue, + * then sec_PKCS7EncryptLength needs to be made smarter! + */ + PORT_Assert(ifraglen == ofraglen); + + max_output_len -= ofraglen; + output_len += ofraglen; + output += ofraglen; + } + + pcount = input_len - ifraglen; + PORT_Assert(pcount < bsize); + if (pcount) + PORT_Memcpy(pbuf, input + ifraglen, pcount); + } + + if (final) { + if (padsize) { + padlen = padsize - (pcount % padsize); + PORT_Memset(pbuf + pcount, padlen, padlen); + } else { + padlen = 0; + } + rv = (*obj->doit)(obj->cx, output, &ofraglen, max_output_len, + pbuf, pcount + padlen); + if (rv != SECSuccess) + return rv; + + /* + * For now anyway, all of our ciphers have the same number of + * bytes of output as they do input. If this ever becomes untrue, + * then sec_PKCS7EncryptLength needs to be made smarter! + */ + PORT_Assert(ofraglen == (pcount + padlen)); + output_len += ofraglen; + } else { + obj->pending_count = pcount; + } + + PORT_Assert(output_len_p != NULL || output_len == 0); + if (output_len_p != NULL) + *output_len_p = output_len; + + return SECSuccess; +} + +/* + * End of cipher stuff. + * ------------------------------------------------------------------- + */ + +/* + * ------------------------------------------------------------------- + * XXX The following Attribute stuff really belongs elsewhere. + * The Attribute type is *not* part of pkcs7 but rather X.501. + * But for now, since PKCS7 is the only customer of attributes, + * we define them here. Once there is a use outside of PKCS7, + * then change the attribute types and functions from internal + * to external naming convention, and move them elsewhere! + */ + +/* + * Look through a set of attributes and find one that matches the + * specified object ID. If "only" is true, then make sure that + * there is not more than one attribute of the same type. Otherwise, + * just return the first one found. (XXX Does anybody really want + * that first-found behavior? It was like that when I found it...) + */ +SEC_PKCS7Attribute * +sec_PKCS7FindAttribute(SEC_PKCS7Attribute **attrs, SECOidTag oidtag, + PRBool only) +{ + SECOidData *oid; + SEC_PKCS7Attribute *attr1, *attr2; + + if (attrs == NULL) + return NULL; + + oid = SECOID_FindOIDByTag(oidtag); + if (oid == NULL) + return NULL; + + while ((attr1 = *attrs++) != NULL) { + if (attr1->type.len == oid->oid.len && PORT_Memcmp(attr1->type.data, oid->oid.data, oid->oid.len) == 0) + break; + } + + if (attr1 == NULL) + return NULL; + + if (!only) + return attr1; + + while ((attr2 = *attrs++) != NULL) { + if (attr2->type.len == oid->oid.len && PORT_Memcmp(attr2->type.data, oid->oid.data, oid->oid.len) == 0) + break; + } + + if (attr2 != NULL) + return NULL; + + return attr1; +} + +/* + * Return the single attribute value, doing some sanity checking first: + * - Multiple values are *not* expected. + * - Empty values are *not* expected. + */ +SECItem * +sec_PKCS7AttributeValue(SEC_PKCS7Attribute *attr) +{ + SECItem *value; + + if (attr == NULL) + return NULL; + + value = attr->values[0]; + + if (value == NULL || value->data == NULL || value->len == 0) + return NULL; + + if (attr->values[1] != NULL) + return NULL; + + return value; +} + +static const SEC_ASN1Template * +sec_attr_choose_attr_value_template(void *src_or_dest, PRBool encoding) +{ + const SEC_ASN1Template *theTemplate; + + SEC_PKCS7Attribute *attribute; + SECOidData *oiddata; + PRBool encoded; + + PORT_Assert(src_or_dest != NULL); + if (src_or_dest == NULL) + return NULL; + + attribute = (SEC_PKCS7Attribute *)src_or_dest; + + if (encoding && attribute->encoded) + return SEC_ASN1_GET(SEC_AnyTemplate); + + oiddata = attribute->typeTag; + if (oiddata == NULL) { + oiddata = SECOID_FindOID(&attribute->type); + attribute->typeTag = oiddata; + } + + if (oiddata == NULL) { + encoded = PR_TRUE; + theTemplate = SEC_ASN1_GET(SEC_AnyTemplate); + } else { + switch (oiddata->offset) { + default: + encoded = PR_TRUE; + theTemplate = SEC_ASN1_GET(SEC_AnyTemplate); + break; + case SEC_OID_PKCS9_EMAIL_ADDRESS: + case SEC_OID_RFC1274_MAIL: + case SEC_OID_PKCS9_UNSTRUCTURED_NAME: + encoded = PR_FALSE; + theTemplate = SEC_ASN1_GET(SEC_IA5StringTemplate); + break; + case SEC_OID_PKCS9_CONTENT_TYPE: + encoded = PR_FALSE; + theTemplate = SEC_ASN1_GET(SEC_ObjectIDTemplate); + break; + case SEC_OID_PKCS9_MESSAGE_DIGEST: + encoded = PR_FALSE; + theTemplate = SEC_ASN1_GET(SEC_OctetStringTemplate); + break; + case SEC_OID_PKCS9_SIGNING_TIME: + encoded = PR_FALSE; + theTemplate = SEC_ASN1_GET(CERT_TimeChoiceTemplate); + break; + /* XXX Want other types here, too */ + } + } + + if (encoding) { + /* + * If we are encoding and we think we have an already-encoded value, + * then the code which initialized this attribute should have set + * the "encoded" property to true (and we would have returned early, + * up above). No devastating error, but that code should be fixed. + * (It could indicate that the resulting encoded bytes are wrong.) + */ + PORT_Assert(!encoded); + } else { + /* + * We are decoding; record whether the resulting value is + * still encoded or not. + */ + attribute->encoded = encoded; + } + return theTemplate; +} + +static const SEC_ASN1TemplateChooserPtr sec_attr_chooser = sec_attr_choose_attr_value_template; + +static const SEC_ASN1Template sec_pkcs7_attribute_template[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(SEC_PKCS7Attribute) }, + { SEC_ASN1_OBJECT_ID, + offsetof(SEC_PKCS7Attribute, type) }, + { SEC_ASN1_DYNAMIC | SEC_ASN1_SET_OF, + offsetof(SEC_PKCS7Attribute, values), + &sec_attr_chooser }, + { 0 } +}; + +static const SEC_ASN1Template sec_pkcs7_set_of_attribute_template[] = { + { SEC_ASN1_SET_OF, 0, sec_pkcs7_attribute_template }, +}; + +/* + * If you are wondering why this routine does not reorder the attributes + * first, and might be tempted to make it do so, see the comment by the + * call to ReorderAttributes in p7encode.c. (Or, see who else calls this + * and think long and hard about the implications of making it always + * do the reordering.) + */ +SECItem * +sec_PKCS7EncodeAttributes(PLArenaPool *poolp, SECItem *dest, void *src) +{ + return SEC_ASN1EncodeItem(poolp, dest, src, + sec_pkcs7_set_of_attribute_template); +} + +/* + * Make sure that the order of the attributes guarantees valid DER + * (which must be in lexigraphically ascending order for a SET OF); + * if reordering is necessary it will be done in place (in attrs). + */ +SECStatus +sec_PKCS7ReorderAttributes(SEC_PKCS7Attribute **attrs) +{ + PLArenaPool *poolp; + int num_attrs, i, pass, besti; + unsigned int j; + SECItem **enc_attrs; + SEC_PKCS7Attribute **new_attrs; + + /* + * I think we should not be called with NULL. But if we are, + * call it a success anyway, because the order *is* okay. + */ + PORT_Assert(attrs != NULL); + if (attrs == NULL) + return SECSuccess; + + /* + * Count how many attributes we are dealing with here. + */ + num_attrs = 0; + while (attrs[num_attrs] != NULL) + num_attrs++; + + /* + * Again, I think we should have some attributes here. + * But if we do not, or if there is only one, then call it + * a success because it also already has a fine order. + */ + PORT_Assert(num_attrs); + if (num_attrs == 0 || num_attrs == 1) + return SECSuccess; + + /* + * Allocate an arena for us to work with, so it is easy to + * clean up all of the memory (fairly small pieces, really). + */ + poolp = PORT_NewArena(1024); /* XXX what is right value? */ + if (poolp == NULL) + return SECFailure; /* no memory; nothing we can do... */ + + /* + * Allocate arrays to hold the individual encodings which we will use + * for comparisons and the reordered attributes as they are sorted. + */ + enc_attrs = (SECItem **)PORT_ArenaZAlloc(poolp, num_attrs * sizeof(SECItem *)); + new_attrs = (SEC_PKCS7Attribute **)PORT_ArenaZAlloc(poolp, + num_attrs * sizeof(SEC_PKCS7Attribute *)); + if (enc_attrs == NULL || new_attrs == NULL) { + PORT_FreeArena(poolp, PR_FALSE); + return SECFailure; + } + + /* + * DER encode each individual attribute. + */ + for (i = 0; i < num_attrs; i++) { + enc_attrs[i] = SEC_ASN1EncodeItem(poolp, NULL, attrs[i], + sec_pkcs7_attribute_template); + if (enc_attrs[i] == NULL) { + PORT_FreeArena(poolp, PR_FALSE); + return SECFailure; + } + } + + /* + * Now compare and sort them; this is not the most efficient sorting + * method, but it is just fine for the problem at hand, because the + * number of attributes is (always) going to be small. + */ + for (pass = 0; pass < num_attrs; pass++) { + /* + * Find the first not-yet-accepted attribute. (Once one is + * sorted into the other array, it is cleared from enc_attrs.) + */ + for (i = 0; i < num_attrs; i++) { + if (enc_attrs[i] != NULL) + break; + } + PORT_Assert(i < num_attrs); + besti = i; + + /* + * Find the lowest (lexigraphically) encoding. One that is + * shorter than all the rest is known to be "less" because each + * attribute is of the same type (a SEQUENCE) and so thus the + * first octet of each is the same, and the second octet is + * the length (or the length of the length with the high bit + * set, followed by the length, which also works out to always + * order the shorter first). Two (or more) that have the + * same length need to be compared byte by byte until a mismatch + * is found. + */ + for (i = besti + 1; i < num_attrs; i++) { + if (enc_attrs[i] == NULL) /* slot already handled */ + continue; + + if (enc_attrs[i]->len != enc_attrs[besti]->len) { + if (enc_attrs[i]->len < enc_attrs[besti]->len) + besti = i; + continue; + } + + for (j = 0; j < enc_attrs[i]->len; j++) { + if (enc_attrs[i]->data[j] < enc_attrs[besti]->data[j]) { + besti = i; + break; + } + } + + /* + * For this not to be true, we would have to have encountered + * two *identical* attributes, which I think we should not see. + * So assert if it happens, but even if it does, let it go + * through; the ordering of the two does not matter. + */ + PORT_Assert(j < enc_attrs[i]->len); + } + + /* + * Now we have found the next-lowest one; copy it over and + * remove it from enc_attrs. + */ + new_attrs[pass] = attrs[besti]; + enc_attrs[besti] = NULL; + } + + /* + * Now new_attrs has the attributes in the order we want; + * copy them back into the attrs array we started with. + */ + for (i = 0; i < num_attrs; i++) + attrs[i] = new_attrs[i]; + + PORT_FreeArena(poolp, PR_FALSE); + return SECSuccess; +} + +/* + * End of attribute stuff. + * ------------------------------------------------------------------- + */ + +/* + * Templates and stuff. Keep these at the end of the file. + */ + +/* forward declaration */ +static const SEC_ASN1Template * +sec_pkcs7_choose_content_template(void *src_or_dest, PRBool encoding); + +static const SEC_ASN1TemplateChooserPtr sec_pkcs7_chooser = sec_pkcs7_choose_content_template; + +const SEC_ASN1Template sec_PKCS7ContentInfoTemplate[] = { + { SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM, + 0, NULL, sizeof(SEC_PKCS7ContentInfo) }, + { SEC_ASN1_OBJECT_ID, + offsetof(SEC_PKCS7ContentInfo, contentType) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_DYNAMIC | SEC_ASN1_MAY_STREAM | SEC_ASN1_EXPLICIT | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(SEC_PKCS7ContentInfo, content), + &sec_pkcs7_chooser }, + { 0 } +}; + +/* XXX These names should change from external to internal convention. */ + +static const SEC_ASN1Template SEC_PKCS7SignerInfoTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(SEC_PKCS7SignerInfo) }, + { SEC_ASN1_INTEGER, + offsetof(SEC_PKCS7SignerInfo, version) }, + { SEC_ASN1_POINTER | SEC_ASN1_XTRN, + offsetof(SEC_PKCS7SignerInfo, issuerAndSN), + SEC_ASN1_SUB(CERT_IssuerAndSNTemplate) }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, + offsetof(SEC_PKCS7SignerInfo, digestAlg), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0, + offsetof(SEC_PKCS7SignerInfo, authAttr), + sec_pkcs7_set_of_attribute_template }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, + offsetof(SEC_PKCS7SignerInfo, digestEncAlg), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_OCTET_STRING, + offsetof(SEC_PKCS7SignerInfo, encDigest) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 1, + offsetof(SEC_PKCS7SignerInfo, unAuthAttr), + sec_pkcs7_set_of_attribute_template }, + { 0 } +}; + +static const SEC_ASN1Template SEC_PKCS7SignedDataTemplate[] = { + { SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM, + 0, NULL, sizeof(SEC_PKCS7SignedData) }, + { SEC_ASN1_INTEGER, + offsetof(SEC_PKCS7SignedData, version) }, + { SEC_ASN1_SET_OF | SEC_ASN1_XTRN, + offsetof(SEC_PKCS7SignedData, digestAlgorithms), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_INLINE, + offsetof(SEC_PKCS7SignedData, contentInfo), + sec_PKCS7ContentInfoTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | + SEC_ASN1_XTRN | 0, + offsetof(SEC_PKCS7SignedData, rawCerts), + SEC_ASN1_SUB(SEC_SetOfAnyTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | + SEC_ASN1_XTRN | 1, + offsetof(SEC_PKCS7SignedData, crls), + SEC_ASN1_SUB(CERT_SetOfSignedCrlTemplate) }, + { SEC_ASN1_SET_OF, + offsetof(SEC_PKCS7SignedData, signerInfos), + SEC_PKCS7SignerInfoTemplate }, + { 0 } +}; + +static const SEC_ASN1Template SEC_PointerToPKCS7SignedDataTemplate[] = { + { SEC_ASN1_POINTER, 0, SEC_PKCS7SignedDataTemplate } +}; + +static const SEC_ASN1Template SEC_PKCS7RecipientInfoTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(SEC_PKCS7RecipientInfo) }, + { SEC_ASN1_INTEGER, + offsetof(SEC_PKCS7RecipientInfo, version) }, + { SEC_ASN1_POINTER | SEC_ASN1_XTRN, + offsetof(SEC_PKCS7RecipientInfo, issuerAndSN), + SEC_ASN1_SUB(CERT_IssuerAndSNTemplate) }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, + offsetof(SEC_PKCS7RecipientInfo, keyEncAlg), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_OCTET_STRING, + offsetof(SEC_PKCS7RecipientInfo, encKey) }, + { 0 } +}; + +static const SEC_ASN1Template SEC_PKCS7EncryptedContentInfoTemplate[] = { + { SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM, + 0, NULL, sizeof(SEC_PKCS7EncryptedContentInfo) }, + { SEC_ASN1_OBJECT_ID, + offsetof(SEC_PKCS7EncryptedContentInfo, contentType) }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, + offsetof(SEC_PKCS7EncryptedContentInfo, contentEncAlg), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_MAY_STREAM | SEC_ASN1_CONTEXT_SPECIFIC | + SEC_ASN1_XTRN | 0, + offsetof(SEC_PKCS7EncryptedContentInfo, encContent), + SEC_ASN1_SUB(SEC_OctetStringTemplate) }, + { 0 } +}; + +static const SEC_ASN1Template SEC_PKCS7EnvelopedDataTemplate[] = { + { SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM, + 0, NULL, sizeof(SEC_PKCS7EnvelopedData) }, + { SEC_ASN1_INTEGER, + offsetof(SEC_PKCS7EnvelopedData, version) }, + { SEC_ASN1_SET_OF, + offsetof(SEC_PKCS7EnvelopedData, recipientInfos), + SEC_PKCS7RecipientInfoTemplate }, + { SEC_ASN1_INLINE, + offsetof(SEC_PKCS7EnvelopedData, encContentInfo), + SEC_PKCS7EncryptedContentInfoTemplate }, + { 0 } +}; + +static const SEC_ASN1Template SEC_PointerToPKCS7EnvelopedDataTemplate[] = { + { SEC_ASN1_POINTER, 0, SEC_PKCS7EnvelopedDataTemplate } +}; + +static const SEC_ASN1Template SEC_PKCS7SignedAndEnvelopedDataTemplate[] = { + { SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM, + 0, NULL, sizeof(SEC_PKCS7SignedAndEnvelopedData) }, + { SEC_ASN1_INTEGER, + offsetof(SEC_PKCS7SignedAndEnvelopedData, version) }, + { SEC_ASN1_SET_OF, + offsetof(SEC_PKCS7SignedAndEnvelopedData, recipientInfos), + SEC_PKCS7RecipientInfoTemplate }, + { SEC_ASN1_SET_OF | SEC_ASN1_XTRN, + offsetof(SEC_PKCS7SignedAndEnvelopedData, digestAlgorithms), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_INLINE, + offsetof(SEC_PKCS7SignedAndEnvelopedData, encContentInfo), + SEC_PKCS7EncryptedContentInfoTemplate }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | + SEC_ASN1_XTRN | 0, + offsetof(SEC_PKCS7SignedAndEnvelopedData, rawCerts), + SEC_ASN1_SUB(SEC_SetOfAnyTemplate) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | + SEC_ASN1_XTRN | 1, + offsetof(SEC_PKCS7SignedAndEnvelopedData, crls), + SEC_ASN1_SUB(CERT_SetOfSignedCrlTemplate) }, + { SEC_ASN1_SET_OF, + offsetof(SEC_PKCS7SignedAndEnvelopedData, signerInfos), + SEC_PKCS7SignerInfoTemplate }, + { 0 } +}; + +static const SEC_ASN1Template + SEC_PointerToPKCS7SignedAndEnvelopedDataTemplate[] = { + { SEC_ASN1_POINTER, 0, SEC_PKCS7SignedAndEnvelopedDataTemplate } + }; + +static const SEC_ASN1Template SEC_PKCS7DigestedDataTemplate[] = { + { SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM, + 0, NULL, sizeof(SEC_PKCS7DigestedData) }, + { SEC_ASN1_INTEGER, + offsetof(SEC_PKCS7DigestedData, version) }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, + offsetof(SEC_PKCS7DigestedData, digestAlg), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_INLINE, + offsetof(SEC_PKCS7DigestedData, contentInfo), + sec_PKCS7ContentInfoTemplate }, + { SEC_ASN1_OCTET_STRING, + offsetof(SEC_PKCS7DigestedData, digest) }, + { 0 } +}; + +static const SEC_ASN1Template SEC_PointerToPKCS7DigestedDataTemplate[] = { + { SEC_ASN1_POINTER, 0, SEC_PKCS7DigestedDataTemplate } +}; + +static const SEC_ASN1Template SEC_PKCS7EncryptedDataTemplate[] = { + { SEC_ASN1_SEQUENCE | SEC_ASN1_MAY_STREAM, + 0, NULL, sizeof(SEC_PKCS7EncryptedData) }, + { SEC_ASN1_INTEGER, + offsetof(SEC_PKCS7EncryptedData, version) }, + { SEC_ASN1_INLINE, + offsetof(SEC_PKCS7EncryptedData, encContentInfo), + SEC_PKCS7EncryptedContentInfoTemplate }, + { 0 } +}; + +static const SEC_ASN1Template SEC_PointerToPKCS7EncryptedDataTemplate[] = { + { SEC_ASN1_POINTER, 0, SEC_PKCS7EncryptedDataTemplate } +}; + +static const SEC_ASN1Template * +sec_pkcs7_choose_content_template(void *src_or_dest, PRBool encoding) +{ + const SEC_ASN1Template *theTemplate; + SEC_PKCS7ContentInfo *cinfo; + SECOidTag kind; + + PORT_Assert(src_or_dest != NULL); + if (src_or_dest == NULL) + return NULL; + + cinfo = (SEC_PKCS7ContentInfo *)src_or_dest; + kind = SEC_PKCS7ContentType(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 = SEC_PointerToPKCS7SignedDataTemplate; + break; + case SEC_OID_PKCS7_ENVELOPED_DATA: + theTemplate = SEC_PointerToPKCS7EnvelopedDataTemplate; + break; + case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: + theTemplate = SEC_PointerToPKCS7SignedAndEnvelopedDataTemplate; + break; + case SEC_OID_PKCS7_DIGESTED_DATA: + theTemplate = SEC_PointerToPKCS7DigestedDataTemplate; + break; + case SEC_OID_PKCS7_ENCRYPTED_DATA: + theTemplate = SEC_PointerToPKCS7EncryptedDataTemplate; + break; + } + return theTemplate; +} + +/* + * End of templates. Do not add stuff after this; put new code + * up above the start of the template definitions. + */ diff --git a/security/nss/lib/pkcs7/p7local.h b/security/nss/lib/pkcs7/p7local.h new file mode 100644 index 0000000000..ad37c3aad6 --- /dev/null +++ b/security/nss/lib/pkcs7/p7local.h @@ -0,0 +1,137 @@ +/* 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/. */ + +/* + * Support routines for PKCS7 implementation, none of which are exported. + * This file should only contain things that are needed by both the + * encoding/creation side *and* the decoding/decryption side. Anything + * else should just be static routines in the appropriate file. + * + * Do not export this file! If something in here is really needed outside + * of pkcs7 code, first try to add a PKCS7 interface which will do it for + * you. If that has a problem, then just move out what you need, changing + * its name as appropriate! + */ + +#ifndef _P7LOCAL_H_ +#define _P7LOCAL_H_ + +#include "secpkcs7.h" +#include "secasn1t.h" + +extern const SEC_ASN1Template sec_PKCS7ContentInfoTemplate[]; + +/* opaque objects */ +typedef struct sec_pkcs7_cipher_object sec_PKCS7CipherObject; + +/************************************************************************/ +SEC_BEGIN_PROTOS + +/* + * Look through a set of attributes and find one that matches the + * specified object ID. If "only" is true, then make sure that + * there is not more than one attribute of the same type. Otherwise, + * just return the first one found. (XXX Does anybody really want + * that first-found behavior? It was like that when I found it...) + */ +extern SEC_PKCS7Attribute *sec_PKCS7FindAttribute(SEC_PKCS7Attribute **attrs, + SECOidTag oidtag, + PRBool only); +/* + * Return the single attribute value, doing some sanity checking first: + * - Multiple values are *not* expected. + * - Empty values are *not* expected. + */ +extern SECItem *sec_PKCS7AttributeValue(SEC_PKCS7Attribute *attr); + +/* + * Encode a set of attributes (found in "src"). + */ +extern SECItem *sec_PKCS7EncodeAttributes(PLArenaPool *poolp, + SECItem *dest, void *src); + +/* + * Make sure that the order of the attributes guarantees valid DER + * (which must be in lexigraphically ascending order for a SET OF); + * if reordering is necessary it will be done in place (in attrs). + */ +extern SECStatus sec_PKCS7ReorderAttributes(SEC_PKCS7Attribute **attrs); + +/* + * Create a context for decrypting, based on the given key and algorithm. + */ +extern sec_PKCS7CipherObject * +sec_PKCS7CreateDecryptObject(PK11SymKey *key, SECAlgorithmID *algid); + +/* + * Create a context for encrypting, based on the given key and algorithm, + * and fill in the algorithm id. + */ +extern sec_PKCS7CipherObject * +sec_PKCS7CreateEncryptObject(PLArenaPool *poolp, PK11SymKey *key, + SECOidTag algtag, SECAlgorithmID *algid); + +/* + * Destroy the given decryption or encryption object. + */ +extern void sec_PKCS7DestroyDecryptObject(sec_PKCS7CipherObject *obj); +extern void sec_PKCS7DestroyEncryptObject(sec_PKCS7CipherObject *obj); + +/* + * What will be the output length of the next call to encrypt/decrypt? + * Result can be used to perform memory allocations. Note that the amount + * is exactly accurate only when not doing a block cipher or when final + * is false, otherwise it is an upper bound on the amount because until + * we see the data we do not know how many padding bytes there are + * (always between 1 and the cipher block size). + * + * Note that this can return zero, which does not mean that the cipher + * operation can be skipped! (It simply means that there are not enough + * bytes to make up an entire block; the bytes will be reserved until + * there are enough to encrypt/decrypt at least one block.) However, + * if zero is returned it *does* mean that no output buffer need be + * passed in to the subsequent cipher operation, as no output bytes + * will be stored. + */ +extern unsigned int sec_PKCS7DecryptLength(sec_PKCS7CipherObject *obj, + unsigned int input_len, + PRBool final); +extern unsigned int sec_PKCS7EncryptLength(sec_PKCS7CipherObject *obj, + unsigned int input_len, + PRBool final); + +/* + * Decrypt a given length of input buffer (starting at "input" and + * containing "input_len" bytes), placing the decrypted bytes in + * "output" and storing the output length in "*output_len_p". + * "obj" is the return value from sec_PKCS7CreateDecryptObject. + * When "final" is true, this is the last of the data to be decrypted. + */ +extern SECStatus sec_PKCS7Decrypt(sec_PKCS7CipherObject *obj, + unsigned char *output, + unsigned int *output_len_p, + unsigned int max_output_len, + const unsigned char *input, + unsigned int input_len, + PRBool final); + +/* + * Encrypt a given length of input buffer (starting at "input" and + * containing "input_len" bytes), placing the encrypted bytes in + * "output" and storing the output length in "*output_len_p". + * "obj" is the return value from sec_PKCS7CreateEncryptObject. + * When "final" is true, this is the last of the data to be encrypted. + */ +extern SECStatus sec_PKCS7Encrypt(sec_PKCS7CipherObject *obj, + unsigned char *output, + unsigned int *output_len_p, + unsigned int max_output_len, + const unsigned char *input, + unsigned int input_len, + PRBool final); + +/************************************************************************/ +SEC_END_PROTOS + +#endif /* _P7LOCAL_H_ */ diff --git a/security/nss/lib/pkcs7/pkcs7.gyp b/security/nss/lib/pkcs7/pkcs7.gyp new file mode 100644 index 0000000000..8a73a2934d --- /dev/null +++ b/security/nss/lib/pkcs7/pkcs7.gyp @@ -0,0 +1,29 @@ +# 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/. +{ + 'includes': [ + '../../coreconf/config.gypi' + ], + 'targets': [ + { + 'target_name': 'pkcs7', + 'type': 'static_library', + 'sources': [ + 'certread.c', + 'p7common.c', + 'p7create.c', + 'p7decode.c', + 'p7encode.c', + 'p7local.c', + 'secmime.c' + ], + 'dependencies': [ + '<(DEPTH)/exports.gyp:nss_exports' + ] + } + ], + 'variables': { + 'module': 'nss' + } +}
\ No newline at end of file diff --git a/security/nss/lib/pkcs7/pkcs7t.h b/security/nss/lib/pkcs7/pkcs7t.h new file mode 100644 index 0000000000..4ef89027be --- /dev/null +++ b/security/nss/lib/pkcs7/pkcs7t.h @@ -0,0 +1,233 @@ +/* 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/. */ + +/* + * Header for pkcs7 types. + */ + +#ifndef _PKCS7T_H_ +#define _PKCS7T_H_ + +#include "plarena.h" + +#include "seccomon.h" +#include "secoidt.h" +#include "certt.h" +#include "secmodt.h" + +/* Opaque objects */ +typedef struct SEC_PKCS7DecoderContextStr SEC_PKCS7DecoderContext; +typedef struct SEC_PKCS7EncoderContextStr SEC_PKCS7EncoderContext; + +/* legacy defines that haven't been active for years */ +typedef void *(*SECKEYGetPasswordKey)(void *arg, void *handle); + +/* Non-opaque objects. NOTE, though: I want them to be treated as + * opaque as much as possible. If I could hide them completely, + * I would. (I tried, but ran into trouble that was taking me too + * much time to get out of.) I still intend to try to do so. + * In fact, the only type that "outsiders" should even *name* is + * SEC_PKCS7ContentInfo, and they should not reference its fields. + */ +/* rjr: PKCS #11 cert handling (pk11cert.c) does use SEC_PKCS7RecipientInfo's. + * This is because when we search the recipient list for the cert and key we + * want, we need to invert the order of the loops we used to have. The old + * loops were: + * + * For each recipient { + * find_cert = PK11_Find_AllCert(recipient->issuerSN); + * [which unrolls to... ] + * For each slot { + * Log into slot; + * search slot for cert; + * } + * } + * + * the new loop searchs all the recipients at once on a slot. this allows + * PKCS #11 to order slots in such a way that logout slots don't get checked + * if we can find the cert on a logged in slot. This eliminates lots of + * spurious password prompts when smart cards are installed... so why this + * comment? If you make SEC_PKCS7RecipientInfo completely opaque, you need + * to provide a non-opaque list of issuerSN's (the only field PKCS#11 needs + * and fix up pk11cert.c first. NOTE: Only S/MIME calls this special PKCS #11 + * function. + */ +typedef struct SEC_PKCS7ContentInfoStr SEC_PKCS7ContentInfo; +typedef struct SEC_PKCS7SignedDataStr SEC_PKCS7SignedData; +typedef struct SEC_PKCS7EncryptedContentInfoStr SEC_PKCS7EncryptedContentInfo; +typedef struct SEC_PKCS7EnvelopedDataStr SEC_PKCS7EnvelopedData; +typedef struct SEC_PKCS7SignedAndEnvelopedDataStr + SEC_PKCS7SignedAndEnvelopedData; +typedef struct SEC_PKCS7SignerInfoStr SEC_PKCS7SignerInfo; +typedef struct SEC_PKCS7RecipientInfoStr SEC_PKCS7RecipientInfo; +typedef struct SEC_PKCS7DigestedDataStr SEC_PKCS7DigestedData; +typedef struct SEC_PKCS7EncryptedDataStr SEC_PKCS7EncryptedData; +/* + * The following is not actually a PKCS7 type, but for now it is only + * used by PKCS7, so we have adopted it. If someone else *ever* needs + * it, its name should be changed and it should be moved out of here. + * Do not dare to use it without doing so! + */ +typedef struct SEC_PKCS7AttributeStr SEC_PKCS7Attribute; + +struct SEC_PKCS7ContentInfoStr { + PLArenaPool *poolp; /* local; not part of encoding */ + PRBool created; /* local; not part of encoding */ + int refCount; /* local; not part of encoding */ + SECOidData *contentTypeTag; /* local; not part of encoding */ + SECKEYGetPasswordKey pwfn; /* local; not part of encoding */ + void *pwfn_arg; /* local; not part of encoding */ + SECItem contentType; + union { + SECItem *data; + SEC_PKCS7DigestedData *digestedData; + SEC_PKCS7EncryptedData *encryptedData; + SEC_PKCS7EnvelopedData *envelopedData; + SEC_PKCS7SignedData *signedData; + SEC_PKCS7SignedAndEnvelopedData *signedAndEnvelopedData; + } content; +}; + +struct SEC_PKCS7SignedDataStr { + SECItem version; + SECAlgorithmID **digestAlgorithms; + SEC_PKCS7ContentInfo contentInfo; + SECItem **rawCerts; + CERTSignedCrl **crls; + SEC_PKCS7SignerInfo **signerInfos; + SECItem **digests; /* local; not part of encoding */ + CERTCertificate **certs; /* local; not part of encoding */ + CERTCertificateList **certLists; /* local; not part of encoding */ +}; +#define SEC_PKCS7_SIGNED_DATA_VERSION 1 /* what we *create* */ + +struct SEC_PKCS7EncryptedContentInfoStr { + SECOidData *contentTypeTag; /* local; not part of encoding */ + SECItem contentType; + SECAlgorithmID contentEncAlg; + SECItem encContent; + SECItem plainContent; /* local; not part of encoding */ + /* bytes not encrypted, but encoded */ + int keysize; /* local; not part of encoding */ + /* size of bulk encryption key + * (only used by creation code) */ + SECOidTag encalg; /* local; not part of encoding */ + /* oid tag of encryption algorithm + * (only used by creation code) */ +}; + +struct SEC_PKCS7EnvelopedDataStr { + SECItem version; + SEC_PKCS7RecipientInfo **recipientInfos; + SEC_PKCS7EncryptedContentInfo encContentInfo; +}; +#define SEC_PKCS7_ENVELOPED_DATA_VERSION 0 /* what we *create* */ + +struct SEC_PKCS7SignedAndEnvelopedDataStr { + SECItem version; + SEC_PKCS7RecipientInfo **recipientInfos; + SECAlgorithmID **digestAlgorithms; + SEC_PKCS7EncryptedContentInfo encContentInfo; + SECItem **rawCerts; + CERTSignedCrl **crls; + SEC_PKCS7SignerInfo **signerInfos; + SECItem **digests; /* local; not part of encoding */ + CERTCertificate **certs; /* local; not part of encoding */ + CERTCertificateList **certLists; /* local; not part of encoding */ + PK11SymKey *sigKey; /* local; not part of encoding */ +}; +#define SEC_PKCS7_SIGNED_AND_ENVELOPED_DATA_VERSION 1 /* what we *create* */ + +struct SEC_PKCS7SignerInfoStr { + SECItem version; + CERTIssuerAndSN *issuerAndSN; + SECAlgorithmID digestAlg; + SEC_PKCS7Attribute **authAttr; + SECAlgorithmID digestEncAlg; + SECItem encDigest; + SEC_PKCS7Attribute **unAuthAttr; + CERTCertificate *cert; /* local; not part of encoding */ + CERTCertificateList *certList; /* local; not part of encoding */ +}; +#define SEC_PKCS7_SIGNER_INFO_VERSION 1 /* what we *create* */ + +struct SEC_PKCS7RecipientInfoStr { + SECItem version; + CERTIssuerAndSN *issuerAndSN; + SECAlgorithmID keyEncAlg; + SECItem encKey; + CERTCertificate *cert; /* local; not part of encoding */ +}; +#define SEC_PKCS7_RECIPIENT_INFO_VERSION 0 /* what we *create* */ + +struct SEC_PKCS7DigestedDataStr { + SECItem version; + SECAlgorithmID digestAlg; + SEC_PKCS7ContentInfo contentInfo; + SECItem digest; +}; +#define SEC_PKCS7_DIGESTED_DATA_VERSION 0 /* what we *create* */ + +struct SEC_PKCS7EncryptedDataStr { + SECItem version; + SEC_PKCS7EncryptedContentInfo encContentInfo; +}; +#define SEC_PKCS7_ENCRYPTED_DATA_VERSION 0 /* what we *create* */ + +/* + * See comment above about this type not really belonging to PKCS7. + */ +struct SEC_PKCS7AttributeStr { + /* The following fields make up an encoded Attribute: */ + SECItem type; + SECItem **values; /* data may or may not be encoded */ + /* The following fields are not part of an encoded Attribute: */ + SECOidData *typeTag; + PRBool encoded; /* when true, values are encoded */ +}; + +/* + * Type of function passed to SEC_PKCS7Decode or SEC_PKCS7DecoderStart. + * If specified, this is where the content bytes (only) will be "sent" + * as they are recovered during the decoding. + * + * XXX Should just combine this with SEC_PKCS7EncoderContentCallback type + * and use a simpler, common name. + */ +typedef void (*SEC_PKCS7DecoderContentCallback)(void *arg, + const char *buf, + unsigned long len); + +/* + * Type of function passed to SEC_PKCS7Encode or SEC_PKCS7EncoderStart. + * This is where the encoded bytes will be "sent". + * + * XXX Should just combine this with SEC_PKCS7DecoderContentCallback type + * and use a simpler, common name. + */ +typedef void (*SEC_PKCS7EncoderOutputCallback)(void *arg, + const char *buf, + unsigned long len); + +/* + * Type of function passed to SEC_PKCS7Decode or SEC_PKCS7DecoderStart + * to retrieve the decryption key. This function is inteded to be + * used for EncryptedData content info's which do not have a key available + * in a certificate, etc. + */ +typedef PK11SymKey *(*SEC_PKCS7GetDecryptKeyCallback)(void *arg, + SECAlgorithmID *algid); + +/* + * Type of function passed to SEC_PKCS7Decode or SEC_PKCS7DecoderStart. + * This function in intended to be used to verify that decrypting a + * particular crypto algorithm is allowed. Content types which do not + * require decryption will not need the callback. If the callback + * is not specified for content types which require decryption, the + * decryption will be disallowed. + */ +typedef PRBool (*SEC_PKCS7DecryptionAllowedCallback)(SECAlgorithmID *algid, + PK11SymKey *bulkkey); + +#endif /* _PKCS7T_H_ */ diff --git a/security/nss/lib/pkcs7/secmime.c b/security/nss/lib/pkcs7/secmime.c new file mode 100644 index 0000000000..8a4afe45b7 --- /dev/null +++ b/security/nss/lib/pkcs7/secmime.c @@ -0,0 +1,800 @@ +/* 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/. */ + +/* + * Stuff specific to S/MIME policy and interoperability. + * Depends on PKCS7, but there should be no dependency the other way around. + */ + +#include "secmime.h" +#include "secoid.h" +#include "pk11func.h" +#include "ciferfam.h" /* for CIPHER_FAMILY symbols */ +#include "secasn1.h" +#include "secitem.h" +#include "cert.h" +#include "keyhi.h" +#include "secerr.h" + +typedef struct smime_cipher_map_struct { + unsigned long cipher; + SECOidTag algtag; + SECItem *parms; +} smime_cipher_map; + +/* + * These are macros because I think some subsequent parameters, + * like those for RC5, will want to use them, too, separately. + */ +#define SMIME_DER_INTVAL_16 SEC_ASN1_INTEGER, 0x01, 0x10 +#define SMIME_DER_INTVAL_40 SEC_ASN1_INTEGER, 0x01, 0x28 +#define SMIME_DER_INTVAL_64 SEC_ASN1_INTEGER, 0x01, 0x40 +#define SMIME_DER_INTVAL_128 SEC_ASN1_INTEGER, 0x02, 0x00, 0x80 + +#ifdef SMIME_DOES_RC5 /* will be needed; quiet unused warning for now */ +static unsigned char smime_int16[] = { SMIME_DER_INTVAL_16 }; +#endif +static unsigned char smime_int40[] = { SMIME_DER_INTVAL_40 }; +static unsigned char smime_int64[] = { SMIME_DER_INTVAL_64 }; +static unsigned char smime_int128[] = { SMIME_DER_INTVAL_128 }; + +static SECItem smime_rc2p40 = { siBuffer, smime_int40, sizeof(smime_int40) }; +static SECItem smime_rc2p64 = { siBuffer, smime_int64, sizeof(smime_int64) }; +static SECItem smime_rc2p128 = { siBuffer, smime_int128, sizeof(smime_int128) }; + +static smime_cipher_map smime_cipher_maps[] = { + { SMIME_RC2_CBC_40, SEC_OID_RC2_CBC, &smime_rc2p40 }, + { SMIME_RC2_CBC_64, SEC_OID_RC2_CBC, &smime_rc2p64 }, + { SMIME_RC2_CBC_128, SEC_OID_RC2_CBC, &smime_rc2p128 }, +#ifdef SMIME_DOES_RC5 + { SMIME_RC5PAD_64_16_40, SEC_OID_RC5_CBC_PAD, &smime_rc5p40 }, + { SMIME_RC5PAD_64_16_64, SEC_OID_RC5_CBC_PAD, &smime_rc5p64 }, + { SMIME_RC5PAD_64_16_128, SEC_OID_RC5_CBC_PAD, &smime_rc5p128 }, +#endif + { SMIME_DES_CBC_56, SEC_OID_DES_CBC, NULL }, + { SMIME_DES_EDE3_168, SEC_OID_DES_EDE3_CBC, NULL } +}; + +/* + * Note, the following value really just needs to be an upper bound + * on the ciphers. + */ +static const int smime_symmetric_count = sizeof(smime_cipher_maps) / sizeof(smime_cipher_map); + +static unsigned long *smime_prefs, *smime_newprefs; +static int smime_current_pref_index = 0; +static PRBool smime_prefs_complete = PR_FALSE; +static PRBool smime_prefs_changed = PR_TRUE; + +static unsigned long smime_policy_bits = 0; + +static int +smime_mapi_by_cipher(unsigned long cipher) +{ + int i; + + for (i = 0; i < smime_symmetric_count; i++) { + if (smime_cipher_maps[i].cipher == cipher) + break; + } + + if (i == smime_symmetric_count) + return -1; + + return i; +} + +/* + * this function locally records the user's preference + */ +SECStatus +SECMIME_EnableCipher(long which, int on) +{ + unsigned long mask; + + if (smime_newprefs == NULL || smime_prefs_complete) { + /* + * This is either the very first time, or we are starting over. + */ + smime_newprefs = (unsigned long *)PORT_ZAlloc(smime_symmetric_count * sizeof(*smime_newprefs)); + if (smime_newprefs == NULL) + return SECFailure; + smime_current_pref_index = 0; + smime_prefs_complete = PR_FALSE; + } + + mask = which & CIPHER_FAMILYID_MASK; + if (mask == CIPHER_FAMILYID_MASK) { + /* + * This call signifies that all preferences have been set. + * Move "newprefs" over, after checking first whether or + * not the new ones are different from the old ones. + */ + if (smime_prefs != NULL) { + if (PORT_Memcmp(smime_prefs, smime_newprefs, + smime_symmetric_count * sizeof(*smime_prefs)) == 0) + smime_prefs_changed = PR_FALSE; + else + smime_prefs_changed = PR_TRUE; + PORT_Free(smime_prefs); + } + + smime_prefs = smime_newprefs; + smime_prefs_complete = PR_TRUE; + return SECSuccess; + } + + PORT_Assert(mask == CIPHER_FAMILYID_SMIME); + if (mask != CIPHER_FAMILYID_SMIME) { + /* XXX set an error! */ + return SECFailure; + } + + if (on) { + PORT_Assert(smime_current_pref_index < smime_symmetric_count); + if (smime_current_pref_index >= smime_symmetric_count) { + /* XXX set an error! */ + return SECFailure; + } + + smime_newprefs[smime_current_pref_index++] = which; + } + + return SECSuccess; +} + +/* + * this function locally records the export policy + */ +SECStatus +SECMIME_SetPolicy(long which, int on) +{ + unsigned long mask; + + PORT_Assert((which & CIPHER_FAMILYID_MASK) == CIPHER_FAMILYID_SMIME); + if ((which & CIPHER_FAMILYID_MASK) != CIPHER_FAMILYID_SMIME) { + /* XXX set an error! */ + return SECFailure; + } + + which &= ~CIPHER_FAMILYID_MASK; + + PORT_Assert(which < 32); /* bits in the long */ + if (which >= 32) { + /* XXX set an error! */ + return SECFailure; + } + + mask = 1UL << which; + + if (on) { + smime_policy_bits |= mask; + } else { + smime_policy_bits &= ~mask; + } + + return SECSuccess; +} + +/* + * Based on the given algorithm (including its parameters, in some cases!) + * and the given key (may or may not be inspected, depending on the + * algorithm), find the appropriate policy algorithm specification + * and return it. If no match can be made, -1 is returned. + */ +static long +smime_policy_algorithm(SECAlgorithmID *algid, PK11SymKey *key) +{ + SECOidTag algtag; + + algtag = SECOID_GetAlgorithmTag(algid); + switch (algtag) { + case SEC_OID_RC2_CBC: { + unsigned int keylen_bits; + + keylen_bits = PK11_GetKeyStrength(key, algid); + switch (keylen_bits) { + case 40: + return SMIME_RC2_CBC_40; + case 64: + return SMIME_RC2_CBC_64; + case 128: + return SMIME_RC2_CBC_128; + default: + break; + } + } break; + case SEC_OID_DES_CBC: + return SMIME_DES_CBC_56; + case SEC_OID_DES_EDE3_CBC: + return SMIME_DES_EDE3_168; +#ifdef SMIME_DOES_RC5 + case SEC_OID_RC5_CBC_PAD: + PORT_Assert(0); /* XXX need to pull out parameters and match */ + break; +#endif + default: + break; + } + + return -1; +} + +static PRBool +smime_cipher_allowed(unsigned long which) +{ + unsigned long mask; + + which &= ~CIPHER_FAMILYID_MASK; + PORT_Assert(which < 32); /* bits per long (min) */ + if (which >= 32) + return PR_FALSE; + + mask = 1UL << which; + if ((mask & smime_policy_bits) == 0) + return PR_FALSE; + + return PR_TRUE; +} + +PRBool +SECMIME_DecryptionAllowed(SECAlgorithmID *algid, PK11SymKey *key) +{ + long which; + + which = smime_policy_algorithm(algid, key); + if (which < 0) + return PR_FALSE; + + return smime_cipher_allowed((unsigned long)which); +} + +/* + * Does the current policy allow *any* S/MIME encryption (or decryption)? + * + * This tells whether or not *any* S/MIME encryption can be done, + * according to policy. Callers may use this to do nicer user interface + * (say, greying out a checkbox so a user does not even try to encrypt + * a message when they are not allowed to) or for any reason they want + * to check whether S/MIME encryption (or decryption, for that matter) + * may be done. + * + * It takes no arguments. The return value is a simple boolean: + * PR_TRUE means encryption (or decryption) is *possible* + * (but may still fail due to other reasons, like because we cannot + * find all the necessary certs, etc.; PR_TRUE is *not* a guarantee) + * PR_FALSE means encryption (or decryption) is not permitted + * + * There are no errors from this routine. + */ +PRBool +SECMIME_EncryptionPossible(void) +{ + if (smime_policy_bits != 0) + return PR_TRUE; + + return PR_FALSE; +} + +/* + * XXX Would like the "parameters" field to be a SECItem *, but the + * encoder is having trouble with optional pointers to an ANY. Maybe + * once that is fixed, can change this back... + */ +typedef struct smime_capability_struct { + unsigned long cipher; /* local; not part of encoding */ + SECOidTag capIDTag; /* local; not part of encoding */ + SECItem capabilityID; + SECItem parameters; +} smime_capability; + +static const SEC_ASN1Template smime_capability_template[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(smime_capability) }, + { SEC_ASN1_OBJECT_ID, + offsetof(smime_capability, capabilityID) }, + { SEC_ASN1_OPTIONAL | SEC_ASN1_ANY, + offsetof(smime_capability, parameters) }, + { 0 } +}; + +static const SEC_ASN1Template smime_capabilities_template[] = { + { SEC_ASN1_SEQUENCE_OF, 0, smime_capability_template } +}; + +static void +smime_fill_capability(smime_capability *cap) +{ + unsigned long cipher; + SECOidTag algtag; + int i; + + algtag = SECOID_FindOIDTag(&(cap->capabilityID)); + + for (i = 0; i < smime_symmetric_count; i++) { + if (smime_cipher_maps[i].algtag != algtag) + continue; + /* + * XXX If SECITEM_CompareItem allowed NULLs as arguments (comparing + * 2 NULLs as equal and NULL and non-NULL as not equal), we could + * use that here instead of all of the following comparison code. + */ + if (cap->parameters.data != NULL) { + if (smime_cipher_maps[i].parms == NULL) + continue; + if (cap->parameters.len != smime_cipher_maps[i].parms->len) + continue; + if (PORT_Memcmp(cap->parameters.data, + smime_cipher_maps[i].parms->data, + cap->parameters.len) == 0) + break; + } else if (smime_cipher_maps[i].parms == NULL) { + break; + } + } + + if (i == smime_symmetric_count) + cipher = 0; + else + cipher = smime_cipher_maps[i].cipher; + + cap->cipher = cipher; + cap->capIDTag = algtag; +} + +static long +smime_choose_cipher(CERTCertificate *scert, CERTCertificate **rcerts) +{ + PLArenaPool *poolp; + long chosen_cipher; + int *cipher_abilities; + int *cipher_votes; + int strong_mapi; + int rcount, mapi, max; + + if (smime_policy_bits == 0) { + PORT_SetError(SEC_ERROR_BAD_EXPORT_ALGORITHM); + return -1; + } + + chosen_cipher = SMIME_RC2_CBC_40; /* the default, LCD */ + + poolp = PORT_NewArena(1024); /* XXX what is right value? */ + if (poolp == NULL) + goto done; + + cipher_abilities = (int *)PORT_ArenaZAlloc(poolp, + smime_symmetric_count * sizeof(int)); + if (cipher_abilities == NULL) + goto done; + + cipher_votes = (int *)PORT_ArenaZAlloc(poolp, + smime_symmetric_count * sizeof(int)); + if (cipher_votes == NULL) + goto done; + + /* + * XXX Should have a #define somewhere which specifies default + * strong cipher. (Or better, a way to configure.) + */ + + /* Make triple-DES the strong cipher. */ + strong_mapi = smime_mapi_by_cipher(SMIME_DES_EDE3_168); + + PORT_Assert(strong_mapi >= 0); + + for (rcount = 0; rcerts[rcount] != NULL; rcount++) { + SECItem *profile; + smime_capability **caps; + int capi, pref; + SECStatus dstat; + + pref = smime_symmetric_count; + profile = CERT_FindSMimeProfile(rcerts[rcount]); + if (profile != NULL && profile->data != NULL && profile->len > 0) { + caps = NULL; + dstat = SEC_QuickDERDecodeItem(poolp, &caps, + smime_capabilities_template, + profile); + if (dstat == SECSuccess && caps != NULL) { + for (capi = 0; caps[capi] != NULL; capi++) { + smime_fill_capability(caps[capi]); + mapi = smime_mapi_by_cipher(caps[capi]->cipher); + if (mapi >= 0) { + cipher_abilities[mapi]++; + cipher_votes[mapi] += pref; + --pref; + } + } + } + } else { + SECKEYPublicKey *key; + unsigned int pklen_bits; + + /* + * XXX This is probably only good for RSA keys. What I would + * really like is a function to just say; Is the public key in + * this cert an export-length key? Then I would not have to + * know things like the value 512, or the kind of key, or what + * a subjectPublicKeyInfo is, etc. + */ + key = CERT_ExtractPublicKey(rcerts[rcount]); + if (key != NULL) { + pklen_bits = SECKEY_PublicKeyStrength(key) * 8; + SECKEY_DestroyPublicKey(key); + + if (pklen_bits > 512) { + cipher_abilities[strong_mapi]++; + cipher_votes[strong_mapi] += pref; + } + } + } + if (profile != NULL) + SECITEM_FreeItem(profile, PR_TRUE); + } + + max = 0; + for (mapi = 0; mapi < smime_symmetric_count; mapi++) { + if (cipher_abilities[mapi] != rcount) + continue; + if (!smime_cipher_allowed(smime_cipher_maps[mapi].cipher)) + continue; + if (cipher_votes[mapi] > max) { + chosen_cipher = smime_cipher_maps[mapi].cipher; + max = cipher_votes[mapi]; + } /* XXX else if a tie, let scert break it? */ + } + +done: + if (poolp != NULL) + PORT_FreeArena(poolp, PR_FALSE); + + return chosen_cipher; +} + +/* + * XXX This is a hack for now to satisfy our current interface. + * Eventually, with more parameters needing to be specified, just + * looking up the keysize is not going to be sufficient. + */ +static int +smime_keysize_by_cipher(unsigned long which) +{ + int keysize; + + switch (which) { + case SMIME_RC2_CBC_40: + keysize = 40; + break; + case SMIME_RC2_CBC_64: + keysize = 64; + break; + case SMIME_RC2_CBC_128: + keysize = 128; + break; +#ifdef SMIME_DOES_RC5 + case SMIME_RC5PAD_64_16_40: + case SMIME_RC5PAD_64_16_64: + case SMIME_RC5PAD_64_16_128: + /* XXX See comment above; keysize is not enough... */ + PORT_Assert(0); + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + keysize = -1; + break; +#endif + case SMIME_DES_CBC_56: + case SMIME_DES_EDE3_168: + /* + * These are special; since the key size is fixed, we actually + * want to *avoid* specifying a key size. + */ + keysize = 0; + break; + default: + keysize = -1; + break; + } + + return keysize; +} + +/* + * Start an S/MIME encrypting context. + * + * "scert" is the cert for the sender. It will be checked for validity. + * "rcerts" are the certs for the recipients. They will also be checked. + * + * "certdb" is the cert database to use for verifying the certs. + * It can be NULL if a default database is available (like in the client). + * + * This function already does all of the stuff specific to S/MIME protocol + * and local policy; the return value just needs to be passed to + * SEC_PKCS7Encode() or to SEC_PKCS7EncoderStart() to create the encoded data, + * and finally to SEC_PKCS7DestroyContentInfo(). + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ +SEC_PKCS7ContentInfo * +SECMIME_CreateEncrypted(CERTCertificate *scert, + CERTCertificate **rcerts, + CERTCertDBHandle *certdb, + SECKEYGetPasswordKey pwfn, + void *pwfn_arg) +{ + SEC_PKCS7ContentInfo *cinfo; + long cipher; + SECOidTag encalg; + int keysize; + int mapi, rci; + + cipher = smime_choose_cipher(scert, rcerts); + if (cipher < 0) + return NULL; + + mapi = smime_mapi_by_cipher(cipher); + if (mapi < 0) + return NULL; + + /* + * XXX This is stretching it -- CreateEnvelopedData should probably + * take a cipher itself of some sort, because we cannot know what the + * future will bring in terms of parameters for each type of algorithm. + * For example, just an algorithm and keysize is *not* sufficient to + * fully specify the usage of RC5 (which also needs to know rounds and + * block size). Work this out into a better API! + */ + encalg = smime_cipher_maps[mapi].algtag; + keysize = smime_keysize_by_cipher(cipher); + if (keysize < 0) + return NULL; + + cinfo = SEC_PKCS7CreateEnvelopedData(scert, certUsageEmailRecipient, + certdb, encalg, keysize, + pwfn, pwfn_arg); + if (cinfo == NULL) + return NULL; + + for (rci = 0; rcerts[rci] != NULL; rci++) { + if (rcerts[rci] == scert) + continue; + if (SEC_PKCS7AddRecipient(cinfo, rcerts[rci], certUsageEmailRecipient, + NULL) != SECSuccess) { + SEC_PKCS7DestroyContentInfo(cinfo); + return NULL; + } + } + + return cinfo; +} + +static smime_capability **smime_capabilities; +static SECItem *smime_encoded_caps; + +static SECStatus +smime_init_caps(void) +{ + smime_capability *cap; + smime_cipher_map *map; + SECOidData *oiddata; + SECStatus rv; + int i; + + if (smime_encoded_caps != NULL && (!smime_prefs_changed)) + return SECSuccess; + + if (smime_encoded_caps != NULL) { + SECITEM_FreeItem(smime_encoded_caps, PR_TRUE); + smime_encoded_caps = NULL; + } + + if (smime_capabilities == NULL) { + smime_capabilities = (smime_capability **)PORT_ZAlloc( + (smime_symmetric_count + 1) * sizeof(smime_capability *)); + if (smime_capabilities == NULL) + return SECFailure; + } + + rv = SECFailure; + + /* + The process of creating the encoded PKCS7 cipher capability list + involves two basic steps: + + (a) Convert our internal representation of cipher preferences + (smime_prefs) into an array containing cipher OIDs and + parameter data (smime_capabilities). This step is + performed here. + + (b) Encode, using ASN.1, the cipher information in + smime_capabilities, leaving the encoded result in + smime_encoded_caps. + + (In the process of performing (a), Lisa put in some optimizations + which allow us to avoid needlessly re-populating elements in + smime_capabilities as we walk through smime_prefs.) + */ + for (i = 0; i < smime_current_pref_index; i++) { + int mapi; + + /* Get the next cipher preference in smime_prefs. */ + mapi = smime_mapi_by_cipher(smime_prefs[i]); + if (mapi < 0) + break; + + /* Find the corresponding entry in the cipher map. */ + PORT_Assert(mapi < smime_symmetric_count); + map = &(smime_cipher_maps[mapi]); + + /* + * Convert the next preference found in smime_prefs into an + * smime_capability. + */ + + cap = smime_capabilities[i]; + if (cap == NULL) { + cap = (smime_capability *)PORT_ZAlloc(sizeof(smime_capability)); + if (cap == NULL) + break; + smime_capabilities[i] = cap; + } else if (cap->cipher == smime_prefs[i]) { + continue; /* no change to this one */ + } + + cap->capIDTag = map->algtag; + oiddata = SECOID_FindOIDByTag(map->algtag); + if (oiddata == NULL) + break; + + if (cap->capabilityID.data != NULL) { + SECITEM_FreeItem(&(cap->capabilityID), PR_FALSE); + cap->capabilityID.data = NULL; + cap->capabilityID.len = 0; + } + + rv = SECITEM_CopyItem(NULL, &(cap->capabilityID), &(oiddata->oid)); + if (rv != SECSuccess) + break; + + if (map->parms == NULL) { + cap->parameters.data = NULL; + cap->parameters.len = 0; + } else { + cap->parameters.data = map->parms->data; + cap->parameters.len = map->parms->len; + } + + cap->cipher = smime_prefs[i]; + } + + if (i != smime_current_pref_index) + return rv; + + while (i < smime_symmetric_count) { + cap = smime_capabilities[i]; + if (cap != NULL) { + SECITEM_FreeItem(&(cap->capabilityID), PR_FALSE); + PORT_Free(cap); + } + smime_capabilities[i] = NULL; + i++; + } + smime_capabilities[i] = NULL; + + smime_encoded_caps = SEC_ASN1EncodeItem(NULL, NULL, &smime_capabilities, + smime_capabilities_template); + if (smime_encoded_caps == NULL) + return SECFailure; + + return SECSuccess; +} + +static SECStatus +smime_add_profile(CERTCertificate *cert, SEC_PKCS7ContentInfo *cinfo) +{ + PORT_Assert(smime_prefs_complete); + if (!smime_prefs_complete) + return SECFailure; + + /* For that matter, if capabilities haven't been initialized yet, + do so now. */ + if (smime_encoded_caps == NULL || smime_prefs_changed) { + SECStatus rv; + + rv = smime_init_caps(); + if (rv != SECSuccess) + return rv; + + PORT_Assert(smime_encoded_caps != NULL); + } + + return SEC_PKCS7AddSignedAttribute(cinfo, SEC_OID_PKCS9_SMIME_CAPABILITIES, + smime_encoded_caps); +} + +/* + * Start an S/MIME signing context. + * + * "scert" is the cert that will be used to sign the data. It will be + * checked for validity. + * + * "ecert" is the signer's encryption cert. If it is different from + * scert, then it will be included in the signed message so that the + * recipient can save it for future encryptions. + * + * "certdb" is the cert database to use for verifying the cert. + * It can be NULL if a default database is available (like in the client). + * + * "digestalg" names the digest algorithm (e.g. SEC_OID_SHA1). + * XXX There should be SECMIME functions for hashing, or the hashing should + * be built into this interface, which we would like because we would + * support more smartcards that way, and then this argument should go away.) + * + * "digest" is the actual digest of the data. It must be provided in + * the case of detached data or NULL if the content will be included. + * + * This function already does all of the stuff specific to S/MIME protocol + * and local policy; the return value just needs to be passed to + * SEC_PKCS7Encode() or to SEC_PKCS7EncoderStart() to create the encoded data, + * and finally to SEC_PKCS7DestroyContentInfo(). + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ + +SEC_PKCS7ContentInfo * +SECMIME_CreateSigned(CERTCertificate *scert, + CERTCertificate *ecert, + CERTCertDBHandle *certdb, + SECOidTag digestalg, + SECItem *digest, + SECKEYGetPasswordKey pwfn, + void *pwfn_arg) +{ + SEC_PKCS7ContentInfo *cinfo; + SECStatus rv; + + /* See note in header comment above about digestalg. */ + /* Doesn't explain this. PORT_Assert (digestalg == SEC_OID_SHA1); */ + + cinfo = SEC_PKCS7CreateSignedData(scert, certUsageEmailSigner, + certdb, digestalg, digest, + pwfn, pwfn_arg); + if (cinfo == NULL) + return NULL; + + if (SEC_PKCS7IncludeCertChain(cinfo, NULL) != SECSuccess) { + SEC_PKCS7DestroyContentInfo(cinfo); + return NULL; + } + + /* if the encryption cert and the signing cert differ, then include + * the encryption cert too. + */ + /* it is ok to compare the pointers since we ref count, and the same + * cert will always have the same pointer + */ + if ((ecert != NULL) && (ecert != scert)) { + rv = SEC_PKCS7AddCertificate(cinfo, ecert); + if (rv != SECSuccess) { + SEC_PKCS7DestroyContentInfo(cinfo); + return NULL; + } + } + /* + * Add the signing time. But if it fails for some reason, + * may as well not give up altogether -- just assert. + */ + rv = SEC_PKCS7AddSigningTime(cinfo); + PORT_Assert(rv == SECSuccess); + + /* + * Add the email profile. Again, if it fails for some reason, + * may as well not give up altogether -- just assert. + */ + rv = smime_add_profile(ecert, cinfo); + PORT_Assert(rv == SECSuccess); + + return cinfo; +} diff --git a/security/nss/lib/pkcs7/secmime.h b/security/nss/lib/pkcs7/secmime.h new file mode 100644 index 0000000000..683cd8db46 --- /dev/null +++ b/security/nss/lib/pkcs7/secmime.h @@ -0,0 +1,160 @@ +/* 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/. */ + +/* + * Header file for routines specific to S/MIME. Keep things that are pure + * pkcs7 out of here; this is for S/MIME policy, S/MIME interoperability, etc. + */ + +#ifndef _SECMIME_H_ +#define _SECMIME_H_ 1 + +#include "secpkcs7.h" + +/************************************************************************/ +SEC_BEGIN_PROTOS + +/* + * Initialize the local recording of the user S/MIME cipher preferences. + * This function is called once for each cipher, the order being + * important (first call records greatest preference, and so on). + * When finished, it is called with a "which" of CIPHER_FAMILID_MASK. + * If the function is called again after that, it is assumed that + * the preferences are being reset, and the old preferences are + * discarded. + * + * XXX This is for a particular user, and right now the storage is + * XXX local, static. The preference should be stored elsewhere to allow + * XXX for multiple uses of one library? How does SSL handle this; + * XXX it has something similar? + * + * - The "which" values are defined in ciferfam.h (the SMIME_* values, + * for example SMIME_DES_CBC_56). + * - If "on" is non-zero then the named cipher is enabled, otherwise + * it is disabled. (It is not necessary to call the function for + * ciphers that are disabled, however, as that is the default.) + * + * If the cipher preference is successfully recorded, SECSuccess + * is returned. Otherwise SECFailure is returned. The only errors + * are due to failure allocating memory or bad parameters/calls: + * SEC_ERROR_XXX ("which" is not in the S/MIME cipher family) + * SEC_ERROR_XXX (function is being called more times than there + * are known/expected ciphers) + */ +extern SECStatus SECMIME_EnableCipher(long which, int on); + +/* + * Initialize the local recording of the S/MIME policy. + * This function is called to enable/disable a particular cipher. + * (S/MIME encryption or decryption using a particular cipher is only + * allowed if that cipher is currently enabled.) At startup, all S/MIME + * ciphers are disabled. From that point, this function can be called + * to enable a cipher -- it is not necessary to call this to disable + * a cipher unless that cipher was previously, explicitly enabled via + * this function. + * + * XXX This is for a the current module, I think, so local, static storage + * XXX is okay. Is that correct, or could multiple uses of the same + * XXX library expect to operate under different policies? + * + * - The "which" values are defined in ciferfam.h (the SMIME_* values, + * for example SMIME_DES_CBC_56). + * - If "on" is non-zero then the named cipher is enabled, otherwise + * it is disabled. + * + * If the cipher is successfully enabled/disabled, SECSuccess is + * returned. Otherwise SECFailure is returned. The only errors + * are due to bad parameters: + * SEC_ERROR_XXX ("which" is not in the S/MIME cipher family) + * SEC_ERROR_XXX ("which" exceeds expected maximum cipher; this is + * really an internal error) + */ +extern SECStatus SECMIME_SetPolicy(long which, int on); + +/* + * Does the current policy allow S/MIME decryption of this particular + * algorithm and keysize? + */ +extern PRBool SECMIME_DecryptionAllowed(SECAlgorithmID *algid, PK11SymKey *key); + +/* + * Does the current policy allow *any* S/MIME encryption (or decryption)? + * + * This tells whether or not *any* S/MIME encryption can be done, + * according to policy. Callers may use this to do nicer user interface + * (say, greying out a checkbox so a user does not even try to encrypt + * a message when they are not allowed to) or for any reason they want + * to check whether S/MIME encryption (or decryption, for that matter) + * may be done. + * + * It takes no arguments. The return value is a simple boolean: + * PR_TRUE means encryption (or decryption) is *possible* + * (but may still fail due to other reasons, like because we cannot + * find all the necessary certs, etc.; PR_TRUE is *not* a guarantee) + * PR_FALSE means encryption (or decryption) is not permitted + * + * There are no errors from this routine. + */ +extern PRBool SECMIME_EncryptionPossible(void); + +/* + * Start an S/MIME encrypting context. + * + * "scert" is the cert for the sender. It will be checked for validity. + * "rcerts" are the certs for the recipients. They will also be checked. + * + * "certdb" is the cert database to use for verifying the certs. + * It can be NULL if a default database is available (like in the client). + * + * This function already does all of the stuff specific to S/MIME protocol + * and local policy; the return value just needs to be passed to + * SEC_PKCS7Encode() or to SEC_PKCS7EncoderStart() to create the encoded data, + * and finally to SEC_PKCS7DestroyContentInfo(). + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ +extern SEC_PKCS7ContentInfo *SECMIME_CreateEncrypted(CERTCertificate *scert, + CERTCertificate **rcerts, + CERTCertDBHandle *certdb, + SECKEYGetPasswordKey pwfn, + void *pwfn_arg); + +/* + * Start an S/MIME signing context. + * + * "scert" is the cert that will be used to sign the data. It will be + * checked for validity. + * + * "certdb" is the cert database to use for verifying the cert. + * It can be NULL if a default database is available (like in the client). + * + * "digestalg" names the digest algorithm. (It should be SEC_OID_SHA1; + * XXX There should be SECMIME functions for hashing, or the hashing should + * be built into this interface, which we would like because we would + * support more smartcards that way, and then this argument should go away.) + * + * "digest" is the actual digest of the data. It must be provided in + * the case of detached data or NULL if the content will be included. + * + * This function already does all of the stuff specific to S/MIME protocol + * and local policy; the return value just needs to be passed to + * SEC_PKCS7Encode() or to SEC_PKCS7EncoderStart() to create the encoded data, + * and finally to SEC_PKCS7DestroyContentInfo(). + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ +extern SEC_PKCS7ContentInfo *SECMIME_CreateSigned(CERTCertificate *scert, + CERTCertificate *ecert, + CERTCertDBHandle *certdb, + SECOidTag digestalg, + SECItem *digest, + SECKEYGetPasswordKey pwfn, + void *pwfn_arg); + +/************************************************************************/ +SEC_END_PROTOS + +#endif /* _SECMIME_H_ */ diff --git a/security/nss/lib/pkcs7/secpkcs7.h b/security/nss/lib/pkcs7/secpkcs7.h new file mode 100644 index 0000000000..4a88df1dfb --- /dev/null +++ b/security/nss/lib/pkcs7/secpkcs7.h @@ -0,0 +1,626 @@ +/* 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/. */ + +/* + * Interface to the PKCS7 implementation. + */ + +#ifndef _SECPKCS7_H_ +#define _SECPKCS7_H_ + +#include "seccomon.h" + +#include "secoidt.h" +#include "certt.h" +#include "keythi.h" +#include "hasht.h" +#include "pkcs7t.h" + +extern const SEC_ASN1Template sec_PKCS7ContentInfoTemplate[]; + +/************************************************************************/ +SEC_BEGIN_PROTOS + +/************************************************************************ + * Miscellaneous + ************************************************************************/ + +/* + * Returns the content type of the given contentInfo. + */ +extern SECOidTag SEC_PKCS7ContentType(SEC_PKCS7ContentInfo *cinfo); + +/* + * Destroy a PKCS7 contentInfo and all of its sub-pieces. + */ +extern void SEC_PKCS7DestroyContentInfo(SEC_PKCS7ContentInfo *contentInfo); + +/* + * Copy a PKCS7 contentInfo. A Destroy is needed on *each* copy. + */ +extern SEC_PKCS7ContentInfo * +SEC_PKCS7CopyContentInfo(SEC_PKCS7ContentInfo *contentInfo); + +/* + * Return a pointer to the actual content. In the case of those types + * which are encrypted, this returns the *plain* content. + */ +extern SECItem *SEC_PKCS7GetContent(SEC_PKCS7ContentInfo *cinfo); + +/************************************************************************ + * PKCS7 Decoding, Verification, etc.. + ************************************************************************/ + +extern SEC_PKCS7DecoderContext * +SEC_PKCS7DecoderStart(SEC_PKCS7DecoderContentCallback callback, + void *callback_arg, + SECKEYGetPasswordKey pwfn, void *pwfn_arg, + SEC_PKCS7GetDecryptKeyCallback decrypt_key_cb, + void *decrypt_key_cb_arg, + SEC_PKCS7DecryptionAllowedCallback decrypt_allowed_cb); + +extern SECStatus +SEC_PKCS7DecoderUpdate(SEC_PKCS7DecoderContext *p7dcx, + const char *buf, unsigned long len); + +extern SEC_PKCS7ContentInfo * +SEC_PKCS7DecoderFinish(SEC_PKCS7DecoderContext *p7dcx); + +/* Abort the underlying ASN.1 stream & set an error */ +void SEC_PKCS7DecoderAbort(SEC_PKCS7DecoderContext *p7dcx, int error); + +extern SEC_PKCS7ContentInfo * +SEC_PKCS7DecodeItem(SECItem *p7item, + SEC_PKCS7DecoderContentCallback cb, void *cb_arg, + SECKEYGetPasswordKey pwfn, void *pwfn_arg, + SEC_PKCS7GetDecryptKeyCallback decrypt_key_cb, + void *decrypt_key_cb_arg, + SEC_PKCS7DecryptionAllowedCallback decrypt_allowed_cb); + +extern PRBool SEC_PKCS7ContainsCertsOrCrls(SEC_PKCS7ContentInfo *cinfo); + +/* checks to see if the contents of the content info is + * empty. it so, PR_TRUE is returned. PR_FALSE, otherwise. + * + * minLen is used to specify a minimum size. if content size <= minLen, + * content is assumed empty. + */ +extern PRBool +SEC_PKCS7IsContentEmpty(SEC_PKCS7ContentInfo *cinfo, unsigned int minLen); + +extern PRBool SEC_PKCS7ContentIsEncrypted(SEC_PKCS7ContentInfo *cinfo); + +/* + * If the PKCS7 content has a signature (not just *could* have a signature) + * return true; false otherwise. This can/should be called before calling + * VerifySignature, which will always indicate failure if no signature is + * present, but that does not mean there even was a signature! + * Note that the content itself can be empty (detached content was sent + * another way); it is the presence of the signature that matters. + */ +extern PRBool SEC_PKCS7ContentIsSigned(SEC_PKCS7ContentInfo *cinfo); + +/* + * SEC_PKCS7VerifySignature + * Look at a PKCS7 contentInfo and check if the signature is good. + * The verification checks that the signing cert is valid and trusted + * for the purpose specified by "certusage". + * + * In addition, if "keepcerts" is true, add any new certificates found + * into our local database. + */ +extern PRBool SEC_PKCS7VerifySignature(SEC_PKCS7ContentInfo *cinfo, + SECCertUsage certusage, + PRBool keepcerts); + +/* + * SEC_PKCS7VerifyDetachedSignature + * Look at a PKCS7 contentInfo and check if the signature matches + * a passed-in digest (calculated, supposedly, from detached contents). + * The verification checks that the signing cert is valid and trusted + * for the purpose specified by "certusage". + * + * In addition, if "keepcerts" is true, add any new certificates found + * into our local database. + */ +extern PRBool SEC_PKCS7VerifyDetachedSignature(SEC_PKCS7ContentInfo *cinfo, + SECCertUsage certusage, + const SECItem *detached_digest, + HASH_HashType digest_type, + PRBool keepcerts); + +/* + * SEC_PKCS7VerifyDetachedSignatureAtTime + * Look at a PKCS7 contentInfo and check if the signature matches + * a passed-in digest (calculated, supposedly, from detached contents). + * The verification checks that the signing cert is valid and trusted + * for the purpose specified by "certusage" at time "atTime". + * + * In addition, if "keepcerts" is true, add any new certificates found + * into our local database. + */ +extern PRBool +SEC_PKCS7VerifyDetachedSignatureAtTime(SEC_PKCS7ContentInfo *cinfo, + SECCertUsage certusage, + const SECItem *detached_digest, + HASH_HashType digest_type, + PRBool keepcerts, + PRTime atTime); + +/* + * SEC_PKCS7GetSignerCommonName, SEC_PKCS7GetSignerEmailAddress + * The passed-in contentInfo is espected to be Signed, and these + * functions return the specified portion of the full signer name. + * + * Returns a pointer to allocated memory, which must be freed. + * A NULL return value is an error. + */ +extern char *SEC_PKCS7GetSignerCommonName(SEC_PKCS7ContentInfo *cinfo); +extern char *SEC_PKCS7GetSignerEmailAddress(SEC_PKCS7ContentInfo *cinfo); + +/* + * Return the the signing time, in UTCTime format, of a PKCS7 contentInfo. + */ +extern SECItem *SEC_PKCS7GetSigningTime(SEC_PKCS7ContentInfo *cinfo); + +/************************************************************************ + * PKCS7 Creation and Encoding. + ************************************************************************/ + +/* + * Start a PKCS7 signing context. + * + * "cert" is the cert that will be used to sign the data. It will be + * checked for validity. + * + * "certusage" describes the signing usage (e.g. certUsageEmailSigner) + * XXX Maybe SECCertUsage should be split so that our caller just says + * "email" and *we* add the "signing" part -- otherwise our caller + * could be lying about the usage; we do not want to allow encryption + * certs for signing or vice versa. + * + * "certdb" is the cert database to use for verifying the cert. + * It can be NULL if a default database is available (like in the client). + * + * "digestalg" names the digest algorithm (e.g. SEC_OID_SHA1). + * + * "digest" is the actual digest of the data. It must be provided in + * the case of detached data or NULL if the content will be included. + * + * The return value can be passed to functions which add things to + * it like attributes, then eventually to SEC_PKCS7Encode() or to + * SEC_PKCS7EncoderStart() to create the encoded data, and finally to + * SEC_PKCS7DestroyContentInfo(). + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ +extern SEC_PKCS7ContentInfo * +SEC_PKCS7CreateSignedData(CERTCertificate *cert, + SECCertUsage certusage, + CERTCertDBHandle *certdb, + SECOidTag digestalg, + SECItem *digest, + SECKEYGetPasswordKey pwfn, void *pwfn_arg); + +/* + * Create a PKCS7 certs-only container. + * + * "cert" is the (first) cert that will be included. + * + * "include_chain" specifies whether the entire chain for "cert" should + * be included. + * + * "certdb" is the cert database to use for finding the chain. + * It can be NULL in when "include_chain" is false, or when meaning + * use the default database. + * + * More certs and chains can be added via AddCertficate and AddCertChain. + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ +extern SEC_PKCS7ContentInfo * +SEC_PKCS7CreateCertsOnly(CERTCertificate *cert, + PRBool include_chain, + CERTCertDBHandle *certdb); + +/* + * Start a PKCS7 enveloping context. + * + * "cert" is the cert for the recipient. It will be checked for validity. + * + * "certusage" describes the encryption usage (e.g. certUsageEmailRecipient) + * XXX Maybe SECCertUsage should be split so that our caller just says + * "email" and *we* add the "recipient" part -- otherwise our caller + * could be lying about the usage; we do not want to allow encryption + * certs for signing or vice versa. + * + * "certdb" is the cert database to use for verifying the cert. + * It can be NULL if a default database is available (like in the client). + * + * "encalg" specifies the bulk encryption algorithm to use (e.g. SEC_OID_RC2). + * + * "keysize" specifies the bulk encryption key size, in bits. + * + * The return value can be passed to functions which add things to + * it like more recipients, then eventually to SEC_PKCS7Encode() or to + * SEC_PKCS7EncoderStart() to create the encoded data, and finally to + * SEC_PKCS7DestroyContentInfo(). + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ +extern SEC_PKCS7ContentInfo * +SEC_PKCS7CreateEnvelopedData(CERTCertificate *cert, + SECCertUsage certusage, + CERTCertDBHandle *certdb, + SECOidTag encalg, + int keysize, + SECKEYGetPasswordKey pwfn, void *pwfn_arg); + +/* + * XXX There will be a similar routine for creating signedAndEnvelopedData. + * But its parameters will be different and I have no plans to implement + * it any time soon because we have no current need for it. + */ + +/* + * Create an empty PKCS7 data content info. + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ +extern SEC_PKCS7ContentInfo *SEC_PKCS7CreateData(void); + +/* + * Create an empty PKCS7 encrypted content info. + * + * "algorithm" specifies the bulk encryption algorithm to use. + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ +extern SEC_PKCS7ContentInfo * +SEC_PKCS7CreateEncryptedData(SECOidTag algorithm, int keysize, + SECKEYGetPasswordKey pwfn, void *pwfn_arg); + +/* + * Create an empty PKCS7 encrypted content info. + * + * Similar to SEC_PKCS7CreateEncryptedData(), but this is capable of + * creating encrypted content for PKCS #5 v2 algorithms. + * + * "pbe_algorithm" specifies the PBE algorithm to use. + * "cipher_algorithm" specifies the bulk encryption algorithm to use. + * "prf_algorithm" specifies the PRF algorithm which pbe_algorithm uses. + * + * An error results in a return value of NULL and an error set. + * (Retrieve specific errors via PORT_GetError()/XP_GetError().) + */ +extern SEC_PKCS7ContentInfo * +SEC_PKCS7CreateEncryptedDataWithPBEV2(SECOidTag pbe_algorithm, + SECOidTag cipher_algorithm, + SECOidTag prf_algorithm, + int keysize, + SECKEYGetPasswordKey pwfn, void *pwfn_arg); + +/* + * All of the following things return SECStatus to signal success or failure. + * Failure should have a more specific error status available via + * PORT_GetError()/XP_GetError(). + */ + +/* + * Add the specified attribute to the authenticated (i.e. signed) attributes + * of "cinfo" -- "oidtag" describes the attribute and "value" is the + * value to be associated with it. NOTE! "value" must already be encoded; + * no interpretation of "oidtag" is done. Also, it is assumed that this + * signedData has only one signer -- if we ever need to add attributes + * when there is more than one signature, we need a way to specify *which* + * signature should get the attribute. + * + * XXX Technically, a signed attribute can have multiple values; if/when + * we ever need to support an attribute which takes multiple values, we + * either need to change this interface or create an AddSignedAttributeValue + * which can be called subsequently, and would then append a value. + * + * "cinfo" should be of type signedData (the only kind of pkcs7 data + * that is allowed authenticated attributes); SECFailure will be returned + * if it is not. + */ +extern SECStatus SEC_PKCS7AddSignedAttribute(SEC_PKCS7ContentInfo *cinfo, + SECOidTag oidtag, + SECItem *value); + +/* + * Add "cert" and its entire chain to the set of certs included in "cinfo". + * + * "certdb" is the cert database to use for finding the chain. + * It can be NULL, meaning use the default database. + * + * "cinfo" should be of type signedData or signedAndEnvelopedData; + * SECFailure will be returned if it is not. + */ +extern SECStatus SEC_PKCS7AddCertChain(SEC_PKCS7ContentInfo *cinfo, + CERTCertificate *cert, + CERTCertDBHandle *certdb); + +/* + * Add "cert" to the set of certs included in "cinfo". + * + * "cinfo" should be of type signedData or signedAndEnvelopedData; + * SECFailure will be returned if it is not. + */ +extern SECStatus SEC_PKCS7AddCertificate(SEC_PKCS7ContentInfo *cinfo, + CERTCertificate *cert); + +/* + * Add another recipient to an encrypted message. + * + * "cinfo" should be of type envelopedData or signedAndEnvelopedData; + * SECFailure will be returned if it is not. + * + * "cert" is the cert for the recipient. It will be checked for validity. + * + * "certusage" describes the encryption usage (e.g. certUsageEmailRecipient) + * XXX Maybe SECCertUsage should be split so that our caller just says + * "email" and *we* add the "recipient" part -- otherwise our caller + * could be lying about the usage; we do not want to allow encryption + * certs for signing or vice versa. + * + * "certdb" is the cert database to use for verifying the cert. + * It can be NULL if a default database is available (like in the client). + */ +extern SECStatus SEC_PKCS7AddRecipient(SEC_PKCS7ContentInfo *cinfo, + CERTCertificate *cert, + SECCertUsage certusage, + CERTCertDBHandle *certdb); + +/* + * Add the signing time to the authenticated (i.e. signed) attributes + * of "cinfo". This is expected to be included in outgoing signed + * messages for email (S/MIME) but is likely useful in other situations. + * + * This should only be added once; a second call will either do + * nothing or replace an old signing time with a newer one. + * + * XXX This will probably just shove the current time into "cinfo" + * but it will not actually get signed until the entire item is + * processed for encoding. Is this (expected to be small) delay okay? + * + * "cinfo" should be of type signedData (the only kind of pkcs7 data + * that is allowed authenticated attributes); SECFailure will be returned + * if it is not. + */ +extern SECStatus SEC_PKCS7AddSigningTime(SEC_PKCS7ContentInfo *cinfo); + +/* + * Add the signer's symmetric capabilities to the authenticated + * (i.e. signed) attributes of "cinfo". This is expected to be + * included in outgoing signed messages for email (S/MIME). + * + * This can only be added once; a second call will return SECFailure. + * + * "cinfo" should be of type signedData or signedAndEnvelopedData; + * SECFailure will be returned if it is not. + */ +extern SECStatus SEC_PKCS7AddSymmetricCapabilities(SEC_PKCS7ContentInfo *cinfo); + +/* + * Mark that the signer's certificate and its issuing chain should + * be included in the encoded data. This is expected to be used + * in outgoing signed messages for email (S/MIME). + * + * "certdb" is the cert database to use for finding the chain. + * It can be NULL, meaning use the default database. + * + * "cinfo" should be of type signedData or signedAndEnvelopedData; + * SECFailure will be returned if it is not. + */ +extern SECStatus SEC_PKCS7IncludeCertChain(SEC_PKCS7ContentInfo *cinfo, + CERTCertDBHandle *certdb); + +/* + * Set the content; it will be included and also hashed and/or encrypted + * as appropriate. This is for in-memory content (expected to be "small") + * that will be included in the PKCS7 object. All others should stream the + * content through when encoding (see SEC_PKCS7Encoder{Start,Update,Finish}). + * + * "buf" points to data of length "len"; it will be copied. + */ +extern SECStatus SEC_PKCS7SetContent(SEC_PKCS7ContentInfo *cinfo, + const char *buf, unsigned long len); + +/* + * Encode a PKCS7 object, in one shot. All necessary components + * of the object must already be specified. Either the data has + * already been included (via SetContent), or the data is detached, + * or there is no data at all (certs-only). + * + * "cinfo" specifies the object to be encoded. + * + * "outputfn" is where the encoded bytes will be passed. + * + * "outputarg" is an opaque argument to the above callback. + * + * "bulkkey" specifies the bulk encryption key to use. This argument + * can be NULL if no encryption is being done, or if the bulk key should + * be generated internally (usually the case for EnvelopedData but never + * for EncryptedData, which *must* provide a bulk encryption key). + * + * "pwfn" is a callback for getting the password which protects the + * private key of the signer. This argument can be NULL if it is known + * that no signing is going to be done. + * + * "pwfnarg" is an opaque argument to the above callback. + */ +extern SECStatus SEC_PKCS7Encode(SEC_PKCS7ContentInfo *cinfo, + SEC_PKCS7EncoderOutputCallback outputfn, + void *outputarg, + PK11SymKey *bulkkey, + SECKEYGetPasswordKey pwfn, + void *pwfnarg); + +/* + * Encode a PKCS7 object, in one shot. All necessary components + * of the object must already be specified. Either the data has + * already been included (via SetContent), or the data is detached, + * or there is no data at all (certs-only). The output, rather than + * being passed to an output function as is done above, is all put + * into a SECItem. + * + * "pool" specifies a pool from which to allocate the result. + * It can be NULL, in which case memory is allocated generically. + * + * "dest" specifies a SECItem in which to put the result data. + * It can be NULL, in which case the entire item is allocated, too. + * + * "cinfo" specifies the object to be encoded. + * + * "bulkkey" specifies the bulk encryption key to use. This argument + * can be NULL if no encryption is being done, or if the bulk key should + * be generated internally (usually the case for EnvelopedData but never + * for EncryptedData, which *must* provide a bulk encryption key). + * + * "pwfn" is a callback for getting the password which protects the + * private key of the signer. This argument can be NULL if it is known + * that no signing is going to be done. + * + * "pwfnarg" is an opaque argument to the above callback. + */ +extern SECItem *SEC_PKCS7EncodeItem(PLArenaPool *pool, + SECItem *dest, + SEC_PKCS7ContentInfo *cinfo, + PK11SymKey *bulkkey, + SECKEYGetPasswordKey pwfn, + void *pwfnarg); + +/* + * For those who want to simply point to the pkcs7 contentInfo ASN.1 + * template, and *not* call the encoding functions directly, the + * following function can be used -- after it is called, the entire + * PKCS7 contentInfo is ready to be encoded. + */ +extern SECStatus SEC_PKCS7PrepareForEncode(SEC_PKCS7ContentInfo *cinfo, + PK11SymKey *bulkkey, + SECKEYGetPasswordKey pwfn, + void *pwfnarg); + +/* + * Start the process of encoding a PKCS7 object. The first part of + * the encoded object will be passed to the output function right away; + * after that it is expected that SEC_PKCS7EncoderUpdate will be called, + * streaming in the actual content that is getting included as well as + * signed or encrypted (or both). + * + * "cinfo" specifies the object to be encoded. + * + * "outputfn" is where the encoded bytes will be passed. + * + * "outputarg" is an opaque argument to the above callback. + * + * "bulkkey" specifies the bulk encryption key to use. This argument + * can be NULL if no encryption is being done, or if the bulk key should + * be generated internally (usually the case for EnvelopedData but never + * for EncryptedData, which *must* provide a bulk encryption key). + * + * Returns an object to be passed to EncoderUpdate and EncoderFinish. + */ +extern SEC_PKCS7EncoderContext * +SEC_PKCS7EncoderStart(SEC_PKCS7ContentInfo *cinfo, + SEC_PKCS7EncoderOutputCallback outputfn, + void *outputarg, + PK11SymKey *bulkkey); + +/* + * Encode more contents, hashing and/or encrypting along the way. + */ +extern SECStatus SEC_PKCS7EncoderUpdate(SEC_PKCS7EncoderContext *p7ecx, + const char *buf, + unsigned long len); + +/* + * No more contents; finish the signature creation, if appropriate, + * and then the encoding. + * + * "pwfn" is a callback for getting the password which protects the + * signer's private key. This argument can be NULL if it is known + * that no signing is going to be done. + * + * "pwfnarg" is an opaque argument to the above callback. + */ +extern SECStatus SEC_PKCS7EncoderFinish(SEC_PKCS7EncoderContext *p7ecx, + SECKEYGetPasswordKey pwfn, + void *pwfnarg); + +/* Abort the underlying ASN.1 stream & set an error */ +void SEC_PKCS7EncoderAbort(SEC_PKCS7EncoderContext *p7dcx, int error); + +/* retrieve the algorithm ID used to encrypt the content info + * for encrypted and enveloped data. The SECAlgorithmID pointer + * returned needs to be freed as it is a copy of the algorithm + * id in the content info. + */ +extern SECAlgorithmID * +SEC_PKCS7GetEncryptionAlgorithm(SEC_PKCS7ContentInfo *cinfo); + +/* the content of an encrypted data content info is encrypted. + * it is assumed that for encrypted data, that the data has already + * been set and is in the "plainContent" field of the content info. + * + * cinfo is the content info to encrypt + * + * key is the key with which to perform the encryption. if the + * algorithm is a password based encryption algorithm, the + * key is actually a password which will be processed per + * PKCS #5. + * + * in the event of an error, SECFailure is returned. SECSuccess + * indicates a success. + */ +extern SECStatus +SEC_PKCS7EncryptContents(PLArenaPool *poolp, + SEC_PKCS7ContentInfo *cinfo, + SECItem *key, + void *wincx); + +/* the content of an encrypted data content info is decrypted. + * it is assumed that for encrypted data, that the data has already + * been set and is in the "encContent" field of the content info. + * + * cinfo is the content info to decrypt + * + * key is the key with which to perform the decryption. if the + * algorithm is a password based encryption algorithm, the + * key is actually a password which will be processed per + * PKCS #5. + * + * in the event of an error, SECFailure is returned. SECSuccess + * indicates a success. + */ +extern SECStatus +SEC_PKCS7DecryptContents(PLArenaPool *poolp, + SEC_PKCS7ContentInfo *cinfo, + SECItem *key, + void *wincx); + +/* retrieve the certificate list from the content info. the list + * is a pointer to the list in the content info. this should not + * be deleted or freed in any way short of calling + * SEC_PKCS7DestroyContentInfo + */ +extern SECItem ** +SEC_PKCS7GetCertificateList(SEC_PKCS7ContentInfo *cinfo); + +/* Returns the key length (in bits) of the algorithm used to encrypt + this object. Returns 0 if it's not encrypted, or the key length is + irrelevant. */ +extern int +SEC_PKCS7GetKeyLength(SEC_PKCS7ContentInfo *cinfo); + +/************************************************************************/ +SEC_END_PROTOS + +#endif /* _SECPKCS7_H_ */ |