diff options
Diffstat (limited to 'security/nss/lib/smime/cmssiginfo.c')
-rw-r--r-- | security/nss/lib/smime/cmssiginfo.c | 1024 |
1 files changed, 1024 insertions, 0 deletions
diff --git a/security/nss/lib/smime/cmssiginfo.c b/security/nss/lib/smime/cmssiginfo.c new file mode 100644 index 000000000..ce4f87c0a --- /dev/null +++ b/security/nss/lib/smime/cmssiginfo.c @@ -0,0 +1,1024 @@ +/* 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/. */ + +/* + * CMS signerInfo methods. + */ + +#include "cmslocal.h" + +#include "cert.h" +#include "key.h" +#include "secasn1.h" +#include "secitem.h" +#include "secoid.h" +#include "pk11func.h" +#include "prtime.h" +#include "secerr.h" +#include "secder.h" +#include "cryptohi.h" + +#include "smime.h" + +/* ============================================================================= + * SIGNERINFO + */ +NSSCMSSignerInfo * +nss_cmssignerinfo_create(NSSCMSMessage *cmsg, NSSCMSSignerIDSelector type, + CERTCertificate *cert, SECItem *subjKeyID, SECKEYPublicKey *pubKey, + SECKEYPrivateKey *signingKey, SECOidTag digestalgtag); + +NSSCMSSignerInfo * +NSS_CMSSignerInfo_CreateWithSubjKeyID(NSSCMSMessage *cmsg, SECItem *subjKeyID, + SECKEYPublicKey *pubKey, + SECKEYPrivateKey *signingKey, SECOidTag digestalgtag) +{ + return nss_cmssignerinfo_create(cmsg, NSSCMSSignerID_SubjectKeyID, NULL, + subjKeyID, pubKey, signingKey, digestalgtag); +} + +NSSCMSSignerInfo * +NSS_CMSSignerInfo_Create(NSSCMSMessage *cmsg, CERTCertificate *cert, SECOidTag digestalgtag) +{ + return nss_cmssignerinfo_create(cmsg, NSSCMSSignerID_IssuerSN, cert, NULL, + NULL, NULL, digestalgtag); +} + +NSSCMSSignerInfo * +nss_cmssignerinfo_create(NSSCMSMessage *cmsg, NSSCMSSignerIDSelector type, + CERTCertificate *cert, SECItem *subjKeyID, SECKEYPublicKey *pubKey, + SECKEYPrivateKey *signingKey, SECOidTag digestalgtag) +{ + void *mark; + NSSCMSSignerInfo *signerinfo; + int version; + PLArenaPool *poolp; + SECStatus rv; + + poolp = cmsg->poolp; + + mark = PORT_ArenaMark(poolp); + + signerinfo = (NSSCMSSignerInfo *)PORT_ArenaZAlloc(poolp, sizeof(NSSCMSSignerInfo)); + if (signerinfo == NULL) { + PORT_ArenaRelease(poolp, mark); + return NULL; + } + + signerinfo->cmsg = cmsg; + + switch (type) { + case NSSCMSSignerID_IssuerSN: + signerinfo->signerIdentifier.identifierType = NSSCMSSignerID_IssuerSN; + if ((signerinfo->cert = CERT_DupCertificate(cert)) == NULL) + goto loser; + if ((signerinfo->signerIdentifier.id.issuerAndSN = CERT_GetCertIssuerAndSN(poolp, cert)) == NULL) + goto loser; + break; + case NSSCMSSignerID_SubjectKeyID: + signerinfo->signerIdentifier.identifierType = NSSCMSSignerID_SubjectKeyID; + PORT_Assert(subjKeyID); + if (!subjKeyID) + goto loser; + + signerinfo->signerIdentifier.id.subjectKeyID = PORT_ArenaNew(poolp, SECItem); + rv = SECITEM_CopyItem(poolp, signerinfo->signerIdentifier.id.subjectKeyID, + subjKeyID); + if (rv != SECSuccess) { + goto loser; + } + signerinfo->signingKey = SECKEY_CopyPrivateKey(signingKey); + if (!signerinfo->signingKey) + goto loser; + signerinfo->pubKey = SECKEY_CopyPublicKey(pubKey); + if (!signerinfo->pubKey) + goto loser; + break; + default: + goto loser; + } + + /* set version right now */ + version = NSS_CMS_SIGNER_INFO_VERSION_ISSUERSN; + /* RFC2630 5.3 "version is the syntax version number. If the .... " */ + if (signerinfo->signerIdentifier.identifierType == NSSCMSSignerID_SubjectKeyID) + version = NSS_CMS_SIGNER_INFO_VERSION_SUBJKEY; + (void)SEC_ASN1EncodeInteger(poolp, &(signerinfo->version), (long)version); + + if (SECOID_SetAlgorithmID(poolp, &signerinfo->digestAlg, digestalgtag, NULL) != SECSuccess) + goto loser; + + PORT_ArenaUnmark(poolp, mark); + return signerinfo; + +loser: + PORT_ArenaRelease(poolp, mark); + return NULL; +} + +/* + * NSS_CMSSignerInfo_Destroy - destroy a SignerInfo data structure + */ +void +NSS_CMSSignerInfo_Destroy(NSSCMSSignerInfo *si) +{ + if (si->cert != NULL) + CERT_DestroyCertificate(si->cert); + + if (si->certList != NULL) + CERT_DestroyCertificateList(si->certList); + + /* XXX storage ??? */ +} + +/* + * NSS_CMSSignerInfo_Sign - sign something + * + */ +SECStatus +NSS_CMSSignerInfo_Sign(NSSCMSSignerInfo *signerinfo, SECItem *digest, + SECItem *contentType) +{ + CERTCertificate *cert; + SECKEYPrivateKey *privkey = NULL; + SECOidTag digestalgtag; + SECOidTag pubkAlgTag; + SECItem signature = { 0 }; + SECStatus rv; + PLArenaPool *poolp, *tmppoolp = NULL; + SECAlgorithmID *algID, freeAlgID; + CERTSubjectPublicKeyInfo *spki; + + PORT_Assert(digest != NULL); + + poolp = signerinfo->cmsg->poolp; + + switch (signerinfo->signerIdentifier.identifierType) { + case NSSCMSSignerID_IssuerSN: + cert = signerinfo->cert; + + privkey = PK11_FindKeyByAnyCert(cert, signerinfo->cmsg->pwfn_arg); + if (privkey == NULL) + goto loser; + algID = &cert->subjectPublicKeyInfo.algorithm; + break; + case NSSCMSSignerID_SubjectKeyID: + privkey = signerinfo->signingKey; + signerinfo->signingKey = NULL; + spki = SECKEY_CreateSubjectPublicKeyInfo(signerinfo->pubKey); + SECKEY_DestroyPublicKey(signerinfo->pubKey); + signerinfo->pubKey = NULL; + SECOID_CopyAlgorithmID(NULL, &freeAlgID, &spki->algorithm); + SECKEY_DestroySubjectPublicKeyInfo(spki); + algID = &freeAlgID; + break; + default: + goto loser; + } + digestalgtag = NSS_CMSSignerInfo_GetDigestAlgTag(signerinfo); + /* + * XXX I think there should be a cert-level interface for this, + * so that I do not have to know about subjectPublicKeyInfo... + */ + pubkAlgTag = SECOID_GetAlgorithmTag(algID); + if (signerinfo->signerIdentifier.identifierType == NSSCMSSignerID_SubjectKeyID) { + SECOID_DestroyAlgorithmID(&freeAlgID, PR_FALSE); + } + + if (signerinfo->authAttr != NULL) { + SECOidTag signAlgTag; + SECItem encoded_attrs; + + /* find and fill in the message digest attribute. */ + rv = NSS_CMSAttributeArray_SetAttr(poolp, &(signerinfo->authAttr), + SEC_OID_PKCS9_MESSAGE_DIGEST, digest, PR_FALSE); + if (rv != SECSuccess) + goto loser; + + if (contentType != NULL) { + /* if the caller wants us to, find and fill in the content type attribute. */ + rv = NSS_CMSAttributeArray_SetAttr(poolp, &(signerinfo->authAttr), + SEC_OID_PKCS9_CONTENT_TYPE, contentType, PR_FALSE); + if (rv != SECSuccess) + goto loser; + } + + if ((tmppoolp = PORT_NewArena(1024)) == NULL) { + PORT_SetError(SEC_ERROR_NO_MEMORY); + goto loser; + } + + /* + * 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. + */ + if (NSS_CMSAttributeArray_Reorder(signerinfo->authAttr) != SECSuccess) + goto loser; + + encoded_attrs.data = NULL; + encoded_attrs.len = 0; + if (NSS_CMSAttributeArray_Encode(tmppoolp, &(signerinfo->authAttr), + &encoded_attrs) == NULL) + goto loser; + + signAlgTag = SEC_GetSignatureAlgorithmOidTag(privkey->keyType, + digestalgtag); + if (signAlgTag == SEC_OID_UNKNOWN) { + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + goto loser; + } + + rv = SEC_SignData(&signature, encoded_attrs.data, encoded_attrs.len, + privkey, signAlgTag); + PORT_FreeArena(tmppoolp, PR_FALSE); /* awkward memory management :-( */ + tmppoolp = 0; + } else { + rv = SGN_Digest(privkey, digestalgtag, &signature, digest); + } + SECKEY_DestroyPrivateKey(privkey); + privkey = NULL; + + if (rv != SECSuccess) + goto loser; + + if (SECITEM_CopyItem(poolp, &(signerinfo->encDigest), &signature) != SECSuccess) + goto loser; + + SECITEM_FreeItem(&signature, PR_FALSE); + + if (SECOID_SetAlgorithmID(poolp, &(signerinfo->digestEncAlg), pubkAlgTag, + NULL) != SECSuccess) + goto loser; + + return SECSuccess; + +loser: + if (signature.len != 0) + SECITEM_FreeItem(&signature, PR_FALSE); + if (privkey) + SECKEY_DestroyPrivateKey(privkey); + if (tmppoolp) + PORT_FreeArena(tmppoolp, PR_FALSE); + return SECFailure; +} + +SECStatus +NSS_CMSSignerInfo_VerifyCertificate(NSSCMSSignerInfo *signerinfo, CERTCertDBHandle *certdb, + SECCertUsage certusage) +{ + CERTCertificate *cert; + PRTime stime; + + if ((cert = NSS_CMSSignerInfo_GetSigningCertificate(signerinfo, certdb)) == NULL) { + signerinfo->verificationStatus = NSSCMSVS_SigningCertNotFound; + return SECFailure; + } + + /* + * Get and convert the signing time; if available, it will be used + * both on the cert verification and for importing the sender + * email profile. + */ + if (NSS_CMSSignerInfo_GetSigningTime(signerinfo, &stime) != SECSuccess) + stime = PR_Now(); /* not found or conversion failed, so check against now */ + + /* + * 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 (CERT_VerifyCert(certdb, cert, PR_TRUE, certusage, stime, + signerinfo->cmsg->pwfn_arg, NULL) != SECSuccess) { + signerinfo->verificationStatus = NSSCMSVS_SigningCertNotTrusted; + return SECFailure; + } + return SECSuccess; +} + +/* + * NSS_CMSSignerInfo_Verify - verify the signature of a single SignerInfo + * + * Just verifies the signature. The assumption is that verification of + * the certificate is done already. + */ +SECStatus +NSS_CMSSignerInfo_Verify(NSSCMSSignerInfo *signerinfo, + SECItem *digest, /* may be NULL */ + SECItem *contentType) /* may be NULL */ +{ + SECKEYPublicKey *publickey = NULL; + NSSCMSAttribute *attr; + SECItem encoded_attrs; + CERTCertificate *cert; + NSSCMSVerificationStatus vs = NSSCMSVS_Unverified; + PLArenaPool *poolp; + SECOidTag digestalgtag; + SECOidTag pubkAlgTag; + + if (signerinfo == NULL) + return SECFailure; + + /* NSS_CMSSignerInfo_GetSigningCertificate will fail if 2nd parm is NULL + ** and cert has not been verified + */ + cert = NSS_CMSSignerInfo_GetSigningCertificate(signerinfo, NULL); + if (cert == NULL) { + vs = NSSCMSVS_SigningCertNotFound; + goto loser; + } + + if ((publickey = CERT_ExtractPublicKey(cert)) == NULL) { + vs = NSSCMSVS_ProcessingError; + goto loser; + } + + digestalgtag = NSS_CMSSignerInfo_GetDigestAlgTag(signerinfo); + pubkAlgTag = SECOID_GetAlgorithmTag(&(signerinfo->digestEncAlg)); + if ((pubkAlgTag == SEC_OID_UNKNOWN) || (digestalgtag == SEC_OID_UNKNOWN)) { + vs = NSSCMSVS_SignatureAlgorithmUnknown; + goto loser; + } + + if (!NSS_CMSArray_IsEmpty((void **)signerinfo->authAttr)) { + if (contentType) { + /* + * Check content type + * + * RFC2630 sez 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. + */ + attr = NSS_CMSAttributeArray_FindAttrByOidTag(signerinfo->authAttr, + SEC_OID_PKCS9_CONTENT_TYPE, PR_TRUE); + if (attr == NULL) { + vs = NSSCMSVS_MalformedSignature; + goto loser; + } + + if (NSS_CMSAttribute_CompareValue(attr, contentType) == PR_FALSE) { + vs = NSSCMSVS_MalformedSignature; + goto loser; + } + } + + /* + * Check digest + */ + attr = NSS_CMSAttributeArray_FindAttrByOidTag(signerinfo->authAttr, + SEC_OID_PKCS9_MESSAGE_DIGEST, PR_TRUE); + if (attr == NULL) { + vs = NSSCMSVS_MalformedSignature; + goto loser; + } + if (!digest || + NSS_CMSAttribute_CompareValue(attr, digest) == PR_FALSE) { + vs = NSSCMSVS_DigestMismatch; + goto loser; + } + + if ((poolp = PORT_NewArena(1024)) == NULL) { + vs = NSSCMSVS_ProcessingError; + goto loser; + } + + /* + * Check signature + * + * The signature is based on a digest of the DER-encoded authenticated + * attributes. So, first we encode and then we digest/verify. + * we trust the decoder to have the attributes in the right (sorted) + * order + */ + encoded_attrs.data = NULL; + encoded_attrs.len = 0; + + if (NSS_CMSAttributeArray_Encode(poolp, &(signerinfo->authAttr), + &encoded_attrs) == NULL || + encoded_attrs.data == NULL || encoded_attrs.len == 0) { + PORT_FreeArena(poolp, PR_FALSE); + vs = NSSCMSVS_ProcessingError; + goto loser; + } + + vs = (VFY_VerifyDataDirect(encoded_attrs.data, encoded_attrs.len, + publickey, &(signerinfo->encDigest), pubkAlgTag, + digestalgtag, NULL, signerinfo->cmsg->pwfn_arg) != SECSuccess) + ? NSSCMSVS_BadSignature + : NSSCMSVS_GoodSignature; + + PORT_FreeArena(poolp, PR_FALSE); /* awkward memory management :-( */ + + } else { + SECItem *sig; + + /* No authenticated attributes. + ** The signature is based on the plain message digest. + */ + sig = &(signerinfo->encDigest); + if (sig->len == 0) + goto loser; + + vs = (!digest || + VFY_VerifyDigestDirect(digest, publickey, sig, pubkAlgTag, + digestalgtag, signerinfo->cmsg->pwfn_arg) != SECSuccess) + ? NSSCMSVS_BadSignature + : NSSCMSVS_GoodSignature; + } + + if (vs == NSSCMSVS_BadSignature) { + int error = PORT_GetError(); + /* + * 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 the PSM 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 (error == SEC_ERROR_BAD_SIGNATURE) + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + /* + * map algorithm failures to NSSCMSVS values + */ + if ((error == SEC_ERROR_PKCS7_KEYALG_MISMATCH) || + (error == SEC_ERROR_INVALID_ALGORITHM)) { + /* keep the same error code as 3.11 and before */ + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + vs = NSSCMSVS_SignatureAlgorithmUnsupported; + } + } + + if (publickey != NULL) + SECKEY_DestroyPublicKey(publickey); + + signerinfo->verificationStatus = vs; + + return (vs == NSSCMSVS_GoodSignature) ? SECSuccess : SECFailure; + +loser: + if (publickey != NULL) + SECKEY_DestroyPublicKey(publickey); + + signerinfo->verificationStatus = vs; + + PORT_SetError(SEC_ERROR_PKCS7_BAD_SIGNATURE); + return SECFailure; +} + +NSSCMSVerificationStatus +NSS_CMSSignerInfo_GetVerificationStatus(NSSCMSSignerInfo *signerinfo) +{ + return signerinfo->verificationStatus; +} + +SECOidData * +NSS_CMSSignerInfo_GetDigestAlg(NSSCMSSignerInfo *signerinfo) +{ + SECOidData *algdata; + SECOidTag algtag; + + algdata = SECOID_FindOID(&(signerinfo->digestAlg.algorithm)); + if (algdata == NULL) { + return algdata; + } + /* Windows may have given us a signer algorithm oid instead of a digest + * algorithm oid. This call will map to a signer oid to a digest one, + * otherwise it leaves the oid alone and let the chips fall as they may + * if it's not a digest oid. + */ + algtag = NSS_CMSUtil_MapSignAlgs(algdata->offset); + if (algtag != algdata->offset) { + /* if the tags don't match, then we must have received a signer + * algorithID. Now we need to get the oid data for the digest + * oid, which the rest of the code is expecting */ + algdata = SECOID_FindOIDByTag(algtag); + } + + return algdata; +} + +SECOidTag +NSS_CMSSignerInfo_GetDigestAlgTag(NSSCMSSignerInfo *signerinfo) +{ + SECOidData *algdata; + + if (!signerinfo) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SEC_OID_UNKNOWN; + } + + algdata = NSS_CMSSignerInfo_GetDigestAlg(signerinfo); + if (algdata != NULL) + return algdata->offset; + else + return SEC_OID_UNKNOWN; +} + +CERTCertificateList * +NSS_CMSSignerInfo_GetCertList(NSSCMSSignerInfo *signerinfo) +{ + return signerinfo->certList; +} + +int +NSS_CMSSignerInfo_GetVersion(NSSCMSSignerInfo *signerinfo) +{ + unsigned long version; + + /* always take apart the SECItem */ + if (SEC_ASN1DecodeInteger(&(signerinfo->version), &version) != SECSuccess) + return 0; + else + return (int)version; +} + +/* + * NSS_CMSSignerInfo_GetSigningTime - return the signing time, + * in UTCTime or GeneralizedTime format, + * of a CMS signerInfo. + * + * sinfo - signerInfo data for this signer + * + * Returns a pointer to XXXX (what?) + * A return value of NULL is an error. + */ +SECStatus +NSS_CMSSignerInfo_GetSigningTime(NSSCMSSignerInfo *sinfo, PRTime *stime) +{ + NSSCMSAttribute *attr; + SECItem *value; + + if (sinfo == NULL) + return SECFailure; + + if (sinfo->signingTime != 0) { + *stime = sinfo->signingTime; /* cached copy */ + return SECSuccess; + } + + attr = NSS_CMSAttributeArray_FindAttrByOidTag(sinfo->authAttr, + SEC_OID_PKCS9_SIGNING_TIME, PR_TRUE); + /* XXXX multi-valued attributes NIH */ + if (attr == NULL || (value = NSS_CMSAttribute_GetValue(attr)) == NULL) + return SECFailure; + if (DER_DecodeTimeChoice(stime, value) != SECSuccess) + return SECFailure; + sinfo->signingTime = *stime; /* make cached copy */ + return SECSuccess; +} + +/* + * Return the signing cert of a CMS signerInfo. + * + * the certs in the enclosing SignedData must have been imported already + */ +CERTCertificate * +NSS_CMSSignerInfo_GetSigningCertificate(NSSCMSSignerInfo *signerinfo, CERTCertDBHandle *certdb) +{ + CERTCertificate *cert; + NSSCMSSignerIdentifier *sid; + + if (signerinfo->cert != NULL) + return signerinfo->cert; + + /* no certdb, and cert hasn't been set yet? */ + if (certdb == NULL) + return NULL; + + /* + * 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. + */ + sid = &signerinfo->signerIdentifier; + switch (sid->identifierType) { + case NSSCMSSignerID_IssuerSN: + cert = CERT_FindCertByIssuerAndSN(certdb, sid->id.issuerAndSN); + break; + case NSSCMSSignerID_SubjectKeyID: + cert = CERT_FindCertBySubjectKeyID(certdb, sid->id.subjectKeyID); + break; + default: + cert = NULL; + break; + } + + /* cert can be NULL at that point */ + signerinfo->cert = cert; /* earmark it */ + + return cert; +} + +/* + * NSS_CMSSignerInfo_GetSignerCommonName - return the common name of the signer + * + * sinfo - signerInfo data for this signer + * + * Returns a pointer to allocated memory, which must be freed with PORT_Free. + * A return value of NULL is an error. + */ +char * +NSS_CMSSignerInfo_GetSignerCommonName(NSSCMSSignerInfo *sinfo) +{ + CERTCertificate *signercert; + + /* will fail if cert is not verified */ + if ((signercert = NSS_CMSSignerInfo_GetSigningCertificate(sinfo, NULL)) == NULL) + return NULL; + + return (CERT_GetCommonName(&signercert->subject)); +} + +/* + * NSS_CMSSignerInfo_GetSignerEmailAddress - return the common name of the signer + * + * sinfo - signerInfo data for this signer + * + * Returns a pointer to allocated memory, which must be freed. + * A return value of NULL is an error. + */ +char * +NSS_CMSSignerInfo_GetSignerEmailAddress(NSSCMSSignerInfo *sinfo) +{ + CERTCertificate *signercert; + + if ((signercert = NSS_CMSSignerInfo_GetSigningCertificate(sinfo, NULL)) == NULL) + return NULL; + + if (!signercert->emailAddr || !signercert->emailAddr[0]) + return NULL; + + return (PORT_Strdup(signercert->emailAddr)); +} + +/* + * NSS_CMSSignerInfo_AddAuthAttr - add an attribute to the + * authenticated (i.e. signed) attributes of "signerinfo". + */ +SECStatus +NSS_CMSSignerInfo_AddAuthAttr(NSSCMSSignerInfo *signerinfo, NSSCMSAttribute *attr) +{ + return NSS_CMSAttributeArray_AddAttr(signerinfo->cmsg->poolp, &(signerinfo->authAttr), attr); +} + +/* + * NSS_CMSSignerInfo_AddUnauthAttr - add an attribute to the + * unauthenticated attributes of "signerinfo". + */ +SECStatus +NSS_CMSSignerInfo_AddUnauthAttr(NSSCMSSignerInfo *signerinfo, NSSCMSAttribute *attr) +{ + return NSS_CMSAttributeArray_AddAttr(signerinfo->cmsg->poolp, &(signerinfo->unAuthAttr), attr); +} + +/* + * NSS_CMSSignerInfo_AddSigningTime - add the signing time to the + * authenticated (i.e. signed) attributes of "signerinfo". + * + * 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 do nothing. + * + * XXX This will probably just shove the current time into "signerinfo" + * but it will not actually get signed until the entire item is + * processed for encoding. Is this (expected to be small) delay okay? + */ +SECStatus +NSS_CMSSignerInfo_AddSigningTime(NSSCMSSignerInfo *signerinfo, PRTime t) +{ + NSSCMSAttribute *attr; + SECItem stime; + void *mark; + PLArenaPool *poolp; + + poolp = signerinfo->cmsg->poolp; + + mark = PORT_ArenaMark(poolp); + + /* create new signing time attribute */ + if (DER_EncodeTimeChoice(NULL, &stime, t) != SECSuccess) + goto loser; + + if ((attr = NSS_CMSAttribute_Create(poolp, SEC_OID_PKCS9_SIGNING_TIME, &stime, PR_FALSE)) == NULL) { + SECITEM_FreeItem(&stime, PR_FALSE); + goto loser; + } + + SECITEM_FreeItem(&stime, PR_FALSE); + + if (NSS_CMSSignerInfo_AddAuthAttr(signerinfo, attr) != SECSuccess) + goto loser; + + PORT_ArenaUnmark(poolp, mark); + + return SECSuccess; + +loser: + PORT_ArenaRelease(poolp, mark); + return SECFailure; +} + +/* + * NSS_CMSSignerInfo_AddSMIMECaps - add a SMIMECapabilities attribute to the + * authenticated (i.e. signed) attributes of "signerinfo". + * + * This is expected to be included in outgoing signed + * messages for email (S/MIME). + */ +SECStatus +NSS_CMSSignerInfo_AddSMIMECaps(NSSCMSSignerInfo *signerinfo) +{ + NSSCMSAttribute *attr; + SECItem *smimecaps = NULL; + void *mark; + PLArenaPool *poolp; + + poolp = signerinfo->cmsg->poolp; + + mark = PORT_ArenaMark(poolp); + + smimecaps = SECITEM_AllocItem(poolp, NULL, 0); + if (smimecaps == NULL) + goto loser; + + /* create new signing time attribute */ + if (NSS_SMIMEUtil_CreateSMIMECapabilities(poolp, smimecaps) != SECSuccess) + goto loser; + + if ((attr = NSS_CMSAttribute_Create(poolp, SEC_OID_PKCS9_SMIME_CAPABILITIES, smimecaps, PR_TRUE)) == NULL) + goto loser; + + if (NSS_CMSSignerInfo_AddAuthAttr(signerinfo, attr) != SECSuccess) + goto loser; + + PORT_ArenaUnmark(poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(poolp, mark); + return SECFailure; +} + +/* + * NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs - add a SMIMEEncryptionKeyPreferences attribute to the + * authenticated (i.e. signed) attributes of "signerinfo". + * + * This is expected to be included in outgoing signed messages for email (S/MIME). + */ +SECStatus +NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs(NSSCMSSignerInfo *signerinfo, CERTCertificate *cert, CERTCertDBHandle *certdb) +{ + NSSCMSAttribute *attr; + SECItem *smimeekp = NULL; + void *mark; + PLArenaPool *poolp; + + /* verify this cert for encryption */ + if (CERT_VerifyCert(certdb, cert, PR_TRUE, certUsageEmailRecipient, PR_Now(), signerinfo->cmsg->pwfn_arg, NULL) != SECSuccess) { + return SECFailure; + } + + poolp = signerinfo->cmsg->poolp; + mark = PORT_ArenaMark(poolp); + + smimeekp = SECITEM_AllocItem(poolp, NULL, 0); + if (smimeekp == NULL) + goto loser; + + /* create new signing time attribute */ + if (NSS_SMIMEUtil_CreateSMIMEEncKeyPrefs(poolp, smimeekp, cert) != SECSuccess) + goto loser; + + if ((attr = NSS_CMSAttribute_Create(poolp, SEC_OID_SMIME_ENCRYPTION_KEY_PREFERENCE, smimeekp, PR_TRUE)) == NULL) + goto loser; + + if (NSS_CMSSignerInfo_AddAuthAttr(signerinfo, attr) != SECSuccess) + goto loser; + + PORT_ArenaUnmark(poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(poolp, mark); + return SECFailure; +} + +/* + * NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs - add a SMIMEEncryptionKeyPreferences attribute to the + * authenticated (i.e. signed) attributes of "signerinfo", using the OID preferred by Microsoft. + * + * This is expected to be included in outgoing signed messages for email (S/MIME), + * if compatibility with Microsoft mail clients is wanted. + */ +SECStatus +NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs(NSSCMSSignerInfo *signerinfo, CERTCertificate *cert, CERTCertDBHandle *certdb) +{ + NSSCMSAttribute *attr; + SECItem *smimeekp = NULL; + void *mark; + PLArenaPool *poolp; + + /* verify this cert for encryption */ + if (CERT_VerifyCert(certdb, cert, PR_TRUE, certUsageEmailRecipient, PR_Now(), signerinfo->cmsg->pwfn_arg, NULL) != SECSuccess) { + return SECFailure; + } + + poolp = signerinfo->cmsg->poolp; + mark = PORT_ArenaMark(poolp); + + smimeekp = SECITEM_AllocItem(poolp, NULL, 0); + if (smimeekp == NULL) + goto loser; + + /* create new signing time attribute */ + if (NSS_SMIMEUtil_CreateMSSMIMEEncKeyPrefs(poolp, smimeekp, cert) != SECSuccess) + goto loser; + + if ((attr = NSS_CMSAttribute_Create(poolp, SEC_OID_MS_SMIME_ENCRYPTION_KEY_PREFERENCE, smimeekp, PR_TRUE)) == NULL) + goto loser; + + if (NSS_CMSSignerInfo_AddAuthAttr(signerinfo, attr) != SECSuccess) + goto loser; + + PORT_ArenaUnmark(poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(poolp, mark); + return SECFailure; +} + +/* + * NSS_CMSSignerInfo_AddCounterSignature - countersign a signerinfo + * + * 1. digest the DER-encoded signature value of the original signerinfo + * 2. create new signerinfo with correct version, sid, digestAlg + * 3. add message-digest authAttr, but NO content-type + * 4. sign the authAttrs + * 5. DER-encode the new signerInfo + * 6. add the whole thing to original signerInfo's unAuthAttrs + * as a SEC_OID_PKCS9_COUNTER_SIGNATURE attribute + * + * XXXX give back the new signerinfo? + */ +SECStatus +NSS_CMSSignerInfo_AddCounterSignature(NSSCMSSignerInfo *signerinfo, + SECOidTag digestalg, CERTCertificate signingcert) +{ + /* XXXX TBD XXXX */ + return SECFailure; +} + +/* + * XXXX the following needs to be done in the S/MIME layer code + * after signature of a signerinfo is verified + */ +SECStatus +NSS_SMIMESignerInfo_SaveSMIMEProfile(NSSCMSSignerInfo *signerinfo) +{ + CERTCertificate *cert = NULL; + SECItem *profile = NULL; + NSSCMSAttribute *attr; + SECItem *stime = NULL; + SECItem *ekp; + CERTCertDBHandle *certdb; + int save_error; + SECStatus rv; + PRBool must_free_cert = PR_FALSE; + + certdb = CERT_GetDefaultCertDB(); + + /* sanity check - see if verification status is ok (unverified does not count...) */ + if (signerinfo->verificationStatus != NSSCMSVS_GoodSignature) + return SECFailure; + + /* find preferred encryption cert */ + if (!NSS_CMSArray_IsEmpty((void **)signerinfo->authAttr) && + (attr = NSS_CMSAttributeArray_FindAttrByOidTag(signerinfo->authAttr, + SEC_OID_SMIME_ENCRYPTION_KEY_PREFERENCE, PR_TRUE)) != NULL) { /* we have a SMIME_ENCRYPTION_KEY_PREFERENCE attribute! */ + ekp = NSS_CMSAttribute_GetValue(attr); + if (ekp == NULL) + return SECFailure; + + /* we assume that all certs coming with the message have been imported to the */ + /* temporary database */ + cert = NSS_SMIMEUtil_GetCertFromEncryptionKeyPreference(certdb, ekp); + if (cert == NULL) + return SECFailure; + must_free_cert = PR_TRUE; + } + + if (cert == NULL) { + /* no preferred cert found? + * find the cert the signerinfo is signed with instead */ + cert = NSS_CMSSignerInfo_GetSigningCertificate(signerinfo, certdb); + if (cert == NULL || cert->emailAddr == NULL || !cert->emailAddr[0]) + return SECFailure; + } + +/* verify this cert for encryption (has been verified for signing so far) */ +/* don't verify this cert for encryption. It may just be a signing cert. + * that's OK, we can still save the S/MIME profile. The encryption cert + * should have already been saved */ +#ifdef notdef + if (CERT_VerifyCert(certdb, cert, PR_TRUE, certUsageEmailRecipient, PR_Now(), signerinfo->cmsg->pwfn_arg, NULL) != SECSuccess) { + if (must_free_cert) + CERT_DestroyCertificate(cert); + return SECFailure; + } +#endif + + /* XXX store encryption cert permanently? */ + + /* + * 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 (!NSS_CMSArray_IsEmpty((void **)signerinfo->authAttr)) { + attr = NSS_CMSAttributeArray_FindAttrByOidTag(signerinfo->authAttr, + SEC_OID_PKCS9_SMIME_CAPABILITIES, + PR_TRUE); + profile = NSS_CMSAttribute_GetValue(attr); + attr = NSS_CMSAttributeArray_FindAttrByOidTag(signerinfo->authAttr, + SEC_OID_PKCS9_SIGNING_TIME, + PR_TRUE); + stime = NSS_CMSAttribute_GetValue(attr); + } + + rv = CERT_SaveSMimeProfile(cert, profile, stime); + if (must_free_cert) + CERT_DestroyCertificate(cert); + + /* + * Restore the saved error in case the calls above set a new + * one that we do not actually care about. + */ + PORT_SetError(save_error); + + return rv; +} + +/* + * NSS_CMSSignerInfo_IncludeCerts - set cert chain inclusion mode for this signer + */ +SECStatus +NSS_CMSSignerInfo_IncludeCerts(NSSCMSSignerInfo *signerinfo, + NSSCMSCertChainMode cm, SECCertUsage usage) +{ + if (signerinfo->cert == NULL) + return SECFailure; + + /* don't leak if we get called twice */ + if (signerinfo->certList != NULL) { + CERT_DestroyCertificateList(signerinfo->certList); + signerinfo->certList = NULL; + } + + switch (cm) { + case NSSCMSCM_None: + signerinfo->certList = NULL; + break; + case NSSCMSCM_CertOnly: + signerinfo->certList = CERT_CertListFromCert(signerinfo->cert); + break; + case NSSCMSCM_CertChain: + signerinfo->certList = CERT_CertChainFromCert(signerinfo->cert, + usage, PR_FALSE); + break; + case NSSCMSCM_CertChainWithRoot: + signerinfo->certList = CERT_CertChainFromCert(signerinfo->cert, + usage, PR_TRUE); + break; + } + + if (cm != NSSCMSCM_None && signerinfo->certList == NULL) + return SECFailure; + + return SECSuccess; +} |