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