Add support for KEKRecipientInfo in cms application.
[openssl.git] / crypto / cms / cms_env.c
index 796b0d37563f8dbdd0bb666b65fe9be5c78f33fc..1bea5582161497c5d486354e39e1dfb1969c7586 100644 (file)
@@ -58,6 +58,7 @@
 #include <openssl/err.h>
 #include <openssl/cms.h>
 #include <openssl/rand.h>
+#include <openssl/aes.h>
 #include "cms_lcl.h"
 #include "asn1_locl.h"
 
@@ -66,6 +67,8 @@
 DECLARE_ASN1_ITEM(CMS_EnvelopedData)
 DECLARE_ASN1_ITEM(CMS_RecipientInfo)
 DECLARE_ASN1_ITEM(CMS_KeyTransRecipientInfo)
+DECLARE_ASN1_ITEM(CMS_KEKRecipientInfo)
+DECLARE_ASN1_ITEM(CMS_OtherKeyAttribute)
 
 DECLARE_STACK_OF(CMS_RecipientInfo)
 
@@ -104,7 +107,7 @@ static CMS_EnvelopedData *cms_enveloped_data_init(CMS_ContentInfo *cms)
 STACK_OF(CMS_RecipientInfo) *CMS_get0_RecipientInfos(CMS_ContentInfo *cms)
        {
        CMS_EnvelopedData *env;
-       env = cms_enveloped_data_init(cms);
+       env = cms_get0_enveloped(cms);
        if (!env)
                return NULL;
        return env->recipientInfos;
@@ -115,10 +118,33 @@ int CMS_RecipientInfo_type(CMS_RecipientInfo *ri)
        return ri->type;
        }
 
+CMS_ContentInfo *CMS_EnvelopedData_create(const EVP_CIPHER *cipher)
+       {
+       CMS_ContentInfo *cms;
+       CMS_EnvelopedData *env;
+       cms = CMS_ContentInfo_new();
+       if (!cms)
+               goto merr;
+       env = cms_enveloped_data_init(cms);
+       if (!env)
+               goto merr;
+       if (!cms_EncryptedContent_init(env->encryptedContentInfo,
+                                       cipher, NULL, 0))
+               goto merr;
+       return cms;
+       merr:
+       if (cms)
+               CMS_ContentInfo_free(cms);
+       CMSerr(CMS_F_CMS_ENVELOPEDDATA_CREATE, ERR_R_MALLOC_FAILURE);
+       return NULL;
+       }
+
+/* Key Transport Recipient Info (KTRI) routines */
+
 /* Add a recipient certificate. For now only handle key transport.
  * If we ever handle key agreement will need updating.
  */
-#if 0 /* currently unused/undeclared */
+
 CMS_RecipientInfo *CMS_add1_recipient_cert(CMS_ContentInfo *cms,
                                        X509 *recip, unsigned int flags)
        {
@@ -127,17 +153,16 @@ CMS_RecipientInfo *CMS_add1_recipient_cert(CMS_ContentInfo *cms,
        CMS_EnvelopedData *env;
        EVP_PKEY *pk = NULL;
        int i, type;
-       /* Init enveloped data */
-       env = cms_enveloped_data_init(cms);
+       env = cms_get0_enveloped(cms);
        if (!env)
                goto err;
 
-       /* Initialized recipient info */
+       /* Initialize recipient info */
        ri = M_ASN1_new_of(CMS_RecipientInfo);
        if (!ri)
                goto merr;
 
-       /* Initialize and add key transrport recipient info */
+       /* Initialize and add key transport recipient info */
 
        ri->d.ktri = M_ASN1_new_of(CMS_KeyTransRecipientInfo);
        if (!ri->d.ktri)
@@ -155,7 +180,6 @@ CMS_RecipientInfo *CMS_add1_recipient_cert(CMS_ContentInfo *cms,
                goto err;
                }
        CRYPTO_add(&recip->references, 1, CRYPTO_LOCK_X509);
-       CRYPTO_add(&pk->references, 1, CRYPTO_LOCK_EVP_PKEY);
        ktri->pkey = pk;
        ktri->recip = recip;
 
@@ -208,7 +232,6 @@ CMS_RecipientInfo *CMS_add1_recipient_cert(CMS_ContentInfo *cms,
        return NULL;
 
        }
-#endif
 
 int CMS_RecipientInfo_ktri_get0_algs(CMS_RecipientInfo *ri,
                                        EVP_PKEY **pk, X509 **recip,
@@ -258,14 +281,28 @@ int CMS_RecipientInfo_ktri_cert_cmp(CMS_RecipientInfo *ri, X509 *cert)
                        CMS_R_NOT_KEY_TRANSPORT);
                return -2;
                }
-
        return cms_SignerIdentifier_cert_cmp(ri->d.ktri->rid, cert);
        }
 
