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