diff options
Diffstat (limited to 'security/nss/lib/pkcs7/p7create.c')
-rw-r--r-- | security/nss/lib/pkcs7/p7create.c | 1306 |
1 files changed, 1306 insertions, 0 deletions
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; +} |