openssl: Add support for AES in CCM mode
authorTobias Brunner <tobias@strongswan.org>
Tue, 4 May 2021 16:19:57 +0000 (18:19 +0200)
committerTobias Brunner <tobias@strongswan.org>
Thu, 6 May 2021 16:29:16 +0000 (18:29 +0200)
While CCM is available in earlier versions, we only use it with
OpenSSL 1.1.0 and newer because the generic control variables are not
available before and we default to GCM for them.

Closes strongswan/strongswan#353.

src/libstrongswan/plugins/openssl/openssl_aead.c
src/libstrongswan/plugins/openssl/openssl_plugin.c

index 52c5ac3..9262cfb 100644 (file)
 #define EVP_CTRL_AEAD_GET_TAG EVP_CTRL_GCM_GET_TAG
 #endif
 
+/* not defined for older versions of BoringSSL */
+#ifndef EVP_CIPH_CCM_MODE
+#define EVP_CIPH_CCM_MODE 0xffff
+#endif
+
 /** as defined in RFC 4106 */
-#define IV_LEN         8
-#define SALT_LEN       4
-#define NONCE_LEN      (IV_LEN + SALT_LEN)
+#define IV_LEN                 8
+#define SALT_LEN               4
+#define NONCE_LEN              (IV_LEN + SALT_LEN)
+/** as defined in RFC 4309 */
+#define CCM_SALT_LEN   3
 
 typedef struct private_aead_t private_aead_t;
 
@@ -57,6 +64,11 @@ struct private_aead_t {
        char salt[SALT_LEN];
 
        /**
+        * Size of the salt
+        */
+       size_t salt_size;
+
+       /**
         * Size of the integrity check value
         */
        size_t icv_size;
@@ -83,27 +95,39 @@ static bool crypt(private_aead_t *this, chunk_t data, chunk_t assoc, chunk_t iv,
        bool success = FALSE;
        int len;
 
-       memcpy(nonce, this->salt, SALT_LEN);
-       memcpy(nonce + SALT_LEN, iv.ptr, IV_LEN);
+       memcpy(nonce, this->salt, this->salt_size);
+       memcpy(nonce + this->salt_size, iv.ptr, IV_LEN);
 
        ctx = EVP_CIPHER_CTX_new();
        EVP_CIPHER_CTX_set_padding(ctx, 0);
        if (!EVP_CipherInit_ex(ctx, this->cipher, NULL, NULL, NULL, enc) ||
-               !EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, NONCE_LEN, NULL) ||
-               !EVP_CipherInit_ex(ctx, NULL, NULL, this->key.ptr, nonce, enc))
+               !EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN,
+                                                        this->salt_size + IV_LEN, NULL))
        {
                goto done;
        }
-       if (!enc && !EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, this->icv_size,
-                                                                        data.ptr + data.len))
-       {       /* set ICV for verification on decryption */
+       if ((!enc || EVP_CIPHER_mode(this->cipher) == EVP_CIPH_CCM_MODE) &&
+               !EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, this->icv_size,
+                                                        enc ? NULL : data.ptr + data.len))
+       {       /* set ICV for verification on decryption, CCM requires the ICV length
+                * when encrypting */
+               goto done;
+       }
+       if (!EVP_CipherInit_ex(ctx, NULL, NULL, this->key.ptr, nonce, enc))
+       {       /* set key and nonce */
+               goto done;
+       }
+       if (EVP_CIPHER_mode(this->cipher) == EVP_CIPH_CCM_MODE &&
+               !EVP_CipherUpdate(ctx, NULL, &len, NULL, data.len))
+       {       /* CCM requires setting the total input length (plain or cipher+ICV) */
                goto done;
        }
        if (assoc.len && !EVP_CipherUpdate(ctx, NULL, &len, assoc.ptr, assoc.len))
        {       /* set AAD if specified */
                goto done;
        }
