/* 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 "keyhi.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 ??? */ } static SECOidTag NSS_CMSSignerInfo_GetSignatureAlgorithmOidTag(KeyType keyType, SECOidTag pubkAlgTag, SECOidTag signAlgTag) { switch (keyType) { case rsaKey: return pubkAlgTag; case rsaPssKey: case dsaKey: case ecKey: return signAlgTag; default: return SEC_OID_UNKNOWN; } } /* * NSS_CMSSignerInfo_Sign - sign something * */ SECStatus NSS_CMSSignerInfo_Sign(NSSCMSSignerInfo *signerinfo, SECItem *digest, SECItem *contentType) { CERTCertificate *cert; SECKEYPrivateKey *privkey = NULL; SECOidTag digestalgtag; SECOidTag pubkAlgTag; SECOidTag signAlgTag; SECOidTag cmsSignAlgTag; 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 (algID == &freeAlgID) { SECOID_DestroyAlgorithmID(&freeAlgID, PR_FALSE); } signAlgTag = SEC_GetSignatureAlgorithmOidTag(SECKEY_GetPrivateKeyType(privkey), digestalgtag); if (signAlgTag == SEC_OID_UNKNOWN) { PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); goto loser; } cmsSignAlgTag = NSS_CMSSignerInfo_GetSignatureAlgorithmOidTag( SECKEY_GetPrivateKeyType(privkey), pubkAlgTag, signAlgTag); if (cmsSignAlgTag == SEC_OID_UNKNOWN) { PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); goto loser; } if (SECOID_SetAlgorithmID(poolp, &(signerinfo->digestEncAlg), cmsSignAlgTag, NULL) != SECSuccess) goto loser; if (signerinfo->authAttr != NULL) { 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; 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); 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; SECOidTag digestalgtagCmp; SECOidTag sigAlgTag; 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(&(cert->subjectPublicKeyInfo.algorithm)); sigAlgTag = SECOID_GetAlgorithmTag(&(signerinfo->digestEncAlg)); if ((pubkAlgTag == SEC_OID_UNKNOWN) || (digestalgtag == SEC_OID_UNKNOWN) || (sigAlgTag == 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; } if (sigAlgTag == pubkAlgTag) { /* This is to handle cases in which signatureAlgorithm field * specifies the public key algorithm rather than a signature * algorithm. */ vs = (VFY_VerifyDataDirect(encoded_attrs.data, encoded_attrs.len, publickey, &(signerinfo->encDigest), pubkAlgTag, digestalgtag, NULL, signerinfo->cmsg->pwfn_arg) != SECSuccess) ? NSSCMSVS_BadSignature : NSSCMSVS_GoodSignature; } else { if (VFY_VerifyDataWithAlgorithmID(encoded_attrs.data, encoded_attrs.len, publickey, &(signerinfo->encDigest), &(signerinfo->digestEncAlg), &digestalgtagCmp, signerinfo->cmsg->pwfn_arg) != SECSuccess) { vs = NSSCMSVS_BadSignature; } else if (digestalgtagCmp != digestalgtag) { PORT_SetError(SEC_ERROR_BAD_SIGNATURE); vs = NSSCMSVS_BadSignature; } else { vs = 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; if (sigAlgTag == pubkAlgTag) { /* This is to handle cases in which signatureAlgorithm field * specifies the public key algorithm rather than a signature * algorithm. */ vs = (!digest || VFY_VerifyDigestDirect(digest, publickey, sig, pubkAlgTag, digestalgtag, signerinfo->cmsg->pwfn_arg) != SECSuccess) ? NSSCMSVS_BadSignature : NSSCMSVS_GoodSignature; } else { vs = (!digest || VFY_VerifyDigestWithAlgorithmID(digest, publickey, sig, &(signerinfo->digestEncAlg), 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; }