Fix for CMS/PKCS7 MMA. If RSA decryption fails use a random key and
authorDr. Stephen Henson <steve@openssl.org>
Mon, 12 Mar 2012 14:51:45 +0000 (14:51 +0000)
committerDr. Stephen Henson <steve@openssl.org>
Mon, 12 Mar 2012 14:51:45 +0000 (14:51 +0000)
continue with symmetric decryption process to avoid leaking timing
information to an attacker.

Thanks to Ivan Nestlerode <inestlerode@us.ibm.com> for discovering
this issue. (CVE-2012-0884)

CHANGES
apps/cms.c
crypto/cms/cms.h
crypto/cms/cms_enc.c
crypto/cms/cms_env.c
crypto/cms/cms_lcl.h
crypto/cms/cms_smime.c
crypto/pkcs7/pk7_doit.c

diff --git a/CHANGES b/CHANGES
index 59de4639fa699ea1d8478746045e4425a1cbf301..a2409acb9ea06887f00224a6786228416569dd24 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -4,6 +4,17 @@
 
  Changes between 0.9.8t and 0.9.8u [xx XXX xxxx]
 
+  *) Fix MMA (Bleichenbacher's attack on PKCS #1 v1.5 RSA padding) weakness
+     in CMS and PKCS7 code. When RSA decryption fails use a random key for
+     content decryption and always return the same error. Note: this attack
+     needs on average 2^20 messages so it only affects automated senders. The
+     old behaviour can be reenabled in the CMS code by setting the
+     CMS_DEBUG_DECRYPT flag: this is useful for debugging and testing where
+     an MMA defence is not necessary.
+     Thanks to Ivan Nestlerode <inestlerode@us.ibm.com> for discovering
+     this issue. (CVE-2012-0884)
+     [Steve Henson]
+
   *) Fix CVE-2011-4619: make sure we really are receiving a 
      client hello before rejecting multiple SGC restarts. Thanks to
      Ivan Nestlerode <inestlerode@us.ibm.com> for discovering this bug.
index 7407ae19ce491b2fc12a3cd5e8cc317810af193a..b8c0ee8dd5d93ae2ae59e81060191692876efbc2 100644 (file)
@@ -226,6 +226,8 @@ int MAIN(int argc, char **argv)
                else if (!strcmp(*args,"-camellia256"))
                                cipher = EVP_camellia_256_cbc();
 #endif
+               else if (!strcmp (*args, "-debug_decrypt")) 
+                               flags |= CMS_DEBUG_DECRYPT;
                else if (!strcmp (*args, "-text")) 
                                flags |= CMS_TEXT;
                else if (!strcmp (*args, "-nointern")) 
@@ -1013,6 +1015,8 @@ int MAIN(int argc, char **argv)
        ret = 4;
        if (operation == SMIME_DECRYPT)
                {
+               if (flags & CMS_DEBUG_DECRYPT)
+                       CMS_decrypt(cms, NULL, NULL, NULL, NULL, flags);
 
                if (secret_key)
                        {
index 25f88745f23e86e76f9aa854f451e554f76617b6..75e3be0e4bc5cef0640f4b58286a70878c889809 100644 (file)
@@ -110,6 +110,7 @@ DECLARE_ASN1_FUNCTIONS_const(CMS_ReceiptRequest)
 #define CMS_PARTIAL                    0x4000
 #define CMS_REUSE_DIGEST               0x8000
 #define CMS_USE_KEYID                  0x10000
+#define CMS_DEBUG_DECRYPT              0x20000
 
 const ASN1_OBJECT *CMS_get0_type(CMS_ContentInfo *cms);
 
index bab26235bdc4674d1997b46aa512dba02719f0e0..580083b45f870aebd3e21680cfe70d6d15260dfd 100644 (file)
@@ -73,6 +73,8 @@ BIO *cms_EncryptedContent_init_bio(CMS_EncryptedContentInfo *ec)
        const EVP_CIPHER *ciph;
        X509_ALGOR *calg = ec->contentEncryptionAlgorithm;
        unsigned char iv[EVP_MAX_IV_LENGTH], *piv = NULL;
+       unsigned char *tkey = NULL;
+       size_t tkeylen;
 
        int ok = 0;
 
@@ -137,32 +139,57 @@ BIO *cms_EncryptedContent_init_bio(CMS_EncryptedContentInfo *ec)
                                CMS_R_CIPHER_PARAMETER_INITIALISATION_ERROR);
                goto err;
                }