-       if (!EVP_CipherUpdate(ctx, out, &len, data.ptr, data.len) ||
+       /* CCM doesn't like NULL pointers as input, make sure we don't pass one */
+       if (!EVP_CipherUpdate(ctx, out, &len, data.ptr ?: out, data.len) ||
                !EVP_CipherFinal_ex(ctx, out + len, &len))
        {       /* EVP_CipherFinal_ex fails if ICV is incorrect on decryption */
                goto done;
@@ -183,7 +207,7 @@ METHOD(aead_t, get_iv_gen, iv_gen_t*,
 METHOD(aead_t, get_key_size, size_t,
        private_aead_t *this)
 {
-       return this->key.len + SALT_LEN;
+       return this->key.len + this->salt_size;
 }
 
 METHOD(aead_t, set_key, bool,
@@ -193,7 +217,7 @@ METHOD(aead_t, set_key, bool,
        {
                return FALSE;
        }
-       memcpy(this->salt, key.ptr + key.len - SALT_LEN, SALT_LEN);
+       memcpy(this->salt, key.ptr + key.len - this->salt_size, this->salt_size);
        memcpy(this->key.ptr, key.ptr, this->key.len);
        return TRUE;
 }
@@ -226,17 +250,21 @@ aead_t *openssl_aead_create(encryption_algorithm_t algo,
                        .set_key = _set_key,
                        .destroy = _destroy,
                },
+               .salt_size = SALT_LEN,
        );
 
        switch (algo)
        {
                case ENCR_AES_GCM_ICV8:
+               case ENCR_AES_CCM_ICV8:
                        this->icv_size = 8;
                        break;
                case ENCR_AES_GCM_ICV12:
+               case ENCR_AES_CCM_ICV12:
                        this->icv_size = 12;
                        break;
                case ENCR_AES_GCM_ICV16:
+               case ENCR_AES_CCM_ICV16:
                        this->icv_size = 16;
                        break;
                case ENCR_CHACHA20_POLY1305:
@@ -247,13 +275,6 @@ aead_t *openssl_aead_create(encryption_algorithm_t algo,
                        return NULL;
        }
 
-       if (salt_size && salt_size != SALT_LEN)
-       {
-               /* currently not supported */
-               free(this);
-               return NULL;
-       }
-
        switch (algo)
        {
                case ENCR_AES_GCM_ICV8:
@@ -278,6 +299,31 @@ aead_t *openssl_aead_create(encryption_algorithm_t algo,
                                        return NULL;
                        }
                        break;
+#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
+               case ENCR_AES_CCM_ICV8:
+               case ENCR_AES_CCM_ICV12:
+               case ENCR_AES_CCM_ICV16:
+                       switch (key_size)
+                       {
+                               case 0:
+                                       key_size = 16;
+                                       /* FALL */
+                               case 16:
+                                       this->cipher = EVP_aes_128_ccm();
+                                       break;
+                               case 24:
+                                       this->cipher = EVP_aes_192_ccm();
+                                       break;
+                               case 32:
+                                       this->cipher = EVP_aes_256_ccm();
+                                       break;
+                               default:
+                                       free(this);
+                                       return NULL;
+                       }
+                       this->salt_size = CCM_SALT_LEN;
+                       break;
+#endif /* OPENSSL_VERSION_NUMBER */
 #if OPENSSL_VERSION_NUMBER >= 0x1010000fL && !defined(OPENSSL_NO_CHACHA)
                case ENCR_CHACHA20_POLY1305:
                        switch (key_size)
@@ -299,6 +345,13 @@ aead_t *openssl_aead_create(encryption_algorithm_t algo,
                        return NULL;
        }
 
+       if (salt_size && salt_size != this->salt_size)
+       {
+               /* currently not supported */
+               free(this);
+               return NULL;
+       }
+
        if (!this->cipher)
        {
                free(this);
index 851d029..edc5dda 100644 (file)
@@ -615,6 +615,18 @@ METHOD(plugin_t, get_features, int,
                        PLUGIN_PROVIDE(AEAD, ENCR_AES_GCM_ICV8,  16),
                        PLUGIN_PROVIDE(AEAD, ENCR_AES_GCM_ICV8,  24),
                        PLUGIN_PROVIDE(AEAD, ENCR_AES_GCM_ICV8,  32),
+#if OPENSSL_VERSION_NUMBER >= 0x1010000fL
+                       /* CCM is available before 1.1.0 but not via generic controls */
+                       PLUGIN_PROVIDE(AEAD, ENCR_AES_CCM_ICV16, 16),
+                       PLUGIN_PROVIDE(AEAD, ENCR_AES_CCM_ICV16, 24),
+                       PLUGIN_PROVIDE(AEAD, ENCR_AES_CCM_ICV16, 32),
+                       PLUGIN_PROVIDE(AEAD, ENCR_AES_CCM_ICV12, 16),
+                       PLUGIN_PROVIDE(AEAD, ENCR_AES_CCM_ICV12, 24),
+                       PLUGIN_PROVIDE(AEAD, ENCR_AES_CCM_ICV12, 32),
+                       PLUGIN_PROVIDE(AEAD, ENCR_AES_CCM_ICV8,  16),
+                       PLUGIN_PROVIDE(AEAD, ENCR_AES_CCM_ICV8,  24),
+                       PLUGIN_PROVIDE(AEAD, ENCR_AES_CCM_ICV8,  32),
+#endif /* OPENSSL_VERSION_NUMBER */
 #endif /* OPENSSL_NO_AES */
 #if OPENSSL_VERSION_NUMBER >= 0x1010000fL && !defined(OPENSSL_NO_CHACHA)
                        PLUGIN_PROVIDE(AEAD, ENCR_CHACHA20_POLY1305, 32),