mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-11-14 03:30:17 +01:00
44b7f056d9
bug1001332, 56b691c003ad, bug1086145, bug1054069, bug1155922, bug991783, bug1125025, bug1162521, bug1162644, bug1132941, bug1164364, bug1166205, bug1166163, bug1166515, bug1138554, bug1167046, bug1167043, bug1169451, bug1172128, bug1170322, bug102794, bug1128184, bug557830, bug1174648, bug1180244, bug1177784, bug1173413, bug1169174, bug1084669, bug951455, bug1183395, bug1177430, bug1183827, bug1160139, bug1154106, bug1142209, bug1185033, bug1193467, bug1182667(with sha512 changes backed out, which breaks VC6 compilation), bug1158489, bug337796
1108 lines
29 KiB
C
1108 lines
29 KiB
C
/* 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 encoding.
|
|
*/
|
|
|
|
#include "p7local.h"
|
|
|
|
#include "cert.h"
|
|
#include "cryptohi.h"
|
|
#include "keyhi.h"
|
|
#include "secasn1.h"
|
|
#include "secoid.h"
|
|
#include "secitem.h"
|
|
#include "pk11func.h"
|
|
#include "secerr.h"
|
|
#include "sechash.h" /* for HASH_GetHashObject() */
|
|
|
|
struct sec_pkcs7_encoder_output {
|
|
SEC_PKCS7EncoderOutputCallback outputfn;
|
|
void *outputarg;
|
|
};
|
|
|
|
struct SEC_PKCS7EncoderContextStr {
|
|
SEC_ASN1EncoderContext *ecx;
|
|
SEC_PKCS7ContentInfo *cinfo;
|
|
struct sec_pkcs7_encoder_output output;
|
|
sec_PKCS7CipherObject *encryptobj;
|
|
const SECHashObject *digestobj;
|
|
void *digestcx;
|
|
};
|
|
|
|
|
|
/*
|
|
* The little output function that the ASN.1 encoder calls to hand
|
|
* us bytes which we in turn hand back to our caller (via the callback
|
|
* they gave us).
|
|
*/
|
|
static void
|
|
sec_pkcs7_encoder_out(void *arg, const char *buf, unsigned long len,
|
|
int depth, SEC_ASN1EncodingPart data_kind)
|
|
{
|
|
struct sec_pkcs7_encoder_output *output;
|
|
|
|
output = (struct sec_pkcs7_encoder_output*)arg;
|
|
output->outputfn (output->outputarg, buf, len);
|
|
}
|
|
|
|
static sec_PKCS7CipherObject *
|
|
sec_pkcs7_encoder_start_encrypt (SEC_PKCS7ContentInfo *cinfo,
|
|
PK11SymKey *orig_bulkkey)
|
|
{
|
|
SECOidTag kind;
|
|
sec_PKCS7CipherObject *encryptobj;
|
|
SEC_PKCS7RecipientInfo **recipientinfos, *ri;
|
|
SEC_PKCS7EncryptedContentInfo *enccinfo;
|
|
SECKEYPublicKey *publickey = NULL;
|
|
SECKEYPrivateKey *ourPrivKey = NULL;
|
|
PK11SymKey *bulkkey;
|
|
void *mark;
|
|
int i;
|
|
PLArenaPool *arena = NULL;
|
|
|
|
kind = SEC_PKCS7ContentType (cinfo);
|
|
switch (kind) {
|
|
default:
|
|
case SEC_OID_PKCS7_DATA:
|
|
case SEC_OID_PKCS7_DIGESTED_DATA:
|
|
case SEC_OID_PKCS7_SIGNED_DATA:
|
|
recipientinfos = NULL;
|
|
enccinfo = NULL;
|
|
break;
|
|
case SEC_OID_PKCS7_ENCRYPTED_DATA:
|
|
{
|
|
SEC_PKCS7EncryptedData *encdp;
|
|
|
|
/* To do EncryptedData we *must* be given a bulk key. */
|
|
PORT_Assert (orig_bulkkey != NULL);
|
|
if (orig_bulkkey == NULL) {
|
|
/* XXX error? */
|
|
return NULL;
|
|
}
|
|
|
|
encdp = cinfo->content.encryptedData;
|
|
recipientinfos = NULL;
|
|
enccinfo = &(encdp->encContentInfo);
|
|
}
|
|
break;
|
|
case SEC_OID_PKCS7_ENVELOPED_DATA:
|
|
{
|
|
SEC_PKCS7EnvelopedData *envdp;
|
|
|
|
envdp = cinfo->content.envelopedData;
|
|
recipientinfos = envdp->recipientInfos;
|
|
enccinfo = &(envdp->encContentInfo);
|
|
}
|
|
break;
|
|
case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
|
|
{
|
|
SEC_PKCS7SignedAndEnvelopedData *saedp;
|
|
|
|
saedp = cinfo->content.signedAndEnvelopedData;
|
|
recipientinfos = saedp->recipientInfos;
|
|
enccinfo = &(saedp->encContentInfo);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (enccinfo == NULL)
|
|
return NULL;
|
|
|
|
bulkkey = orig_bulkkey;
|
|
if (bulkkey == NULL) {
|
|
CK_MECHANISM_TYPE type = PK11_AlgtagToMechanism(enccinfo->encalg);
|
|
PK11SlotInfo *slot;
|
|
|
|
|
|
slot = PK11_GetBestSlot(type,cinfo->pwfn_arg);
|
|
if (slot == NULL) {
|
|
return NULL;
|
|
}
|
|
bulkkey = PK11_KeyGen(slot,type,NULL, enccinfo->keysize/8,
|
|
cinfo->pwfn_arg);
|
|
PK11_FreeSlot(slot);
|
|
if (bulkkey == NULL) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
encryptobj = NULL;
|
|
mark = PORT_ArenaMark (cinfo->poolp);
|
|
|
|
/*
|
|
* Encrypt the bulk key with the public key of each recipient.
|
|
*/
|
|
for (i = 0; recipientinfos && (ri = recipientinfos[i]) != NULL; i++) {
|
|
CERTCertificate *cert;
|
|
SECOidTag certalgtag, encalgtag;
|
|
SECStatus rv;
|
|
int data_len;
|
|
SECItem *params = NULL;
|
|
|
|
cert = ri->cert;
|
|
PORT_Assert (cert != NULL);
|
|
if (cert == NULL)
|
|
continue;
|
|
|
|
/*
|
|
* XXX Want an interface that takes a cert and some data and
|
|
* fills in an algorithmID and encrypts the data with the public
|
|
* key from the cert. Or, give me two interfaces -- one which
|
|
* gets the algorithm tag from a cert (I should not have to go
|
|
* down into the subjectPublicKeyInfo myself) and another which
|
|
* takes a public key and algorithm tag and data and encrypts
|
|
* the data. Or something like that. The point is that all
|
|
* of the following hardwired RSA stuff should be done elsewhere.
|
|
*/
|
|
|
|
certalgtag=SECOID_GetAlgorithmTag(&(cert->subjectPublicKeyInfo.algorithm));
|
|
|
|
switch (certalgtag) {
|
|
case SEC_OID_PKCS1_RSA_ENCRYPTION:
|
|
encalgtag = certalgtag;
|
|
publickey = CERT_ExtractPublicKey (cert);
|
|
if (publickey == NULL) goto loser;
|
|
|
|
data_len = SECKEY_PublicKeyStrength(publickey);
|
|
ri->encKey.data =
|
|
(unsigned char*)PORT_ArenaAlloc(cinfo->poolp ,data_len);
|
|
ri->encKey.len = data_len;
|
|
if (ri->encKey.data == NULL) goto loser;
|
|
|
|
rv = PK11_PubWrapSymKey(PK11_AlgtagToMechanism(certalgtag),publickey,
|
|
bulkkey,&ri->encKey);
|
|
|
|
SECKEY_DestroyPublicKey(publickey);
|
|
publickey = NULL;
|
|
if (rv != SECSuccess) goto loser;
|
|
params = NULL; /* paranoia */
|
|
break;
|
|
default:
|
|
PORT_SetError (SEC_ERROR_INVALID_ALGORITHM);
|
|
goto loser;
|
|
}
|
|
|
|
rv = SECOID_SetAlgorithmID(cinfo->poolp, &ri->keyEncAlg, encalgtag,
|
|
params);
|
|
if (rv != SECSuccess)
|
|
goto loser;
|
|
if (arena) PORT_FreeArena(arena,PR_FALSE);
|
|
arena = NULL;
|
|
}
|
|
|
|
encryptobj = sec_PKCS7CreateEncryptObject (cinfo->poolp, bulkkey,
|
|
enccinfo->encalg,
|
|
&(enccinfo->contentEncAlg));
|
|
if (encryptobj != NULL) {
|
|
PORT_ArenaUnmark (cinfo->poolp, mark);
|
|
mark = NULL; /* good one; do not want to release */
|
|
}
|
|
/* fallthru */
|
|
|
|
loser:
|
|
if (arena) {
|
|
PORT_FreeArena(arena, PR_FALSE);
|
|
}
|
|
if (publickey) {
|
|
SECKEY_DestroyPublicKey(publickey);
|
|
}
|
|
if (ourPrivKey) {
|
|
SECKEY_DestroyPrivateKey(ourPrivKey);
|
|
}
|
|
if (mark != NULL) {
|
|
PORT_ArenaRelease (cinfo->poolp, mark);
|
|
}
|
|
if (orig_bulkkey == NULL) {
|
|
if (bulkkey) PK11_FreeSymKey(bulkkey);
|
|
}
|
|
|
|
return encryptobj;
|
|
}
|
|
|
|
|
|
static void
|
|
sec_pkcs7_encoder_notify (void *arg, PRBool before, void *dest, int depth)
|
|
{
|
|
SEC_PKCS7EncoderContext *p7ecx;
|
|
SEC_PKCS7ContentInfo *cinfo;
|
|
SECOidTag kind;
|
|
PRBool before_content;
|
|
|
|
/*
|
|
* We want to notice just before the content field. After fields are
|
|
* not interesting to us.
|
|
*/
|
|
if (!before)
|
|
return;
|
|
|
|
p7ecx = (SEC_PKCS7EncoderContext*)arg;
|
|
cinfo = p7ecx->cinfo;
|
|
|
|
before_content = PR_FALSE;
|
|
|
|
/*
|
|
* Watch for the content field, at which point we want to instruct
|
|
* the ASN.1 encoder to start taking bytes from the buffer.
|
|
*
|
|
* XXX The following assumes the inner content type is data;
|
|
* if/when we want to handle fully nested types, this will have
|
|
* to recurse until reaching the innermost data content.
|
|
*/
|
|
kind = SEC_PKCS7ContentType (cinfo);
|
|
switch (kind) {
|
|
default:
|
|
case SEC_OID_PKCS7_DATA:
|
|
if (dest == &(cinfo->content.data))
|
|
before_content = PR_TRUE;
|
|
break;
|
|
|
|
case SEC_OID_PKCS7_DIGESTED_DATA:
|
|
{
|
|
SEC_PKCS7DigestedData *digd;
|
|
|
|
digd = cinfo->content.digestedData;
|
|
if (digd == NULL)
|
|
break;
|
|
|
|
if (dest == &(digd->contentInfo.content))
|
|
before_content = PR_TRUE;
|
|
}
|
|
break;
|
|
|
|
case SEC_OID_PKCS7_ENCRYPTED_DATA:
|
|
{
|
|
SEC_PKCS7EncryptedData *encd;
|
|
|
|
encd = cinfo->content.encryptedData;
|
|
if (encd == NULL)
|
|
break;
|
|
|
|
if (dest == &(encd->encContentInfo.encContent))
|
|
before_content = PR_TRUE;
|
|
}
|
|
break;
|
|
|
|
case SEC_OID_PKCS7_ENVELOPED_DATA:
|
|
{
|
|
SEC_PKCS7EnvelopedData *envd;
|
|
|
|
envd = cinfo->content.envelopedData;
|
|
if (envd == NULL)
|
|
break;
|
|
|
|
if (dest == &(envd->encContentInfo.encContent))
|
|
before_content = PR_TRUE;
|
|
}
|
|
break;
|
|
|
|
case SEC_OID_PKCS7_SIGNED_DATA:
|
|
{
|
|
SEC_PKCS7SignedData *sigd;
|
|
|
|
sigd = cinfo->content.signedData;
|
|
if (sigd == NULL)
|
|
break;
|
|
|
|
if (dest == &(sigd->contentInfo.content))
|
|
before_content = PR_TRUE;
|
|
}
|
|
break;
|
|
|
|
case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
|
|
{
|
|
SEC_PKCS7SignedAndEnvelopedData *saed;
|
|
|
|
saed = cinfo->content.signedAndEnvelopedData;
|
|
if (saed == NULL)
|
|
break;
|
|
|
|
if (dest == &(saed->encContentInfo.encContent))
|
|
before_content = PR_TRUE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (before_content) {
|
|
/*
|
|
* This will cause the next SEC_ASN1EncoderUpdate to take the
|
|
* contents bytes from the passed-in buffer.
|
|
*/
|
|
SEC_ASN1EncoderSetTakeFromBuf (p7ecx->ecx);
|
|
/*
|
|
* And that is all we needed this notify function for.
|
|
*/
|
|
SEC_ASN1EncoderClearNotifyProc (p7ecx->ecx);
|
|
}
|
|
}
|
|
|
|
|
|
static SEC_PKCS7EncoderContext *
|
|
sec_pkcs7_encoder_start_contexts (SEC_PKCS7ContentInfo *cinfo,
|
|
PK11SymKey *bulkkey)
|
|
{
|
|
SEC_PKCS7EncoderContext *p7ecx;
|
|
SECOidTag kind;
|
|
PRBool encrypt;
|
|
SECItem **digests;
|
|
SECAlgorithmID *digestalg, **digestalgs;
|
|
|
|
p7ecx =
|
|
(SEC_PKCS7EncoderContext*)PORT_ZAlloc (sizeof(SEC_PKCS7EncoderContext));
|
|
if (p7ecx == NULL)
|
|
return NULL;
|
|
|
|
digests = NULL;
|
|
digestalg = NULL;
|
|
digestalgs = NULL;
|
|
encrypt = PR_FALSE;
|
|
|
|
kind = SEC_PKCS7ContentType (cinfo);
|
|
switch (kind) {
|
|
default:
|
|
case SEC_OID_PKCS7_DATA:
|
|
break;
|
|
case SEC_OID_PKCS7_DIGESTED_DATA:
|
|
digestalg = &(cinfo->content.digestedData->digestAlg);
|
|
break;
|
|
case SEC_OID_PKCS7_SIGNED_DATA:
|
|
digests = cinfo->content.signedData->digests;
|
|
digestalgs = cinfo->content.signedData->digestAlgorithms;
|
|
break;
|
|
case SEC_OID_PKCS7_ENCRYPTED_DATA:
|
|
case SEC_OID_PKCS7_ENVELOPED_DATA:
|
|
encrypt = PR_TRUE;
|
|
break;
|
|
case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
|
|
digests = cinfo->content.signedAndEnvelopedData->digests;
|
|
digestalgs = cinfo->content.signedAndEnvelopedData->digestAlgorithms;
|
|
encrypt = PR_TRUE;
|
|
break;
|
|
}
|
|
|
|
if (encrypt) {
|
|
p7ecx->encryptobj = sec_pkcs7_encoder_start_encrypt (cinfo, bulkkey);
|
|
if (p7ecx->encryptobj == NULL) {
|
|
PORT_Free (p7ecx);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (digestalgs != NULL) {
|
|
if (digests != NULL) {
|
|
/* digests already created (probably for detached data) */
|
|
digestalg = NULL;
|
|
} else {
|
|
/*
|
|
* XXX Some day we should handle multiple digests; for now,
|
|
* assume only one will be done.
|
|
*/
|
|
PORT_Assert (digestalgs[0] != NULL && digestalgs[1] == NULL);
|
|
digestalg = digestalgs[0];
|
|
}
|
|
}
|
|
|
|
if (digestalg != NULL) {
|
|
SECOidTag oidTag = SECOID_FindOIDTag(&(digestalg->algorithm));
|
|
|
|
p7ecx->digestobj = HASH_GetHashObjectByOidTag(oidTag);
|
|
if (p7ecx->digestobj != NULL) {
|
|
p7ecx->digestcx = (* p7ecx->digestobj->create) ();
|
|
if (p7ecx->digestcx == NULL)
|
|
p7ecx->digestobj = NULL;
|
|
else
|
|
(* p7ecx->digestobj->begin) (p7ecx->digestcx);
|
|
}
|
|
if (p7ecx->digestobj == NULL) {
|
|
if (p7ecx->encryptobj != NULL)
|
|
sec_PKCS7DestroyEncryptObject (p7ecx->encryptobj);
|
|
PORT_Free (p7ecx);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
p7ecx->cinfo = cinfo;
|
|
return p7ecx;
|
|
}
|
|
|
|
|
|
SEC_PKCS7EncoderContext *
|
|
SEC_PKCS7EncoderStart (SEC_PKCS7ContentInfo *cinfo,
|
|
SEC_PKCS7EncoderOutputCallback outputfn,
|
|
void *outputarg,
|
|
PK11SymKey *bulkkey)
|
|
{
|
|
SEC_PKCS7EncoderContext *p7ecx;
|
|
SECStatus rv;
|
|
|
|
p7ecx = sec_pkcs7_encoder_start_contexts (cinfo, bulkkey);
|
|
if (p7ecx == NULL)
|
|
return NULL;
|
|
|
|
p7ecx->output.outputfn = outputfn;
|
|
p7ecx->output.outputarg = outputarg;
|
|
|
|
/*
|
|
* Initialize the BER encoder.
|
|
*/
|
|
p7ecx->ecx = SEC_ASN1EncoderStart (cinfo, sec_PKCS7ContentInfoTemplate,
|
|
sec_pkcs7_encoder_out, &(p7ecx->output));
|
|
if (p7ecx->ecx == NULL) {
|
|
PORT_Free (p7ecx);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Indicate that we are streaming. We will be streaming until we
|
|
* get past the contents bytes.
|
|
*/
|
|
SEC_ASN1EncoderSetStreaming (p7ecx->ecx);
|
|
|
|
/*
|
|
* The notify function will watch for the contents field.
|
|
*/
|
|
SEC_ASN1EncoderSetNotifyProc (p7ecx->ecx, sec_pkcs7_encoder_notify, p7ecx);
|
|
|
|
/*
|
|
* This will encode everything up to the content bytes. (The notify
|
|
* function will then cause the encoding to stop there.) Then our
|
|
* caller can start passing contents bytes to our Update, which we
|
|
* will pass along.
|
|
*/
|
|
rv = SEC_ASN1EncoderUpdate (p7ecx->ecx, NULL, 0);
|
|
if (rv != SECSuccess) {
|
|
PORT_Free (p7ecx);
|
|
return NULL;
|
|
}
|
|
|
|
return p7ecx;
|
|
}
|
|
|
|
|
|
/*
|
|
* XXX If/when we support nested contents, this needs to be revised.
|
|
*/
|
|
static SECStatus
|
|
sec_pkcs7_encoder_work_data (SEC_PKCS7EncoderContext *p7ecx, SECItem *dest,
|
|
const unsigned char *data, unsigned long len,
|
|
PRBool final)
|
|
{
|
|
unsigned char *buf = NULL;
|
|
SECStatus rv;
|
|
|
|
|
|
rv = SECSuccess; /* may as well be optimistic */
|
|
|
|
/*
|
|
* We should really have data to process, or we should be trying
|
|
* to finish/flush the last block. (This is an overly paranoid
|
|
* check since all callers are in this file and simple inspection
|
|
* proves they do it right. But it could find a bug in future
|
|
* modifications/development, that is why it is here.)
|
|
*/
|
|
PORT_Assert ((data != NULL && len) || final);
|
|
|
|
/*
|
|
* Update the running digest.
|
|
* XXX This needs modification if/when we handle multiple digests.
|
|
*/
|
|
if (len && p7ecx->digestobj != NULL) {
|
|
(* p7ecx->digestobj->update) (p7ecx->digestcx, data, len);
|
|
}
|
|
|
|
/*
|
|
* Encrypt this chunk.
|
|
*/
|
|
if (p7ecx->encryptobj != NULL) {
|
|
/* XXX the following lengths should all be longs? */
|
|
unsigned int inlen; /* length of data being encrypted */
|
|
unsigned int outlen; /* length of encrypted data */
|
|
unsigned int buflen; /* length available for encrypted data */
|
|
|
|
inlen = len;
|
|
buflen = sec_PKCS7EncryptLength (p7ecx->encryptobj, inlen, final);
|
|
if (buflen == 0) {
|
|
/*
|
|
* No output is expected, but the input data may be buffered
|
|
* so we still have to call Encrypt.
|
|
*/
|
|
rv = sec_PKCS7Encrypt (p7ecx->encryptobj, NULL, NULL, 0,
|
|
data, inlen, final);
|
|
if (final) {
|
|
len = 0;
|
|
goto done;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
if (dest != NULL)
|
|
buf = (unsigned char*)PORT_ArenaAlloc(p7ecx->cinfo->poolp, buflen);
|
|
else
|
|
buf = (unsigned char*)PORT_Alloc (buflen);
|
|
|
|
if (buf == NULL) {
|
|
rv = SECFailure;
|
|
} else {
|
|
rv = sec_PKCS7Encrypt (p7ecx->encryptobj, buf, &outlen, buflen,
|
|
data, inlen, final);
|
|
data = buf;
|
|
len = outlen;
|
|
}
|
|
if (rv != SECSuccess) {
|
|
if (final)
|
|
goto done;
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (p7ecx->ecx != NULL) {
|
|
/*
|
|
* Encode the contents bytes.
|
|
*/
|
|
if(len) {
|
|
rv = SEC_ASN1EncoderUpdate (p7ecx->ecx, (const char *)data, len);
|
|
}
|
|
}
|
|
|
|
done:
|
|
if (p7ecx->encryptobj != NULL) {
|
|
if (final)
|
|
sec_PKCS7DestroyEncryptObject (p7ecx->encryptobj);
|
|
if (dest != NULL) {
|
|
dest->data = buf;
|
|
dest->len = len;
|
|
} else if (buf != NULL) {
|
|
PORT_Free (buf);
|
|
}
|
|
}
|
|
|
|
if (final && p7ecx->digestobj != NULL) {
|
|
SECItem *digest, **digests, ***digestsp;
|
|
unsigned char *digdata;
|
|
SECOidTag kind;
|
|
|
|
kind = SEC_PKCS7ContentType (p7ecx->cinfo);
|
|
switch (kind) {
|
|
default:
|
|
PORT_Assert (0);
|
|
return SECFailure;
|
|
case SEC_OID_PKCS7_DIGESTED_DATA:
|
|
digest = &(p7ecx->cinfo->content.digestedData->digest);
|
|
digestsp = NULL;
|
|
break;
|
|
case SEC_OID_PKCS7_SIGNED_DATA:
|
|
digest = NULL;
|
|
digestsp = &(p7ecx->cinfo->content.signedData->digests);
|
|
break;
|
|
case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
|
|
digest = NULL;
|
|
digestsp = &(p7ecx->cinfo->content.signedAndEnvelopedData->digests);
|
|
break;
|
|
}
|
|
|
|
digdata = (unsigned char*)PORT_ArenaAlloc (p7ecx->cinfo->poolp,
|
|
p7ecx->digestobj->length);
|
|
if (digdata == NULL)
|
|
return SECFailure;
|
|
|
|
if (digestsp != NULL) {
|
|
PORT_Assert (digest == NULL);
|
|
|
|
digest = (SECItem*)PORT_ArenaAlloc (p7ecx->cinfo->poolp,
|
|
sizeof(SECItem));
|
|
digests = (SECItem**)PORT_ArenaAlloc (p7ecx->cinfo->poolp,
|
|
2 * sizeof(SECItem *));
|
|
if (digests == NULL || digest == NULL)
|
|
return SECFailure;
|
|
|
|
digests[0] = digest;
|
|
digests[1] = NULL;
|
|
|
|
*digestsp = digests;
|
|
}
|
|
|
|
PORT_Assert (digest != NULL);
|
|
|
|
digest->data = digdata;
|
|
digest->len = p7ecx->digestobj->length;
|
|
|
|
(* p7ecx->digestobj->end) (p7ecx->digestcx, digest->data,
|
|
&(digest->len), digest->len);
|
|
(* p7ecx->digestobj->destroy) (p7ecx->digestcx, PR_TRUE);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
SECStatus
|
|
SEC_PKCS7EncoderUpdate (SEC_PKCS7EncoderContext *p7ecx,
|
|
const char *data, unsigned long len)
|
|
{
|
|
/* XXX Error handling needs help. Return what? Do "Finish" on failure? */
|
|
return sec_pkcs7_encoder_work_data (p7ecx, NULL,
|
|
(const unsigned char *)data, len,
|
|
PR_FALSE);
|
|
}
|
|
|
|
static SECStatus
|
|
sec_pkcs7_encoder_sig_and_certs (SEC_PKCS7ContentInfo *cinfo,
|
|
SECKEYGetPasswordKey pwfn, void *pwfnarg)
|
|
{
|
|
SECOidTag kind;
|
|
CERTCertificate **certs;
|
|
CERTCertificateList **certlists;
|
|
SECAlgorithmID **digestalgs;
|
|
SECItem **digests;
|
|
SEC_PKCS7SignerInfo *signerinfo, **signerinfos;
|
|
SECItem **rawcerts, ***rawcertsp;
|
|
PLArenaPool *poolp;
|
|
int certcount;
|
|
int ci, cli, rci, si;
|
|
|
|
kind = SEC_PKCS7ContentType (cinfo);
|
|
switch (kind) {
|
|
default:
|
|
case SEC_OID_PKCS7_DATA:
|
|
case SEC_OID_PKCS7_DIGESTED_DATA:
|
|
case SEC_OID_PKCS7_ENCRYPTED_DATA:
|
|
case SEC_OID_PKCS7_ENVELOPED_DATA:
|
|
certs = NULL;
|
|
certlists = NULL;
|
|
digestalgs = NULL;
|
|
digests = NULL;
|
|
signerinfos = NULL;
|
|
rawcertsp = NULL;
|
|
break;
|
|
case SEC_OID_PKCS7_SIGNED_DATA:
|
|
{
|
|
SEC_PKCS7SignedData *sdp;
|
|
|
|
sdp = cinfo->content.signedData;
|
|
certs = sdp->certs;
|
|
certlists = sdp->certLists;
|
|
digestalgs = sdp->digestAlgorithms;
|
|
digests = sdp->digests;
|
|
signerinfos = sdp->signerInfos;
|
|
rawcertsp = &(sdp->rawCerts);
|
|
}
|
|
break;
|
|
case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
|
|
{
|
|
SEC_PKCS7SignedAndEnvelopedData *saedp;
|
|
|
|
saedp = cinfo->content.signedAndEnvelopedData;
|
|
certs = saedp->certs;
|
|
certlists = saedp->certLists;
|
|
digestalgs = saedp->digestAlgorithms;
|
|
digests = saedp->digests;
|
|
signerinfos = saedp->signerInfos;
|
|
rawcertsp = &(saedp->rawCerts);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (certs == NULL && certlists == NULL && signerinfos == NULL)
|
|
return SECSuccess; /* nothing for us to do! */
|
|
|
|
poolp = cinfo->poolp;
|
|
certcount = 0;
|
|
|
|
if (signerinfos != NULL) {
|
|
SECOidTag digestalgtag;
|
|
int di;
|
|
SECStatus rv;
|
|
CERTCertificate *cert;
|
|
SECKEYPrivateKey *privkey;
|
|
SECItem signature;
|
|
SECOidTag signalgtag;
|
|
|
|
PORT_Assert (digestalgs != NULL && digests != NULL);
|
|
|
|
/*
|
|
* If one fails, we bail right then. If we want to continue and
|
|
* try to do subsequent signatures, this loop, and the departures
|
|
* from it, will need to be reworked.
|
|
*/
|
|
for (si = 0; signerinfos[si] != NULL; si++) {
|
|
|
|
signerinfo = signerinfos[si];
|
|
|
|
/* find right digest */
|
|
digestalgtag = SECOID_GetAlgorithmTag (&(signerinfo->digestAlg));
|
|
for (di = 0; digestalgs[di] != NULL; di++) {
|
|
/* XXX Should I be comparing more than the tag? */
|
|
if (digestalgtag == SECOID_GetAlgorithmTag (digestalgs[di]))
|
|
break;
|
|
}
|
|
if (digestalgs[di] == NULL) {
|
|
/* XXX oops; do what? set an error? */
|
|
return SECFailure;
|
|
}
|
|
PORT_Assert (digests[di] != NULL);
|
|
|
|
cert = signerinfo->cert;
|
|
privkey = PK11_FindKeyByAnyCert (cert, pwfnarg);
|
|
if (privkey == NULL)
|
|
return SECFailure;
|
|
|
|
/*
|
|
* XXX I think there should be a cert-level interface for this,
|
|
* so that I do not have to know about subjectPublicKeyInfo...
|
|
*/
|
|
signalgtag = SECOID_GetAlgorithmTag (&(cert->subjectPublicKeyInfo.algorithm));
|
|
|
|
if (signerinfo->authAttr != NULL) {
|
|
SEC_PKCS7Attribute *attr;
|
|
SECItem encoded_attrs;
|
|
SECItem *dummy;
|
|
SECOidTag algid;
|
|
|
|
/*
|
|
* First, find and fill in the message digest attribute.
|
|
*/
|
|
attr = sec_PKCS7FindAttribute (signerinfo->authAttr,
|
|
SEC_OID_PKCS9_MESSAGE_DIGEST,
|
|
PR_TRUE);
|
|
PORT_Assert (attr != NULL);
|
|
if (attr == NULL) {
|
|
SECKEY_DestroyPrivateKey (privkey);
|
|
return SECFailure;
|
|
}
|
|
|
|
/*
|
|
* XXX The second half of the following assertion prevents
|
|
* the encoder from being called twice on the same content.
|
|
* Either just remove the second half the assertion, or
|
|
* change the code to check if the value already there is
|
|
* the same as digests[di], whichever seems more right.
|
|
*/
|
|
PORT_Assert (attr->values != NULL && attr->values[0] == NULL);
|
|
attr->values[0] = digests[di];
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
rv = sec_PKCS7ReorderAttributes (signerinfo->authAttr);
|
|
if (rv != SECSuccess) {
|
|
SECKEY_DestroyPrivateKey (privkey);
|
|
return SECFailure;
|
|
}
|
|
|
|
encoded_attrs.data = NULL;
|
|
encoded_attrs.len = 0;
|
|
dummy = sec_PKCS7EncodeAttributes (NULL, &encoded_attrs,
|
|
&(signerinfo->authAttr));
|
|
if (dummy == NULL) {
|
|
SECKEY_DestroyPrivateKey (privkey);
|
|
return SECFailure;
|
|
}
|
|
|
|
algid = SEC_GetSignatureAlgorithmOidTag(privkey->keyType,
|
|
digestalgtag);
|
|
if (algid == SEC_OID_UNKNOWN) {
|
|
PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
|
|
SECKEY_DestroyPrivateKey (privkey);
|
|
return SECFailure;
|
|
}
|
|
rv = SEC_SignData (&signature,
|
|
encoded_attrs.data, encoded_attrs.len,
|
|
privkey,
|
|
algid);
|
|
SECITEM_FreeItem (&encoded_attrs, PR_FALSE);
|
|
} else {
|
|
rv = SGN_Digest (privkey, digestalgtag, &signature,
|
|
digests[di]);
|
|
}
|
|
|
|
SECKEY_DestroyPrivateKey (privkey);
|
|
|
|
if (rv != SECSuccess)
|
|
return rv;
|
|
|
|
rv = SECITEM_CopyItem (poolp, &(signerinfo->encDigest), &signature);
|
|
if (rv != SECSuccess)
|
|
return rv;
|
|
|
|
SECITEM_FreeItem (&signature, PR_FALSE);
|
|
|
|
rv = SECOID_SetAlgorithmID (poolp, &(signerinfo->digestEncAlg),
|
|
signalgtag, NULL);
|
|
if (rv != SECSuccess)
|
|
return SECFailure;
|
|
|
|
/*
|
|
* Count the cert chain for this signer.
|
|
*/
|
|
if (signerinfo->certList != NULL)
|
|
certcount += signerinfo->certList->len;
|
|
}
|
|
}
|
|
|
|
if (certs != NULL) {
|
|
for (ci = 0; certs[ci] != NULL; ci++)
|
|
certcount++;
|
|
}
|
|
|
|
if (certlists != NULL) {
|
|
for (cli = 0; certlists[cli] != NULL; cli++)
|
|
certcount += certlists[cli]->len;
|
|
}
|
|
|
|
if (certcount == 0)
|
|
return SECSuccess; /* signing done; no certs */
|
|
|
|
/*
|
|
* 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.)
|
|
*/
|
|
rawcerts = (SECItem**)PORT_ArenaAlloc (poolp,
|
|
(certcount + 1) * sizeof(SECItem *));
|
|
if (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...
|
|
*/
|
|
rci = 0;
|
|
if (signerinfos != NULL) {
|
|
for (si = 0; signerinfos[si] != NULL; si++) {
|
|
signerinfo = signerinfos[si];
|
|
for (ci = 0; ci < signerinfo->certList->len; ci++)
|
|
rawcerts[rci++] = &(signerinfo->certList->certs[ci]);
|
|
}
|
|
|
|
}
|
|
|
|
if (certs != NULL) {
|
|
for (ci = 0; certs[ci] != NULL; ci++)
|
|
rawcerts[rci++] = &(certs[ci]->derCert);
|
|
}
|
|
|
|
if (certlists != NULL) {
|
|
for (cli = 0; certlists[cli] != NULL; cli++) {
|
|
for (ci = 0; ci < certlists[cli]->len; ci++)
|
|
rawcerts[rci++] = &(certlists[cli]->certs[ci]);
|
|
}
|
|
}
|
|
|
|
rawcerts[rci] = NULL;
|
|
*rawcertsp = rawcerts;
|
|
|
|
return SECSuccess;
|
|
}
|
|
|
|
|
|
SECStatus
|
|
SEC_PKCS7EncoderFinish (SEC_PKCS7EncoderContext *p7ecx,
|
|
SECKEYGetPasswordKey pwfn, void *pwfnarg)
|
|
{
|
|
SECStatus rv;
|
|
|
|
/*
|
|
* Flush out any remaining data.
|
|
*/
|
|
rv = sec_pkcs7_encoder_work_data (p7ecx, NULL, NULL, 0, PR_TRUE);
|
|
|
|
/*
|
|
* Turn off streaming stuff.
|
|
*/
|
|
SEC_ASN1EncoderClearTakeFromBuf (p7ecx->ecx);
|
|
SEC_ASN1EncoderClearStreaming (p7ecx->ecx);
|
|
|
|
if (rv != SECSuccess)
|
|
goto loser;
|
|
|
|
rv = sec_pkcs7_encoder_sig_and_certs (p7ecx->cinfo, pwfn, pwfnarg);
|
|
if (rv != SECSuccess)
|
|
goto loser;
|
|
|
|
rv = SEC_ASN1EncoderUpdate (p7ecx->ecx, NULL, 0);
|
|
|
|
loser:
|
|
SEC_ASN1EncoderFinish (p7ecx->ecx);
|
|
PORT_Free (p7ecx);
|
|
return rv;
|
|
}
|
|
|
|
/*
|
|
* Abort the ASN.1 stream. Used by pkcs 12
|
|
*/
|
|
void
|
|
SEC_PKCS7EncoderAbort(SEC_PKCS7EncoderContext *p7ecx, int error)
|
|
{
|
|
PORT_Assert(p7ecx);
|
|
SEC_ASN1EncoderAbort(p7ecx->ecx, error);
|
|
}
|
|
|
|
/*
|
|
* After this routine is called, the entire PKCS7 contentInfo is ready
|
|
* to be encoded. This is used internally, but can also be called from
|
|
* elsewhere for those who want to be able to just have pointers to
|
|
* the ASN1 template for pkcs7 contentInfo built into their own encodings.
|
|
*/
|
|
SECStatus
|
|
SEC_PKCS7PrepareForEncode (SEC_PKCS7ContentInfo *cinfo,
|
|
PK11SymKey *bulkkey,
|
|
SECKEYGetPasswordKey pwfn,
|
|
void *pwfnarg)
|
|
{
|
|
SEC_PKCS7EncoderContext *p7ecx;
|
|
SECItem *content, *enc_content;
|
|
SECStatus rv;
|
|
|
|
p7ecx = sec_pkcs7_encoder_start_contexts (cinfo, bulkkey);
|
|
if (p7ecx == NULL)
|
|
return SECFailure;
|
|
|
|
content = SEC_PKCS7GetContent (cinfo);
|
|
|
|
if (p7ecx->encryptobj != NULL) {
|
|
SECOidTag kind;
|
|
SEC_PKCS7EncryptedContentInfo *enccinfo;
|
|
|
|
kind = SEC_PKCS7ContentType (p7ecx->cinfo);
|
|
switch (kind) {
|
|
default:
|
|
PORT_Assert (0);
|
|
rv = SECFailure;
|
|
goto loser;
|
|
case SEC_OID_PKCS7_ENCRYPTED_DATA:
|
|
enccinfo = &(p7ecx->cinfo->content.encryptedData->encContentInfo);
|
|
break;
|
|
case SEC_OID_PKCS7_ENVELOPED_DATA:
|
|
enccinfo = &(p7ecx->cinfo->content.envelopedData->encContentInfo);
|
|
break;
|
|
case SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA:
|
|
enccinfo = &(p7ecx->cinfo->content.signedAndEnvelopedData->encContentInfo);
|
|
break;
|
|
}
|
|
enc_content = &(enccinfo->encContent);
|
|
} else {
|
|
enc_content = NULL;
|
|
}
|
|
|
|
if (content != NULL && content->data != NULL && content->len) {
|
|
rv = sec_pkcs7_encoder_work_data (p7ecx, enc_content,
|
|
content->data, content->len, PR_TRUE);
|
|
if (rv != SECSuccess)
|
|
goto loser;
|
|
}
|
|
|
|
rv = sec_pkcs7_encoder_sig_and_certs (cinfo, pwfn, pwfnarg);
|
|
|
|
loser:
|
|
PORT_Free (p7ecx);
|
|
return rv;
|
|
}
|
|
|
|
|
|
/*
|
|
* Encode a PKCS7 object, in one shot. All necessary components
|
|
* of the object must already be specified. Either the data has
|
|
* already been included (via SetContent), or the data is detached,
|
|
* or there is no data at all (certs-only).
|
|
*
|
|
* "cinfo" specifies the object to be encoded.
|
|
*
|
|
* "outputfn" is where the encoded bytes will be passed.
|
|
*
|
|
* "outputarg" is an opaque argument to the above callback.
|
|
*
|
|
* "bulkkey" specifies the bulk encryption key to use. This argument
|
|
* can be NULL if no encryption is being done, or if the bulk key should
|
|
* be generated internally (usually the case for EnvelopedData but never
|
|
* for EncryptedData, which *must* provide a bulk encryption key).
|
|
*
|
|
* "pwfn" is a callback for getting the password which protects the
|
|
* private key of the signer. This argument can be NULL if it is known
|
|
* that no signing is going to be done.
|
|
*
|
|
* "pwfnarg" is an opaque argument to the above callback.
|
|
*/
|
|
SECStatus
|
|
SEC_PKCS7Encode (SEC_PKCS7ContentInfo *cinfo,
|
|
SEC_PKCS7EncoderOutputCallback outputfn,
|
|
void *outputarg,
|
|
PK11SymKey *bulkkey,
|
|
SECKEYGetPasswordKey pwfn,
|
|
void *pwfnarg)
|
|
{
|
|
SECStatus rv;
|
|
|
|
rv = SEC_PKCS7PrepareForEncode (cinfo, bulkkey, pwfn, pwfnarg);
|
|
if (rv == SECSuccess) {
|
|
struct sec_pkcs7_encoder_output outputcx;
|
|
|
|
outputcx.outputfn = outputfn;
|
|
outputcx.outputarg = outputarg;
|
|
|
|
rv = SEC_ASN1Encode (cinfo, sec_PKCS7ContentInfoTemplate,
|
|
sec_pkcs7_encoder_out, &outputcx);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
/*
|
|
* Encode a PKCS7 object, in one shot. All necessary components
|
|
* of the object must already be specified. Either the data has
|
|
* already been included (via SetContent), or the data is detached,
|
|
* or there is no data at all (certs-only). The output, rather than
|
|
* being passed to an output function as is done above, is all put
|
|
* into a SECItem.
|
|
*
|
|
* "pool" specifies a pool from which to allocate the result.
|
|
* It can be NULL, in which case memory is allocated generically.
|
|
*
|
|
* "dest" specifies a SECItem in which to put the result data.
|
|
* It can be NULL, in which case the entire item is allocated, too.
|
|
*
|
|
* "cinfo" specifies the object to be encoded.
|
|
*
|
|
* "bulkkey" specifies the bulk encryption key to use. This argument
|
|
* can be NULL if no encryption is being done, or if the bulk key should
|
|
* be generated internally (usually the case for EnvelopedData but never
|
|
* for EncryptedData, which *must* provide a bulk encryption key).
|
|
*
|
|
* "pwfn" is a callback for getting the password which protects the
|
|
* private key of the signer. This argument can be NULL if it is known
|
|
* that no signing is going to be done.
|
|
*
|
|
* "pwfnarg" is an opaque argument to the above callback.
|
|
*/
|
|
SECItem *
|
|
SEC_PKCS7EncodeItem (PLArenaPool *pool,
|
|
SECItem *dest,
|
|
SEC_PKCS7ContentInfo *cinfo,
|
|
PK11SymKey *bulkkey,
|
|
SECKEYGetPasswordKey pwfn,
|
|
void *pwfnarg)
|
|
{
|
|
SECStatus rv;
|
|
|
|
rv = SEC_PKCS7PrepareForEncode (cinfo, bulkkey, pwfn, pwfnarg);
|
|
if (rv != SECSuccess)
|
|
return NULL;
|
|
|
|
return SEC_ASN1EncodeItem (pool, dest, cinfo, sec_PKCS7ContentInfoTemplate);
|
|
}
|
|
|