-int CMS_RecipientInfo_decrypt(CMS_ContentInfo *cms, CMS_RecipientInfo *ri,
-                              EVP_PKEY *pkey)
+int CMS_RecipientInfo_set0_pkey(CMS_RecipientInfo *ri, EVP_PKEY *pkey)
+       {
+       if (ri->type != CMS_RECIPINFO_TRANS)
+               {
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_SET0_PKEY,
+                       CMS_R_NOT_KEY_TRANSPORT);
+               return 0;
+               }
+       ri->d.ktri->pkey = pkey;
+       return 1;
+       }
+
+/* Encrypt content key in key transport recipient info */
+
+static int cms_RecipientInfo_ktri_encrypt(CMS_ContentInfo *cms,
+                                       CMS_RecipientInfo *ri)
        {
        CMS_KeyTransRecipientInfo *ktri;
+       CMS_EncryptedContentInfo *ec;
        EVP_PKEY_CTX *pctx = NULL;
        unsigned char *ek = NULL;
        size_t eklen;
@@ -274,13 +311,75 @@ int CMS_RecipientInfo_decrypt(CMS_ContentInfo *cms, CMS_RecipientInfo *ri,
 
        if (ri->type != CMS_RECIPINFO_TRANS)
                {
-               CMSerr(CMS_F_CMS_RECIPIENTINFO_DECRYPT,
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_KTRI_ENCRYPT,
                        CMS_R_NOT_KEY_TRANSPORT);
                return 0;
                }
        ktri = ri->d.ktri;
+       ec = cms->d.envelopedData->encryptedContentInfo;
+
+       pctx = EVP_PKEY_CTX_new(ktri->pkey, NULL);
+       if (!pctx)
+               return 0;
+
+       if (EVP_PKEY_encrypt_init(pctx) <= 0)
+               goto err;
+
+       if (EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_ENCRYPT,
+                               EVP_PKEY_CTRL_CMS_ENCRYPT, 0, ri) <= 0)
+               {
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_KTRI_ENCRYPT, CMS_R_CTRL_ERROR);
+               goto err;
+               }
+
+       if (EVP_PKEY_encrypt(pctx, NULL, &eklen, ec->key, ec->keylen) <= 0)
+               goto err;
+
+       ek = OPENSSL_malloc(eklen);
+
+       if (ek == NULL)
+               {
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_KTRI_ENCRYPT,
+                                                       ERR_R_MALLOC_FAILURE);
+               goto err;
+               }
+
+       if (EVP_PKEY_encrypt(pctx, ek, &eklen, ec->key, ec->keylen) <= 0)
+               goto err;
+
+       ASN1_STRING_set0(ktri->encryptedKey, ek, eklen);
+       ek = NULL;
+
+       ret = 1;
+
+       err:
+       if (pctx)
+               EVP_PKEY_CTX_free(pctx);
+       if (ek)
+               OPENSSL_free(ek);
+       return ret;
+
+       }
+
+/* Decrypt content key from KTRI */
+
+static int cms_RecipientInfo_ktri_decrypt(CMS_ContentInfo *cms,
+                                                       CMS_RecipientInfo *ri)
+       {
+       CMS_KeyTransRecipientInfo *ktri = ri->d.ktri;
+       EVP_PKEY_CTX *pctx = NULL;
+       unsigned char *ek = NULL;
+       size_t eklen;
+       int ret = 0;
 
-       pctx = EVP_PKEY_CTX_new(pkey, NULL);
+       if (ktri->pkey == NULL)
+               {
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_KTRI_DECRYPT,
+                       CMS_R_NO_PRIVATE_KEY);
+               return 0;
+               }
+
+       pctx = EVP_PKEY_CTX_new(ktri->pkey, NULL);
        if (!pctx)
                return 0;
 
