Source code

Revision control

Copy as Markdown

Other Tools

/* 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;
}