-
-
-       if (enc && !ec->key)
+       /* Generate random session key */
+       if (!enc || !ec->key)
                {
-               /* Generate random key */
-               if (!ec->keylen)
-                       ec->keylen = EVP_CIPHER_CTX_key_length(ctx);
-               ec->key = OPENSSL_malloc(ec->keylen);
-               if (!ec->key)
+               tkeylen = EVP_CIPHER_CTX_key_length(ctx);
+               tkey = OPENSSL_malloc(tkeylen);
+               if (!tkey)
                        {
                        CMSerr(CMS_F_CMS_ENCRYPTEDCONTENT_INIT_BIO,
                                                        ERR_R_MALLOC_FAILURE);
                        goto err;
                        }
-               if (EVP_CIPHER_CTX_rand_key(ctx, ec->key) <= 0)
+               if (EVP_CIPHER_CTX_rand_key(ctx, tkey) <= 0)
                        goto err;
-               keep_key = 1;
                }
-       else if (ec->keylen != (unsigned int)EVP_CIPHER_CTX_key_length(ctx))
+
+       if (!ec->key)
+               {
+               ec->key = tkey;
+               ec->keylen = tkeylen;
+               tkey = NULL;
+               if (enc)
+                       keep_key = 1;
+               else
+                       ERR_clear_error();
+               
+               }
+
+       if (ec->keylen != tkeylen)
                {
                /* If necessary set key length */
                if (EVP_CIPHER_CTX_set_key_length(ctx, ec->keylen) <= 0)
                        {
-                       CMSerr(CMS_F_CMS_ENCRYPTEDCONTENT_INIT_BIO,
-                               CMS_R_INVALID_KEY_LENGTH);
-                       goto err;
+                       /* Only reveal failure if debugging so we don't
+                        * leak information which may be useful in MMA.
+                        */
+                       if (ec->debug)
+                               {
+                               CMSerr(CMS_F_CMS_ENCRYPTEDCONTENT_INIT_BIO,
+                                               CMS_R_INVALID_KEY_LENGTH);
+                               goto err;
+                               }
+                       else
+                               {
+                               /* Use random key */
+                               OPENSSL_cleanse(ec->key, ec->keylen);
+                               OPENSSL_free(ec->key);
+                               ec->key = tkey;
+                               ec->keylen = tkeylen;
+                               tkey = NULL;
+                               ERR_clear_error();
+                               }
                        }
                }
 
@@ -198,6 +225,11 @@ BIO *cms_EncryptedContent_init_bio(CMS_EncryptedContentInfo *ec)
                OPENSSL_free(ec->key);
                ec->key = NULL;
                }
+       if (tkey)
+               {
+               OPENSSL_cleanse(tkey, tkeylen);
+               OPENSSL_free(tkey);
+               }
        if (ok)
                return b;
        BIO_free(b);