@@ -290,7 +389,7 @@ int CMS_RecipientInfo_decrypt(CMS_ContentInfo *cms, CMS_RecipientInfo *ri,
        if (EVP_PKEY_CTX_ctrl(pctx, -1, EVP_PKEY_OP_DECRYPT,
                                EVP_PKEY_CTRL_CMS_DECRYPT, 0, ri) <= 0)
                {
-               CMSerr(CMS_F_CMS_RECIPIENTINFO_DECRYPT, CMS_R_CTRL_ERROR);
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_KTRI_DECRYPT, CMS_R_CTRL_ERROR);
                goto err;
                }
 
@@ -303,7 +402,8 @@ int CMS_RecipientInfo_decrypt(CMS_ContentInfo *cms, CMS_RecipientInfo *ri,
 
        if (ek == NULL)
                {
-               CMSerr(CMS_F_CMS_RECIPIENTINFO_DECRYPT, ERR_R_MALLOC_FAILURE);
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_KTRI_DECRYPT,
+                                                       ERR_R_MALLOC_FAILURE);
                goto err;
                }
 
@@ -311,7 +411,7 @@ int CMS_RecipientInfo_decrypt(CMS_ContentInfo *cms, CMS_RecipientInfo *ri,
                                ktri->encryptedKey->data,
                                ktri->encryptedKey->length) <= 0)
                {
-               CMSerr(CMS_F_CMS_RECIPIENTINFO_DECRYPT, CMS_R_CMS_LIB);
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_KTRI_DECRYPT, CMS_R_CMS_LIB);
                goto err;
                }
 
@@ -329,9 +429,416 @@ int CMS_RecipientInfo_decrypt(CMS_ContentInfo *cms, CMS_RecipientInfo *ri,
        return ret;
        }
 
