OSStatus SecCmsSignedDataAddSignerInfo(SecCmsSignedDataRef sigd, SecCmsSignerInfoRef signerinfo) { void *mark; OSStatus rv; SECOidTag digestalgtag; PLArenaPool *poolp; poolp = sigd->cmsg->poolp; mark = PORT_ArenaMark(poolp); /* add signerinfo */ rv = SecCmsArrayAdd(poolp, (void ***)&(sigd->signerInfos), (void *)signerinfo); if (rv != SECSuccess) goto loser; signerinfo->sigd = sigd; /* * add empty digest * Empty because we don't have it yet. Either it gets created during encoding * (if the data is present) or has to be set externally. * XXX maybe pass it in optionally? */ digestalgtag = SecCmsSignerInfoGetDigestAlgTag(signerinfo); rv = SecCmsSignedDataSetDigestValue(sigd, digestalgtag, NULL); if (rv != SECSuccess) goto loser; /* * The last thing to get consistency would be adding the digest. */ PORT_ArenaUnmark(poolp, mark); return SECSuccess; loser: PORT_ArenaRelease (poolp, mark); return SECFailure; }
/* * SecCmsSignedDataEncodeBeforeStart - do all the necessary things to a SignedData * before start of encoding. * * In detail: * - find out about the right value to put into sigd->version * - come up with a list of digestAlgorithms (which should be the union of the algorithms * in the signerinfos). * If we happen to have a pre-set list of algorithms (and digest values!), we * check if we have all the signerinfos' algorithms. If not, this is an error. */ OSStatus SecCmsSignedDataEncodeBeforeStart(SecCmsSignedDataRef sigd) { SecCmsSignerInfoRef signerinfo; SECOidTag digestalgtag; CSSM_DATA_PTR dummy; int version; OSStatus rv; Boolean haveDigests = PR_FALSE; int n, i; PLArenaPool *poolp; poolp = sigd->cmsg->poolp; /* we assume that we have precomputed digests if there is a list of algorithms, and */ /* a chunk of data for each of those algorithms */ if (sigd->digestAlgorithms != NULL && sigd->digests != NULL) { for (i=0; sigd->digestAlgorithms[i] != NULL; i++) { if (sigd->digests[i] == NULL) break; } if (sigd->digestAlgorithms[i] == NULL) /* reached the end of the array? */ haveDigests = PR_TRUE; /* yes: we must have all the digests */ } version = SEC_CMS_SIGNED_DATA_VERSION_BASIC; /* RFC2630 5.1 "version is the syntax version number..." */ if (SecCmsContentInfoGetContentTypeTag(&(sigd->contentInfo)) != SEC_OID_PKCS7_DATA) version = SEC_CMS_SIGNED_DATA_VERSION_EXT; /* prepare all the SignerInfos (there may be none) */ for (i=0; i < SecCmsSignedDataSignerInfoCount(sigd); i++) { signerinfo = SecCmsSignedDataGetSignerInfo(sigd, i); /* RFC2630 5.1 "version is the syntax version number..." */ if (SecCmsSignerInfoGetVersion(signerinfo) != SEC_CMS_SIGNER_INFO_VERSION_ISSUERSN) version = SEC_CMS_SIGNED_DATA_VERSION_EXT; /* collect digestAlgorithms from SignerInfos */ /* (we need to know which algorithms we have when the content comes in) */ /* do not overwrite any existing digestAlgorithms (and digest) */ digestalgtag = SecCmsSignerInfoGetDigestAlgTag(signerinfo); n = SecCmsAlgArrayGetIndexByAlgTag(sigd->digestAlgorithms, digestalgtag); if (n < 0 && haveDigests) { /* oops, there is a digestalg we do not have a digest for */ /* but we were supposed to have all the digests already... */ goto loser; } else if (n < 0) { /* add the digestAlgorithm & a NULL digest */ rv = SecCmsSignedDataAddDigest((SecArenaPoolRef)poolp, sigd, digestalgtag, NULL); if (rv != SECSuccess) goto loser; } else { /* found it, nothing to do */ } } dummy = SEC_ASN1EncodeInteger(poolp, &(sigd->version), (long)version); if (dummy == NULL) return SECFailure; /* this is a SET OF, so we need to sort them guys */ rv = SecCmsArraySortByDER((void **)sigd->digestAlgorithms, SEC_ASN1_GET(SECOID_AlgorithmIDTemplate), (void **)sigd->digests); if (rv != SECSuccess) return SECFailure; return SECSuccess; loser: return SECFailure; }
/* * SecCmsSignedDataEncodeAfterData - do all the necessary things to a SignedData * after all the encapsulated data was passed through the encoder. * * In detail: * - create the signatures in all the SignerInfos * * Please note that nothing is done to the Certificates and CRLs in the message - this * is entirely the responsibility of our callers. */ OSStatus SecCmsSignedDataEncodeAfterData(SecCmsSignedDataRef sigd) { SecCmsSignerInfoRef *signerinfos, signerinfo; SecCmsContentInfoRef cinfo; SECOidTag digestalgtag; OSStatus ret = SECFailure; OSStatus rv; CSSM_DATA_PTR contentType; int certcount; int i, ci, n, rci, si; PLArenaPool *poolp; CFArrayRef certlist; extern const SecAsn1Template SecCmsSignerInfoTemplate[]; poolp = sigd->cmsg->poolp; cinfo = &(sigd->contentInfo); /* did we have digest calculation going on? */ if (cinfo->digcx) { rv = SecCmsDigestContextFinishMultiple(cinfo->digcx, (SecArenaPoolRef)poolp, &(sigd->digests)); if (rv != SECSuccess) goto loser; /* error has been set by SecCmsDigestContextFinishMultiple */ cinfo->digcx = NULL; } signerinfos = sigd->signerInfos; certcount = 0; /* prepare all the SignerInfos (there may be none) */ for (i=0; i < SecCmsSignedDataSignerInfoCount(sigd); i++) { signerinfo = SecCmsSignedDataGetSignerInfo(sigd, i); /* find correct digest for this signerinfo */ digestalgtag = SecCmsSignerInfoGetDigestAlgTag(signerinfo); n = SecCmsAlgArrayGetIndexByAlgTag(sigd->digestAlgorithms, digestalgtag); if (n < 0 || sigd->digests == NULL || sigd->digests[n] == NULL) { /* oops - digest not found */ PORT_SetError(SEC_ERROR_DIGEST_NOT_FOUND); goto loser; } /* XXX if our content is anything else but data, we need to force the * presence of signed attributes (RFC2630 5.3 "signedAttributes is a * collection...") */ /* pass contentType here as we want a contentType attribute */ if ((contentType = SecCmsContentInfoGetContentTypeOID(cinfo)) == NULL) goto loser; /* sign the thing */ rv = SecCmsSignerInfoSign(signerinfo, sigd->digests[n], contentType); if (rv != SECSuccess) goto loser; /* while we're at it, count number of certs in certLists */ certlist = SecCmsSignerInfoGetCertList(signerinfo); if (certlist) certcount += CFArrayGetCount(certlist); } /* Now we can get a timestamp, since we have all the digests */ // We force the setting of a callback, since this is the most usual case if (!sigd->cmsg->tsaCallback) SecCmsMessageSetTSACallback(sigd->cmsg, (SecCmsTSACallback)SecCmsTSADefaultCallback); if (sigd->cmsg->tsaCallback && sigd->cmsg->tsaContext) { CSSM_DATA tsaResponse = {0,}; SecAsn1TSAMessageImprint messageImprint = {{{0},},{0,}}; // <rdar://problem/11073466> Add nonce support for timestamping client uint64_t nonce = 0; require_noerr(getRandomNonce(&nonce), tsxit); dprintf("SecCmsSignedDataSignerInfoCount: %d\n", SecCmsSignedDataSignerInfoCount(sigd)); // Calculate hash of encDigest and put in messageImprint.hashedMessage SecCmsSignerInfoRef signerinfo = SecCmsSignedDataGetSignerInfo(sigd, 0); // NB - assume 1 signer only! CSSM_DATA *encDigest = SecCmsSignerInfoGetEncDigest(signerinfo); require_noerr(createTSAMessageImprint(sigd, encDigest, &messageImprint), tsxit); // Callback to fire up XPC service to talk to TimeStamping server, etc. require_noerr(rv =(*sigd->cmsg->tsaCallback)(sigd->cmsg->tsaContext, &messageImprint, nonce, &tsaResponse), tsxit); require_noerr(rv = validateTSAResponseAndAddTimeStamp(signerinfo, &tsaResponse, nonce), tsxit); /* It is likely that every occurrence of "goto loser" in this file should also do a PORT_SetError. Since it is not clear what might depend on this behavior, we just do this in the timestamping case. */ tsxit: if (rv) { dprintf("Original timestamp error: %d\n", (int)rv); rv = remapTimestampError(rv); PORT_SetError(rv); goto loser; } } /* this is a SET OF, so we need to sort them guys */ rv = SecCmsArraySortByDER((void **)signerinfos, SecCmsSignerInfoTemplate, NULL); if (rv != SECSuccess) goto loser; /* * now prepare certs & crls */ /* count the rest of the certs */ if (sigd->certs != NULL) certcount += CFArrayGetCount(sigd->certs); if (certcount == 0) { sigd->rawCerts = NULL; } else { /* * Combine all of the certs and cert chains into rawcerts. * Note: certcount is an upper bound; we may not need that many slots * but we will allocate anyway to avoid having to do another pass. * (The temporary space saving is not worth it.) * * XXX ARGH - this NEEDS to be fixed. need to come up with a decent * SetOfDERcertficates implementation */ sigd->rawCerts = (CSSM_DATA_PTR *)PORT_ArenaAlloc(poolp, (certcount + 1) * sizeof(CSSM_DATA_PTR)); if (sigd->rawCerts == NULL) return SECFailure; /* * XXX Want to check for duplicates and not add *any* cert that is * already in the set. This will be more important when we start * dealing with larger sets of certs, dual-key certs (signing and * encryption), etc. For the time being we can slide by... * * XXX ARGH - this NEEDS to be fixed. need to come up with a decent * SetOfDERcertficates implementation */ rci = 0; if (signerinfos != NULL) { for (si = 0; signerinfos[si] != NULL; si++) { signerinfo = signerinfos[si]; for (ci = 0; ci < CFArrayGetCount(signerinfo->certList); ci++) { sigd->rawCerts[rci] = PORT_ArenaZAlloc(poolp, sizeof(CSSM_DATA)); SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(signerinfo->certList, ci); SecCertificateGetData(cert, sigd->rawCerts[rci++]); } } } if (sigd->certs != NULL) { for (ci = 0; ci < CFArrayGetCount(sigd->certs); ci++) { sigd->rawCerts[rci] = PORT_ArenaZAlloc(poolp, sizeof(CSSM_DATA)); SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(sigd->certs, ci); SecCertificateGetData(cert, sigd->rawCerts[rci++]); } } sigd->rawCerts[rci] = NULL; /* this is a SET OF, so we need to sort them guys - we have the DER already, though */ SecCmsArraySort((void **)sigd->rawCerts, SecCmsUtilDERCompare, NULL, NULL); } ret = SECSuccess; loser: dprintf("SecCmsSignedDataEncodeAfterData: ret: %ld, rv: %ld\n", (long)ret, (long)rv); return ret; }
/* * SecCmsSignerInfoSign - sign something * */ OSStatus SecCmsSignerInfoSign(SecCmsSignerInfoRef signerinfo, CSSM_DATA_PTR digest, CSSM_DATA_PTR contentType) { SecCertificateRef cert; SecPrivateKeyRef privkey = NULL; SECOidTag digestalgtag; SECOidTag pubkAlgTag; CSSM_DATA signature = { 0 }; OSStatus rv; PLArenaPool *poolp, *tmppoolp = NULL; const SECAlgorithmID *algID; SECAlgorithmID freeAlgID; //CERTSubjectPublicKeyInfo *spki; PORT_Assert (digest != NULL); poolp = signerinfo->cmsg->poolp; switch (signerinfo->signerIdentifier.identifierType) { case SecCmsSignerIDIssuerSN: privkey = signerinfo->signingKey; signerinfo->signingKey = NULL; cert = signerinfo->cert; if (SecCertificateGetAlgorithmID(cert,&algID)) { PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); goto loser; } break; case SecCmsSignerIDSubjectKeyID: privkey = signerinfo->signingKey; signerinfo->signingKey = NULL; #if 0 spki = SECKEY_CreateSubjectPublicKeyInfo(signerinfo->pubKey); SECKEY_DestroyPublicKey(signerinfo->pubKey); signerinfo->pubKey = NULL; SECOID_CopyAlgorithmID(NULL, &freeAlgID, &spki->algorithm); SECKEY_DestroySubjectPublicKeyInfo(spki); algID = &freeAlgID; #else #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR)) if (SecKeyGetAlgorithmID(signerinfo->pubKey,&algID)) { #else /* TBD: Unify this code. Currently, iOS has an incompatible * SecKeyGetAlgorithmID implementation. */ if (true) { #endif PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); goto loser; } CFRelease(signerinfo->pubKey); signerinfo->pubKey = NULL; #endif break; default: PORT_SetError(SEC_ERROR_UNSUPPORTED_MESSAGE_TYPE); goto loser; } digestalgtag = SecCmsSignerInfoGetDigestAlgTag(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 == SecCmsSignerIDSubjectKeyID) { SECOID_DestroyAlgorithmID(&freeAlgID, PR_FALSE); } #if 0 // @@@ Not yet /* Fortezza MISSI have weird signature formats. * Map them to standard DSA formats */ pubkAlgTag = PK11_FortezzaMapSig(pubkAlgTag); #endif if (signerinfo->authAttr != NULL) { CSSM_DATA encoded_attrs; /* find and fill in the message digest attribute. */ rv = SecCmsAttributeArraySetAttr(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 = SecCmsAttributeArraySetAttr(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 (SecCmsAttributeArrayReorder(signerinfo->authAttr) != SECSuccess) goto loser; encoded_attrs.Data = NULL; encoded_attrs.Length = 0; if (SecCmsAttributeArrayEncode(tmppoolp, &(signerinfo->authAttr), &encoded_attrs) == NULL) goto loser; rv = SEC_SignData(&signature, encoded_attrs.Data, (int)encoded_attrs.Length, privkey, digestalgtag, pubkAlgTag); PORT_FreeArena(tmppoolp, PR_FALSE); /* awkward memory management :-( */ tmppoolp = 0; } else { rv = SGN_Digest(privkey, digestalgtag, pubkAlgTag, &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(pubkAlgTag == SEC_OID_EC_PUBLIC_KEY) { /* * RFC 3278 section section 2.1.1 states that the signatureAlgorithm * field contains the full ecdsa-with-SHA1 OID, not plain old ecPublicKey * as would appear in other forms of signed datas. However Microsoft doesn't * do this, it puts ecPublicKey there, and if we put ecdsa-with-SHA1 there, * MS can't verify - presumably because it takes the digest of the digest * before feeding it to ECDSA. * We handle this with a preference; default if it's not there is * "Microsoft compatibility mode". */ if(!SecCmsMsEcdsaCompatMode()) { pubkAlgTag = SEC_OID_ECDSA_WithSHA1; } /* else violating the spec for compatibility */ } if (SECOID_SetAlgorithmID(poolp, &(signerinfo->digestEncAlg), pubkAlgTag, NULL) != SECSuccess) goto loser; return SECSuccess; loser: if (signature.Length != 0) SECITEM_FreeItem (&signature, PR_FALSE); if (privkey) SECKEY_DestroyPrivateKey(privkey); if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE); return SECFailure; } OSStatus SecCmsSignerInfoVerifyCertificate(SecCmsSignerInfoRef signerinfo, SecKeychainRef keychainOrArray, CFTypeRef policies, SecTrustRef *trustRef) { SecCertificateRef cert; CFAbsoluteTime stime; OSStatus rv; CSSM_DATA_PTR *otherCerts; if ((cert = SecCmsSignerInfoGetSigningCertificate(signerinfo, keychainOrArray)) == NULL) { dprintf("SecCmsSignerInfoVerifyCertificate: no signing cert\n"); signerinfo->verificationStatus = SecCmsVSSigningCertNotFound; 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. */ CFTypeRef timeStampPolicies=SecPolicyCreateAppleTimeStampingAndRevocationPolicies(policies); if (SecCmsSignerInfoGetTimestampTimeWithPolicy(signerinfo, timeStampPolicies, &stime) != SECSuccess) if (SecCmsSignerInfoGetSigningTime(signerinfo, &stime) != SECSuccess) stime = CFAbsoluteTimeGetCurrent(); CFReleaseSafe(timeStampPolicies); rv = SecCmsSignedDataRawCerts(signerinfo->sigd, &otherCerts); if(rv) { return rv; } rv = CERT_VerifyCert(keychainOrArray, cert, otherCerts, policies, stime, trustRef); dprintfRC("SecCmsSignerInfoVerifyCertificate after vfy: certp %p cert.rc %d\n", cert, (int)CFGetRetainCount(cert)); if (rv || !trustRef) { if (PORT_GetError() == SEC_ERROR_UNTRUSTED_CERT) { /* Signature or digest level verificationStatus errors should supercede certificate level errors, so only change the verificationStatus if the status was GoodSignature. */ if (signerinfo->verificationStatus == SecCmsVSGoodSignature) signerinfo->verificationStatus = SecCmsVSSigningCertNotTrusted; } } /* FIXME isn't this leaking the cert? */ dprintf("SecCmsSignerInfoVerifyCertificate: CertVerify rtn %d\n", (int)rv); return rv; }