index d499ae85b4000a6c366c4b7d1f5ce1710a576384..b8685fa17590a8d92862ddf10188b3a8a7ad4ccc 100644 (file)
@@ -352,6 +352,8 @@ static int cms_RecipientInfo_ktri_decrypt(CMS_ContentInfo *cms,
        unsigned char *ek = NULL;
        int eklen;
        int ret = 0;
+       CMS_EncryptedContentInfo *ec;
+       ec = cms->d.envelopedData->encryptedContentInfo;
 
        if (ktri->pkey == NULL)
                {
@@ -382,8 +384,14 @@ static int cms_RecipientInfo_ktri_decrypt(CMS_ContentInfo *cms,
 
        ret = 1;
 
-       cms->d.envelopedData->encryptedContentInfo->key = ek;
-       cms->d.envelopedData->encryptedContentInfo->keylen = eklen;
+       if (ec->key)
+               {
+               OPENSSL_cleanse(ec->key, ec->keylen);
+               OPENSSL_free(ec->key);
+               }
+
+       ec->key = ek;
+       ec->keylen = eklen;
 
        err:
        if (!ret && ek)
index 7d60fac67eb71a32ad1ce7d8d665dc80f864e12c..ce65d6ef665c076d92bde3d317a52c89aa4b720e 100644 (file)
@@ -175,6 +175,8 @@ struct CMS_EncryptedContentInfo_st
        const EVP_CIPHER *cipher;
        unsigned char *key;
        size_t keylen;
+       /* Set to 1 if we are debugging decrypt and don't fake keys for MMA */
+       int debug;
        };
 
 struct CMS_RecipientInfo_st
index f35883aa22b3bf1bfe6822a1bd440183bb5d0a8c..2be07c2099af636973f5b9090ed0e8baf81167ce 100644 (file)
@@ -622,7 +622,10 @@ int CMS_decrypt_set1_pkey(CMS_ContentInfo *cms, EVP_PKEY *pk, X509 *cert)
        STACK_OF(CMS_RecipientInfo) *ris;
        CMS_RecipientInfo *ri;
        int i, r;
+       int debug = 0;
        ris = CMS_get0_RecipientInfos(cms);
+       if (ris)
+               debug = cms->d.envelopedData->encryptedContentInfo->debug;
        for (i = 0; i < sk_CMS_RecipientInfo_num(ris); i++)
                {
                ri = sk_CMS_RecipientInfo_value(ris, i);
@@ -636,17 +639,38 @@ int CMS_decrypt_set1_pkey(CMS_ContentInfo *cms, EVP_PKEY *pk, X509 *cert)
                        CMS_RecipientInfo_set0_pkey(ri, pk);
                        r = CMS_RecipientInfo_decrypt(cms, ri);
                        CMS_RecipientInfo_set0_pkey(ri, NULL);
-                       if (r > 0)
-                               return 1;
                        if (cert)
                                {
+                               /* If not debugging clear any error and
+                                * return success to avoid leaking of
+                                * information useful to MMA
+                                */
+                               if (!debug)
+                                       {
+                                       ERR_clear_error();
+                                       return 1;
+                                       }
+                               if (r > 0)
+                                       return 1;
                                CMSerr(CMS_F_CMS_DECRYPT_SET1_PKEY,
                                                CMS_R_DECRYPT_ERROR);
                                return 0;
                                }
-                       ERR_clear_error();
+                       /* If no cert and not debugging don't leave loop
+                        * after first successful decrypt. Always attempt
+                        * to decrypt all recipients to avoid leaking timing
+                        * of a successful decrypt.
+                        */
+                       else if (r > 0 && debug)
+                               return 1;
                        }
                }
+       /* If no cert and not debugging always return success */
+       if (!cert && !debug)
+               {
+               ERR_clear_error();
+               return 1;
+               }
 
        CMSerr(CMS_F_CMS_DECRYPT_SET1_PKEY, CMS_R_NO_MATCHING_RECIPIENT);
        return 0;
@@ -705,9 +729,14 @@ int CMS_decrypt(CMS_ContentInfo *cms, EVP_PKEY *pk, X509 *cert,
                }
        if (!dcont && !check_content(cms))
                return 0;
+       if (flags & CMS_DEBUG_DECRYPT)
+               cms->d.envelopedData->encryptedContentInfo->debug = 1;
+       else
+               cms->d.envelopedData->encryptedContentInfo->debug = 0;
+       if (!pk && !cert && !dcont && !out)
+               return 1;
        if (pk && !CMS_decrypt_set1_pkey(cms, pk, cert))
                return 0;
-
        cont = CMS_dataInit(cms, dcont);
        if (!cont)
                return 0;
index c8f1eb1b458959066c42ab8a1012a6c8eacf4c2e..8b3024e77407a51a5882b3914c510d9f8da8ea0e 100644 (file)
@@ -420,6 +420,8 @@ BIO *PKCS7_dataDecode(PKCS7 *p7, EVP_PKEY *pkey, BIO *in_bio, X509 *pcert)
                int max;
                X509_OBJECT ret;
 #endif
+               unsigned char *tkey = NULL;
+               int tkeylen;
                int jj;
 
                if ((etmp=BIO_new(BIO_f_cipher())) == NULL)
@@ -461,36 +463,42 @@ BIO *PKCS7_dataDecode(PKCS7 *p7, EVP_PKEY *pkey, BIO *in_bio, X509 *pcert)
 
                if (pcert == NULL)
                        {
+                       /* Temporary storage in case EVP_PKEY_decrypt
+                        * overwrites output buffer on error.
+                        */
+                       unsigned char *tmp2;
+                       tmp2 = OPENSSL_malloc(jj);
+                       if (!tmp2)
+                               goto err;
+                       jj = -1;
+                       /* Always attempt to decrypt all cases to avoid
+                        * leaking timing information about a successful
+                        * decrypt.
+                        */
                        for (i=0; i<sk_PKCS7_RECIP_INFO_num(rsk); i++)
                                {
+                               int tret;
                                ri=sk_PKCS7_RECIP_INFO_value(rsk,i);
-                               jj=EVP_PKEY_decrypt(tmp,
+                               tret=EVP_PKEY_decrypt(tmp2,
                                        M_ASN1_STRING_data(ri->enc_key),
                                        M_ASN1_STRING_length(ri->enc_key),
                                                pkey);
-                               if (jj > 0)
-                                       break;
+                               if (tret > 0)
+                                       {
+                                       memcpy(tmp, tmp2, tret);
+                                       OPENSSL_cleanse(tmp2, tret);
+                                       jj = tret;
+                                       }
                                ERR_clear_error();
-                               ri = NULL;
-                               }
-                       if (ri == NULL)
-                               {
-                               PKCS7err(PKCS7_F_PKCS7_DATADECODE,
-                                     PKCS7_R_NO_RECIPIENT_MATCHES_KEY);
-                               goto err;
                                }
+                       OPENSSL_free(tmp2);
                        }
                else
                        {
                        jj=EVP_PKEY_decrypt(tmp,
                                M_ASN1_STRING_data(ri->enc_key),
                                M_ASN1_STRING_length(ri->enc_key), pkey);
-                       if (jj <= 0)
-                               {
-                               PKCS7err(PKCS7_F_PKCS7_DATADECODE,
-                                                               ERR_R_EVP_LIB);
-                               goto err;
-                               }
+                       ERR_clear_error();
                        }
 
                evp_ctx=NULL;
@@ -499,24 +507,49 @@ BIO *PKCS7_dataDecode(PKCS7 *p7, EVP_PKEY *pkey, BIO *in_bio, X509 *pcert)
                        goto err;
                if (EVP_CIPHER_asn1_to_param(evp_ctx,enc_alg->parameter) < 0)
                        goto err;
+               /* Generate random key to counter MMA */
+               tkeylen = EVP_CIPHER_CTX_key_length(evp_ctx);
+               tkey = OPENSSL_malloc(tkeylen);
+               if (!tkey)
+                       goto err;
+               if (EVP_CIPHER_CTX_rand_key(evp_ctx, tkey) <= 0)
+                       goto err;
+               /* If we have no key use random key */
+               if (jj <= 0)
+                       {
+                       OPENSSL_free(tmp);
+                       jj = tkeylen;
+                       tmp = tkey;
+                       tkey = NULL;
+                       }
 
-               if (jj != EVP_CIPHER_CTX_key_length(evp_ctx)) {
+               if (jj != tkeylen) {
                        /* Some S/MIME clients don't use the same key
                         * and effective key length. The key length is
                         * determined by the size of the decrypted RSA key.
                         */
                        if(!EVP_CIPHER_CTX_set_key_length(evp_ctx, jj))
                                {
-                               PKCS7err(PKCS7_F_PKCS7_DATADECODE,
-                                       PKCS7_R_DECRYPTED_KEY_IS_WRONG_LENGTH);
-                               goto err;
+                               /* As MMA defence use random key instead */
+                               OPENSSL_cleanse(tmp, jj);
+                               OPENSSL_free(tmp);
+                               jj = tkeylen;
+                               tmp = tkey;
+                               tkey = NULL;
                                }
                } 
+               ERR_clear_error();
                if (EVP_CipherInit_ex(evp_ctx,NULL,NULL,tmp,NULL,0) <= 0)
                        goto err;
 
                OPENSSL_cleanse(tmp,jj);
 
+               if (tkey)
+                       {
+                       OPENSSL_cleanse(tkey, tkeylen);
+                       OPENSSL_free(tkey);
+                       }
+
                if (out == NULL)
                        out=etmp;
                else