| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| /* |
| * PKCS7 implementation -- the exported parts that are used whether |
| * creating or decoding. |
| */ |
| |
| #include "p7local.h" |
| |
| #include "cert.h" |
| #include "secitem.h" |
| #include "secoid.h" |
| #include "pk11func.h" |
| |
| /* |
| * Find out (saving pointer to lookup result for future reference) |
| * and return the inner content type. |
| */ |
| SECOidTag |
| SEC_PKCS7ContentType(SEC_PKCS7ContentInfo *cinfo) |
| { |
| if (cinfo->contentTypeTag == NULL) |
| cinfo->contentTypeTag = SECOID_FindOID(&(cinfo->contentType)); |
| |
| if (cinfo->contentTypeTag == NULL) |
| return SEC_OID_UNKNOWN; |
| |
| return cinfo->contentTypeTag->offset; |
| } |
| |
| /* |
| * Destroy a PKCS7 contentInfo and all of its sub-pieces. |
| */ |
| void |
| SEC_PKCS7DestroyContentInfo(SEC_PKCS7ContentInfo *cinfo) |
| { |
| SECOidTag kind; |
| CERTCertificate **certs; |
| CERTCertificateList **certlists; |
| SEC_PKCS7SignerInfo **signerinfos; |
| SEC_PKCS7RecipientInfo **recipientinfos; |
| |
| PORT_Assert(cinfo->refCount > 0); |
| if (cinfo->refCount <= 0) |
| return; |
| |
| cinfo->refCount--; |
| if (cinfo->refCount > 0) |
| return; |
| |
| certs = NULL; |
| certlists = NULL; |
| recipientinfos = NULL; |
| signerinfos = NULL; |
| |
| kind = SEC_PKCS7ContentType(cinfo); |
| switch (kind) { |
| case SEC_OID_PKCS7_ENVELOPED_DATA: { |
| SEC_PKCS7EnvelopedData *edp; |
| |
| edp = cinfo->content.envelopedData; |
| if (edp != NULL) { |
| recipientinfos = edp->recipientInfos; |
| } |
| } break; |
| case SEC_OID_PKCS7_SIGNED_DATA: { |
| SEC_PKCS7SignedData *sdp; |
| |
| sdp = cinfo->content.signedData; |
| if (sdp != NULL) { |
| certs = sdp->certs; |
| certlists = sdp->certLists; |
| signerinfos = sdp->signerInfos; |
| } |
| } break; |
| case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: { |
| SEC_PKCS7SignedAndEnvelopedData *saedp; |
| |
| saedp = cinfo->content.signedAndEnvelopedData; |
| if (saedp != NULL) { |
| certs = saedp->certs; |
| certlists = saedp->certLists; |
| recipientinfos = saedp->recipientInfos; |
| signerinfos = saedp->signerInfos; |
| if (saedp->sigKey != NULL) |
| PK11_FreeSymKey(saedp->sigKey); |
| } |
| } break; |
| default: |
| /* XXX Anything else that needs to be "manually" freed/destroyed? */ |
| break; |
| } |
| |
| if (certs != NULL) { |
| CERTCertificate *cert; |
| |
| while ((cert = *certs++) != NULL) { |
| CERT_DestroyCertificate(cert); |
| } |
| } |
| |
| if (certlists != NULL) { |
| CERTCertificateList *certlist; |
| |
| while ((certlist = *certlists++) != NULL) { |
| CERT_DestroyCertificateList(certlist); |
| } |
| } |
| |
| if (recipientinfos != NULL) { |
| SEC_PKCS7RecipientInfo *ri; |
| |
| while ((ri = *recipientinfos++) != NULL) { |
| if (ri->cert != NULL) |
| CERT_DestroyCertificate(ri->cert); |
| } |
| } |
| |
| if (signerinfos != NULL) { |
| SEC_PKCS7SignerInfo *si; |
| |
| while ((si = *signerinfos++) != NULL) { |
| if (si->cert != NULL) |
| CERT_DestroyCertificate(si->cert); |
| if (si->certList != NULL) |
| CERT_DestroyCertificateList(si->certList); |
| } |
| } |
| |
| if (cinfo->poolp != NULL) { |
| PORT_FreeArena(cinfo->poolp, PR_FALSE); /* XXX clear it? */ |
| } |
| } |
| |
| /* |
| * Return a copy of the given contentInfo. The copy may be virtual |
| * or may be real -- either way, the result needs to be passed to |
| * SEC_PKCS7DestroyContentInfo later (as does the original). |
| */ |
| SEC_PKCS7ContentInfo * |
| SEC_PKCS7CopyContentInfo(SEC_PKCS7ContentInfo *cinfo) |
| { |
| if (cinfo == NULL) |
| return NULL; |
| |
| PORT_Assert(cinfo->refCount > 0); |
| |
| if (cinfo->created) { |
| /* |
| * Want to do a real copy of these; otherwise subsequent |
| * changes made to either copy are likely to be a surprise. |
| * XXX I suspect that this will not actually be called for yet, |
| * which is why the assert, so to notice if it is... |
| */ |
| PORT_Assert(0); |
| /* |
| * XXX Create a new pool here, and copy everything from |
| * within. For cert stuff, need to call the appropriate |
| * copy functions, etc. |
| */ |
| } |
| |
| cinfo->refCount++; |
| return cinfo; |
| } |
| |
| /* |
| * Return a pointer to the actual content. In the case of those types |
| * which are encrypted, this returns the *plain* content. |
| * XXX Needs revisiting if/when we handle nested encrypted types. |
| */ |
| SECItem * |
| SEC_PKCS7GetContent(SEC_PKCS7ContentInfo *cinfo) |
| { |
| SECOidTag kind; |
| |
| kind = SEC_PKCS7ContentType(cinfo); |
| switch (kind) { |
| case SEC_OID_PKCS7_DATA: |
| return cinfo->content.data; |
| case SEC_OID_PKCS7_DIGESTED_DATA: { |
| SEC_PKCS7DigestedData *digd; |
| |
| digd = cinfo->content.digestedData; |
| if (digd == NULL) |
| break; |
| return SEC_PKCS7GetContent(&(digd->contentInfo)); |
| } |
| case SEC_OID_PKCS7_ENCRYPTED_DATA: { |
| SEC_PKCS7EncryptedData *encd; |
| |
| encd = cinfo->content.encryptedData; |
| if (encd == NULL) |
| break; |
| return &(encd->encContentInfo.plainContent); |
| } |
| case SEC_OID_PKCS7_ENVELOPED_DATA: { |
| SEC_PKCS7EnvelopedData *envd; |
| |
| envd = cinfo->content.envelopedData; |
| if (envd == NULL) |
| break; |
| return &(envd->encContentInfo.plainContent); |
| } |
| case SEC_OID_PKCS7_SIGNED_DATA: { |
| SEC_PKCS7SignedData *sigd; |
| |
| sigd = cinfo->content.signedData; |
| if (sigd == NULL) |
| break; |
| return SEC_PKCS7GetContent(&(sigd->contentInfo)); |
| } |
| case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: { |
| SEC_PKCS7SignedAndEnvelopedData *saed; |
| |
| saed = cinfo->content.signedAndEnvelopedData; |
| if (saed == NULL) |
| break; |
| return &(saed->encContentInfo.plainContent); |
| } |
| default: |
| PORT_Assert(0); |
| break; |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * XXX Fix the placement and formatting of the |
| * following routines (i.e. make them consistent with the rest of |
| * the pkcs7 code -- I think some/many belong in other files and |
| * they all need a formatting/style rehaul) |
| */ |
| |
| /* retrieve the algorithm identifier for encrypted data. |
| * the identifier returned is a copy of the algorithm identifier |
| * in the content info and needs to be freed after being used. |
| * |
| * cinfo is the content info for which to retrieve the |
| * encryption algorithm. |
| * |
| * if the content info is not encrypted data or an error |
| * occurs NULL is returned. |
| */ |
| SECAlgorithmID * |
| SEC_PKCS7GetEncryptionAlgorithm(SEC_PKCS7ContentInfo *cinfo) |
| { |
| SECAlgorithmID *alg = 0; |
| switch (SEC_PKCS7ContentType(cinfo)) { |
| case SEC_OID_PKCS7_ENCRYPTED_DATA: |
| alg = &cinfo->content.encryptedData->encContentInfo.contentEncAlg; |
| break; |
| case SEC_OID_PKCS7_ENVELOPED_DATA: |
| alg = &cinfo->content.envelopedData->encContentInfo.contentEncAlg; |
| break; |
| case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA: |
| alg = &cinfo->content.signedAndEnvelopedData |
| ->encContentInfo.contentEncAlg; |
| break; |
| default: |
| alg = 0; |
| break; |
| } |
| |
| return alg; |
| } |
| |
| /* set the content of the content info. For data content infos, |
| * the data is set. For encrytped content infos, the plainContent |
| * is set, and is expected to be encrypted later. |
| * |
| * cinfo is the content info where the data will be set |
| * |
| * buf is a buffer of the data to set |
| * |
| * len is the length of the data being set. |
| * |
| * in the event of an error, SECFailure is returned. SECSuccess |
| * indicates the content was successfully set. |
| */ |
| SECStatus |
| SEC_PKCS7SetContent(SEC_PKCS7ContentInfo *cinfo, |
| const char *buf, |
| unsigned long len) |
| { |
| SECOidTag cinfo_type; |
| SECStatus rv; |
| SECItem content; |
| SECOidData *contentTypeTag = NULL; |
| |
| content.type = siBuffer; |
| content.data = (unsigned char *)buf; |
| content.len = len; |
| |
| cinfo_type = SEC_PKCS7ContentType(cinfo); |
| |
| /* set inner content */ |
| switch (cinfo_type) { |
| case SEC_OID_PKCS7_SIGNED_DATA: |
| if (content.len > 0) { |
| /* we "leak" the old content here, but as it's all in the pool */ |
| /* it does not really matter */ |
| |
| /* create content item if necessary */ |
| if (cinfo->content.signedData->contentInfo.content.data == NULL) |
| cinfo->content.signedData->contentInfo.content.data = SECITEM_AllocItem(cinfo->poolp, NULL, 0); |
| rv = SECITEM_CopyItem(cinfo->poolp, |
| cinfo->content.signedData->contentInfo.content.data, |
| &content); |
| } else { |
| cinfo->content.signedData->contentInfo.content.data->data = NULL; |
| cinfo->content.signedData->contentInfo.content.data->len = 0; |
| rv = SECSuccess; |
| } |
| if (rv == SECFailure) |
| goto loser; |
| |
| break; |
| case SEC_OID_PKCS7_ENCRYPTED_DATA: |
| /* XXX this forces the inner content type to be "data" */ |
| /* do we really want to override without asking or reason? */ |
| contentTypeTag = SECOID_FindOIDByTag(SEC_OID_PKCS7_DATA); |
| if (contentTypeTag == NULL) |
| goto loser; |
| rv = SECITEM_CopyItem(cinfo->poolp, |
| &(cinfo->content.encryptedData->encContentInfo.contentType), |
| &(contentTypeTag->oid)); |
| if (rv == SECFailure) |
| goto loser; |
| if (content.len > 0) { |
| rv = SECITEM_CopyItem(cinfo->poolp, |
| &(cinfo->content.encryptedData->encContentInfo.plainContent), |
| &content); |
| } else { |
| cinfo->content.encryptedData->encContentInfo.plainContent.data = NULL; |
| cinfo->content.encryptedData->encContentInfo.encContent.data = NULL; |
| cinfo->content.encryptedData->encContentInfo.plainContent.len = 0; |
| cinfo->content.encryptedData->encContentInfo.encContent.len = 0; |
| rv = SECSuccess; |
| } |
| if (rv == SECFailure) |
| goto loser; |
| break; |
| case SEC_OID_PKCS7_DATA: |
| cinfo->content.data = (SECItem *)PORT_ArenaZAlloc(cinfo->poolp, |
| sizeof(SECItem)); |
| if (cinfo->content.data == NULL) |
| goto loser; |
| if (content.len > 0) { |
| rv = SECITEM_CopyItem(cinfo->poolp, |
| cinfo->content.data, &content); |
| } else { |
| /* handle case with NULL content */ |
| rv = SECSuccess; |
| } |
| if (rv == SECFailure) |
| goto loser; |
| break; |
| default: |
| goto loser; |
| } |
| |
| return SECSuccess; |
| |
| loser: |
| |
| return SECFailure; |
| } |
| |
| /* the content of an encrypted data content info is encrypted. |
| * it is assumed that for encrypted data, that the data has already |
| * been set and is in the "plainContent" field of the content info. |
| * |
| * cinfo is the content info to encrypt |
| * |
| * key is the key with which to perform the encryption. if the |
| * algorithm is a password based encryption algorithm, the |
| * key is actually a password which will be processed per |
| * PKCS #5. |
| * |
| * in the event of an error, SECFailure is returned. SECSuccess |
| * indicates a success. |
| */ |
| SECStatus |
| SEC_PKCS7EncryptContents(PLArenaPool *poolp, |
| SEC_PKCS7ContentInfo *cinfo, |
| SECItem *key, |
| void *wincx) |
| { |
| SECAlgorithmID *algid = NULL; |
| SECItem *src; |
| SECItem *dest; |
| SECItem *blocked_data = NULL; |
| void *mark; |
| void *cx; |
| PK11SymKey *eKey = NULL; |
| PK11SlotInfo *slot = NULL; |
| |
| CK_MECHANISM_TYPE cryptoMechType; |
| int bs; |
| SECStatus rv = SECFailure; |
| SECItem *c_param = NULL; |
| |
| if ((cinfo == NULL) || (key == NULL)) |
| return SECFailure; |
| |
| if (SEC_PKCS7ContentType(cinfo) != SEC_OID_PKCS7_ENCRYPTED_DATA) |
| return SECFailure; |
| |
| algid = SEC_PKCS7GetEncryptionAlgorithm(cinfo); |
| if (algid == NULL) |
| return SECFailure; |
| |
| if (poolp == NULL) |
| poolp = cinfo->poolp; |
| |
| mark = PORT_ArenaMark(poolp); |
| |
| src = &cinfo->content.encryptedData->encContentInfo.plainContent; |
| dest = &cinfo->content.encryptedData->encContentInfo.encContent; |
| dest->data = (unsigned char *)PORT_ArenaZAlloc(poolp, (src->len + 64)); |
| dest->len = (src->len + 64); |
| if (dest->data == NULL) { |
| rv = SECFailure; |
| goto loser; |
| } |
| |
| slot = PK11_GetInternalKeySlot(); |
| if (slot == NULL) { |
| rv = SECFailure; |
| goto loser; |
| } |
| |
| eKey = PK11_PBEKeyGen(slot, algid, key, PR_FALSE, wincx); |
| if (eKey == NULL) { |
| rv = SECFailure; |
| goto loser; |
| } |
| |
| cryptoMechType = PK11_GetPBECryptoMechanism(algid, &c_param, key); |
| if (cryptoMechType == CKM_INVALID_MECHANISM) { |
| rv = SECFailure; |
| goto loser; |
| } |
| |
| /* block according to PKCS 8 */ |
| bs = PK11_GetBlockSize(cryptoMechType, c_param); |
| rv = SECSuccess; |
| if (bs) { |
| char pad_char; |
| pad_char = (char)(bs - (src->len % bs)); |
| if (src->len % bs) { |
| rv = SECSuccess; |
| blocked_data = PK11_BlockData(src, bs); |
| if (blocked_data) { |
| PORT_Memset((blocked_data->data + blocked_data->len - (int)pad_char), |
| pad_char, (int)pad_char); |
| } else { |
| rv = SECFailure; |
| goto loser; |
| } |
| } else { |
| blocked_data = SECITEM_DupItem(src); |
| if (blocked_data) { |
| blocked_data->data = (unsigned char *)PORT_Realloc( |
| blocked_data->data, |
| blocked_data->len + bs); |
| if (blocked_data->data) { |
| blocked_data->len += bs; |
| PORT_Memset((blocked_data->data + src->len), (char)bs, bs); |
| } else { |
| rv = SECFailure; |
| goto loser; |
| } |
| } else { |
| rv = SECFailure; |
| goto loser; |
| } |
| } |
| } else { |
| blocked_data = SECITEM_DupItem(src); |
| if (!blocked_data) { |
| rv = SECFailure; |
| goto loser; |
| } |
| } |
| |
| cx = PK11_CreateContextBySymKey(cryptoMechType, CKA_ENCRYPT, |
| eKey, c_param); |
| if (cx == NULL) { |
| rv = SECFailure; |
| goto loser; |
| } |
| |
| rv = PK11_CipherOp((PK11Context *)cx, dest->data, (int *)(&dest->len), |
| (int)(src->len + 64), blocked_data->data, |
| (int)blocked_data->len); |
| PK11_DestroyContext((PK11Context *)cx, PR_TRUE); |
| |
| loser: |
| /* let success fall through */ |
| if (blocked_data != NULL) |
| SECITEM_ZfreeItem(blocked_data, PR_TRUE); |
| |
| if (rv == SECFailure) |
| PORT_ArenaRelease(poolp, mark); |
| else |
| PORT_ArenaUnmark(poolp, mark); |
| |
| if (eKey != NULL) |
| PK11_FreeSymKey(eKey); |
| |
| if (slot != NULL) |
| PK11_FreeSlot(slot); |
| |
| if (c_param != NULL) |
| SECITEM_ZfreeItem(c_param, PR_TRUE); |
| |
| return rv; |
| } |
| |
| /* the content of an encrypted data content info is decrypted. |
| * it is assumed that for encrypted data, that the data has already |
| * been set and is in the "encContent" field of the content info. |
| * |
| * cinfo is the content info to decrypt |
| * |
| * key is the key with which to perform the decryption. if the |
| * algorithm is a password based encryption algorithm, the |
| * key is actually a password which will be processed per |
| * PKCS #5. |
| * |
| * in the event of an error, SECFailure is returned. SECSuccess |
| * indicates a success. |
| */ |
| SECStatus |
| SEC_PKCS7DecryptContents(PLArenaPool *poolp, |
| SEC_PKCS7ContentInfo *cinfo, |
| SECItem *key, |
| void *wincx) |
| { |
| SECAlgorithmID *algid = NULL; |
| SECStatus rv = SECFailure; |
| SECItem *dest, *src; |
| void *mark; |
| |
| PK11SymKey *eKey = NULL; |
| PK11SlotInfo *slot = NULL; |
| CK_MECHANISM_TYPE cryptoMechType; |
| void *cx; |
| SECItem *c_param = NULL; |
| int bs; |
| |
| if ((cinfo == NULL) || (key == NULL)) |
| return SECFailure; |
| |
| if (SEC_PKCS7ContentType(cinfo) != SEC_OID_PKCS7_ENCRYPTED_DATA) |
| return SECFailure; |
| |
| algid = SEC_PKCS7GetEncryptionAlgorithm(cinfo); |
| if (algid == NULL) |
| return SECFailure; |
| |
| if (poolp == NULL) |
| poolp = cinfo->poolp; |
| |
| mark = PORT_ArenaMark(poolp); |
| |
| src = &cinfo->content.encryptedData->encContentInfo.encContent; |
| dest = &cinfo->content.encryptedData->encContentInfo.plainContent; |
| dest->data = (unsigned char *)PORT_ArenaZAlloc(poolp, (src->len + 64)); |
| dest->len = (src->len + 64); |
| if (dest->data == NULL) { |
| rv = SECFailure; |
| goto loser; |
| } |
| |
| slot = PK11_GetInternalKeySlot(); |
| if (slot == NULL) { |
| rv = SECFailure; |
| goto loser; |
| } |
| |
| eKey = PK11_PBEKeyGen(slot, algid, key, PR_FALSE, wincx); |
| if (eKey == NULL) { |
| rv = SECFailure; |
| goto loser; |
| } |
| |
| cryptoMechType = PK11_GetPBECryptoMechanism(algid, &c_param, key); |
| if (cryptoMechType == CKM_INVALID_MECHANISM) { |
| rv = SECFailure; |
| goto loser; |
| } |
| |
| cx = PK11_CreateContextBySymKey(cryptoMechType, CKA_DECRYPT, |
| eKey, c_param); |
| if (cx == NULL) { |
| rv = SECFailure; |
| goto loser; |
| } |
| |
| rv = PK11_CipherOp((PK11Context *)cx, dest->data, (int *)(&dest->len), |
| (int)(src->len + 64), src->data, (int)src->len); |
| PK11_DestroyContext((PK11Context *)cx, PR_TRUE); |
| |
| bs = PK11_GetBlockSize(cryptoMechType, c_param); |
| if (bs) { |
| /* check for proper badding in block algorithms. this assumes |
| * RC2 cbc or a DES cbc variant. and the padding is thus defined |
| */ |
| if (((int)dest->data[dest->len - 1] <= bs) && |
| ((int)dest->data[dest->len - 1] > 0)) { |
| dest->len -= (int)dest->data[dest->len - 1]; |
| } else { |
| rv = SECFailure; |
| /* set an error ? */ |
| } |
| } |
| |
| loser: |
| /* let success fall through */ |
| if (rv == SECFailure) |
| PORT_ArenaRelease(poolp, mark); |
| else |
| PORT_ArenaUnmark(poolp, mark); |
| |
| if (eKey != NULL) |
| PK11_FreeSymKey(eKey); |
| |
| if (slot != NULL) |
| PK11_FreeSlot(slot); |
| |
| if (c_param != NULL) |
| SECITEM_ZfreeItem(c_param, PR_TRUE); |
| |
| return rv; |
| } |
| |
| SECItem ** |
| SEC_PKCS7GetCertificateList(SEC_PKCS7ContentInfo *cinfo) |
| { |
| switch (SEC_PKCS7ContentType(cinfo)) { |
| case SEC_OID_PKCS7_SIGNED_DATA: |
| return cinfo->content.signedData->rawCerts; |
| break; |
| default: |
| return NULL; |
| break; |
| } |
| } |
| |
| int |
| SEC_PKCS7GetKeyLength(SEC_PKCS7ContentInfo *cinfo) |
| { |
| if (cinfo->contentTypeTag->offset == SEC_OID_PKCS7_ENVELOPED_DATA) |
| return cinfo->content.envelopedData->encContentInfo.keysize; |
| else |
| return 0; |
| } |