+/* Key Encrypted Key (KEK) RecipientInfo routines */
+
+/* For now hard code AES key wrap info */
+
+static size_t aes_wrap_keylen(int nid)
+       {
+       switch (nid)
+               {
+               case NID_id_aes128_wrap:
+               return 16;
+
+               case NID_id_aes192_wrap:
+               return  24;
+
+               case NID_id_aes256_wrap:
+               return  32;
+
+               default:
+               return 0;
+               }
+       }
+
+CMS_RecipientInfo *CMS_add0_recipient_key(CMS_ContentInfo *cms, int nid,
+                                       unsigned char *key, size_t keylen,
+                                       unsigned char *id, size_t idlen,
+                                       ASN1_GENERALIZEDTIME *date,
+                                       ASN1_OBJECT *otherTypeId,
+                                       ASN1_TYPE *otherType)
+       {
+       CMS_RecipientInfo *ri = NULL;
+       CMS_EnvelopedData *env;
+       CMS_KEKRecipientInfo *kekri;
+       env = cms_get0_enveloped(cms);
+       if (!env)
+               goto err;
+
+       if (nid == NID_undef)
+               {
+               switch (keylen)
+                       {
+                       case 16:
+                       nid = NID_id_aes128_wrap;
+                       break;
+
+                       case  24:
+                       nid = NID_id_aes192_wrap;
+                       break;
+
+                       case  32:
+                       nid = NID_id_aes256_wrap;
+                       break;
+
+                       default:
+                       CMSerr(CMS_F_CMS_ADD0_RECIPIENT_KEY,
+                                               CMS_R_INVALID_KEY_LENGTH);
+                       goto err;
+                       }
+
+               }
+       else
+               {
+
+               size_t exp_keylen = aes_wrap_keylen(nid);
+
+               if (!exp_keylen)
+                       {
+                       CMSerr(CMS_F_CMS_ADD0_RECIPIENT_KEY,
+                                       CMS_R_UNSUPPORTED_KEK_ALGORITHM);
+                       goto err;
+                       }
+
+               if (keylen != exp_keylen)
+                       {
+                       CMSerr(CMS_F_CMS_ADD0_RECIPIENT_KEY,
+                                       CMS_R_INVALID_KEY_LENGTH);
+                       goto err;
+                       }
+
+               }
+
+       /* Initialize recipient info */
+       ri = M_ASN1_new_of(CMS_RecipientInfo);
+       if (!ri)
+               goto merr;
+
+       ri->d.kekri = M_ASN1_new_of(CMS_KEKRecipientInfo);
+       if (!ri->d.kekri)
+               goto merr;
+       ri->type = CMS_RECIPINFO_KEK;
+
+       kekri = ri->d.kekri;
+
+       if (otherTypeId)
+               {
+               kekri->kekid->other = M_ASN1_new_of(CMS_OtherKeyAttribute);
+               if (kekri->kekid->other == NULL)
+                       goto merr;
+               }
+
+       if (!sk_CMS_RecipientInfo_push(env->recipientInfos, ri))
+               goto merr;
+
+
+       /* After this point no calls can fail */
+
+       kekri->version = 4;
+
+       kekri->key = key;
+       kekri->keylen = keylen;
+
+       ASN1_STRING_set0(kekri->kekid->keyIdentifier, id, idlen);
+
+       kekri->kekid->date = date;
+
+       if (kekri->kekid->other)
+               {
+               kekri->kekid->other->keyAttrId = otherTypeId;
+               kekri->kekid->other->keyAttr = otherType;
+               }
+
+       X509_ALGOR_set0(kekri->keyEncryptionAlgorithm,
+                               OBJ_nid2obj(nid), V_ASN1_UNDEF, NULL);
+
+       return ri;
+
+       merr:
+       CMSerr(CMS_F_CMS_ADD0_RECIPIENT_KEY, ERR_R_MALLOC_FAILURE);
+       err:
+       if (ri)
+               M_ASN1_free_of(ri, CMS_RecipientInfo);
+       return NULL;
+
+       }
+
+int CMS_RecipientInfo_kekri_get0_id(CMS_RecipientInfo *ri,
+                                       X509_ALGOR **palg,
+                                       ASN1_OCTET_STRING **pid,
+                                       ASN1_GENERALIZEDTIME **pdate,
+                                       ASN1_OBJECT **potherid,
+                                       ASN1_TYPE **pothertype)
+       {
+       CMS_KEKIdentifier *rkid;
+       if (ri->type != CMS_RECIPINFO_KEK)
+               {
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_KEKRI_GET0_ID, CMS_R_NOT_KEK);
+               return 0;
+               }
+       rkid =  ri->d.kekri->kekid;
+       if (palg)
+               *palg = ri->d.kekri->keyEncryptionAlgorithm;
+       if (pid)
+               *pid = rkid->keyIdentifier;
+       if (pdate)
+               *pdate = rkid->date;
+       if (potherid)
+               {
+               if (rkid->other)
+                       *potherid = rkid->other->keyAttrId;
+               else
+                       *potherid = NULL;
+               }
+       if (pothertype)
+               {
+               if (rkid->other)
+                       *pothertype = rkid->other->keyAttr;
+               else
+                       *pothertype = NULL;
+               }
+       return 1;
+       }
+
+
+int CMS_RecipientInfo_set0_key(CMS_RecipientInfo *ri, 
+                               unsigned char *key, size_t keylen)
+       {
+       CMS_KEKRecipientInfo *kekri;
+       int wrap_nid;
+       if (ri->type != CMS_RECIPINFO_KEK)
+               {
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_SET0_KEY, CMS_R_NOT_KEK);
+               return 0;
+               }
+       kekri = ri->d.kekri;
+       wrap_nid = OBJ_obj2nid(kekri->keyEncryptionAlgorithm->algorithm);
+       if (aes_wrap_keylen(wrap_nid) != keylen)
+               {
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_SET0_KEY,
+                       CMS_R_INVALID_KEY_LENGTH);
+               return 0;
+               }
+       kekri->key = key;
+       kekri->keylen = keylen;
+       return 1;
+       }
+
+
+/* Encrypt content key in KEK recipient info */
+
+static int cms_RecipientInfo_kekri_encrypt(CMS_ContentInfo *cms,
+                                       CMS_RecipientInfo *ri)
+       {
+       CMS_EncryptedContentInfo *ec;
+       CMS_KEKRecipientInfo *kekri;
+       AES_KEY actx;
+       unsigned char *wkey = NULL;
+       int wkeylen;
+       int r = 0;
+
+       ec = cms->d.envelopedData->encryptedContentInfo;
+
+       kekri = ri->d.kekri;
+
+       if (!kekri->key)
+               {
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_KEKRI_ENCRYPT, CMS_R_NO_KEY);
+               return 0;
+               }
+
+       if (AES_set_encrypt_key(kekri->key, kekri->keylen << 3, &actx))
+               { 
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_KEKRI_ENCRYPT,
+                                               CMS_R_ERROR_SETTING_KEY);
+               goto err;
+               }
+
+       wkey = OPENSSL_malloc(ec->keylen + 8);
+
+       if (!wkey)
+               { 
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_KEKRI_ENCRYPT,
+                                               ERR_R_MALLOC_FAILURE);
+               goto err;
+               }
+
+       wkeylen = AES_wrap_key(&actx, NULL, wkey, ec->key, ec->keylen);
+
+       if (wkeylen <= 0)
+               {
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_KEKRI_ENCRYPT, CMS_R_WRAP_ERROR);
+               goto err;
+               }
+
+       ASN1_STRING_set0(kekri->encryptedKey, wkey, wkeylen);
+
+       r = 1;
+
+       err:
+
+       if (!r && wkey)
+               OPENSSL_free(wkey);
+       OPENSSL_cleanse(&actx, sizeof(actx));
+
+       return r;
+
+       }
+
+/* Decrypt content key in KEK recipient info */
+
+static int cms_RecipientInfo_kekri_decrypt(CMS_ContentInfo *cms,
+                                       CMS_RecipientInfo *ri)
+       {
+       CMS_EncryptedContentInfo *ec;
+       CMS_KEKRecipientInfo *kekri;
+       AES_KEY actx;
+       unsigned char *ukey = NULL;
+       int ukeylen;
+       int r = 0;
+
+       ec = cms->d.envelopedData->encryptedContentInfo;
+
+       kekri = ri->d.kekri;
+
+       if (!kekri->key)
+               {
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_KEKRI_DECRYPT, CMS_R_NO_KEY);
+               return 0;
+               }
+
+       /* If encrypted key length is invalid don't bother */
+
+       if (kekri->encryptedKey->length < 16)
+               { 
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_KEKRI_DECRYPT,
+                                       CMS_R_INVALID_ENCRYPTED_KEY_LENGTH);
+               goto err;
+               }
+
+       if (AES_set_decrypt_key(kekri->key, kekri->keylen << 3, &actx))
+               { 
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_KEKRI_DECRYPT,
+                                               CMS_R_ERROR_SETTING_KEY);
+               goto err;
+               }
+
+       ukey = OPENSSL_malloc(kekri->encryptedKey->length - 8);
+
+       if (!ukey)
+               { 
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_KEKRI_DECRYPT,
+                                               ERR_R_MALLOC_FAILURE);
+               goto err;
+               }
+
+       ukeylen = AES_unwrap_key(&actx, NULL, ukey,
+                                       kekri->encryptedKey->data,
+                                       kekri->encryptedKey->length);
+
+       if (ukeylen <= 0)
+               {
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_KEKRI_DECRYPT,
+                                                       CMS_R_UNWRAP_ERROR);
+               goto err;
+               }
+
+       ec->key = ukey;
+       ec->keylen = ukeylen;
+
+       r = 1;
+
+       err:
+
+       if (!r && ukey)
+               OPENSSL_free(ukey);
+       OPENSSL_cleanse(&actx, sizeof(actx));
+
+       return r;
+
+       }
+
+int CMS_RecipientInfo_decrypt(CMS_ContentInfo *cms, CMS_RecipientInfo *ri)
+       {
+       switch(ri->type)
+               {
+               case CMS_RECIPINFO_TRANS:
+               return cms_RecipientInfo_ktri_decrypt(cms, ri);
+
+               case CMS_RECIPINFO_KEK:
+               return cms_RecipientInfo_kekri_decrypt(cms, ri);
+
+               default:
+               CMSerr(CMS_F_CMS_RECIPIENTINFO_DECRYPT,
+                       CMS_R_UNSUPPORTED_RECPIENTINFO_TYPE);
+               return 0;
+               }
+       }
+
 BIO *cms_EnvelopedData_init_bio(CMS_ContentInfo *cms)
        {
        CMS_EncryptedContentInfo *ec;
+       STACK_OF(CMS_RecipientInfo) *rinfos;
+       CMS_RecipientInfo *ri;
+       int i, r, ok = 0;
+       BIO *ret;
+
+       /* Get BIO first to set up key */
+
        ec = cms->d.envelopedData->encryptedContentInfo;
-       return cms_EncryptedContent_init_bio(ec);
+       ret = cms_EncryptedContent_init_bio(ec);
+
+       /* If error or no cipher end of processing */
+
+       if (!ret || !ec->cipher)
+               return ret;
+
+       /* Now encrypt content key according to each RecipientInfo type */
+
+       rinfos = cms->d.envelopedData->recipientInfos;
+
+       for (i = 0; i < sk_CMS_RecipientInfo_num(rinfos); i++)
+               {
+               ri = sk_CMS_RecipientInfo_value(rinfos, i);
+
+               switch (ri->type)
+                       {
+                       case CMS_RECIPINFO_TRANS:
+                       r = cms_RecipientInfo_ktri_encrypt(cms, ri);
+                       break;
+
+                       case CMS_RECIPINFO_KEK:
+                       r = cms_RecipientInfo_kekri_encrypt(cms, ri);
+                       break;
+
+                       default:
+                       CMSerr(CMS_F_CMS_ENVELOPEDDATA_INIT_BIO,
+                               CMS_R_UNSUPPORTED_RECIPIENT_TYPE);
+                       goto err;
+                       }
+
+               if (r <= 0)
+                       {
+                       CMSerr(CMS_F_CMS_ENVELOPEDDATA_INIT_BIO,
+                               CMS_R_ERROR_SETTING_RECIPIENTINFO);
+                       goto err;
+                       }
+               }
+
+       ok = 1;
+
+       err:
+       ec->cipher = NULL;
+       if (ec->key)
+               {
+               OPENSSL_cleanse(ec->key, ec->keylen);
+               OPENSSL_free(ec->key);
+               ec->key = NULL;
+               ec->keylen = 0;
+               }
+       if (ok)
+               return ret;
+       BIO_free(ret);
+       return NULL;
+
        }