diff --git a/patches/boringssl/expose_aes-cfb8.patch b/patches/boringssl/expose_aes-cfb8.patch new file mode 100644 index 000000000000..38d6c942dd19 --- /dev/null +++ b/patches/boringssl/expose_aes-cfb8.patch @@ -0,0 +1,149 @@ +--- a/crypto/cipher/get_cipher.cc ++++ b/crypto/cipher/get_cipher.cc +@@ -32,17 +32,20 @@ + } kCiphers[] = { + {NID_aes_128_cbc, "aes-128-cbc", EVP_aes_128_cbc}, + {NID_aes_128_cfb128, "aes-128-cfb", EVP_aes_128_cfb128}, ++ {NID_aes_128_cfb8, "aes-128-cfb8", EVP_aes_128_cfb8}, + {NID_aes_128_ctr, "aes-128-ctr", EVP_aes_128_ctr}, + {NID_aes_128_ecb, "aes-128-ecb", EVP_aes_128_ecb}, + {NID_aes_128_gcm, "aes-128-gcm", EVP_aes_128_gcm}, + {NID_aes_128_ofb128, "aes-128-ofb", EVP_aes_128_ofb}, + {NID_aes_192_cbc, "aes-192-cbc", EVP_aes_192_cbc}, ++ {NID_aes_192_cfb8, "aes-192-cfb8", EVP_aes_192_cfb8}, + {NID_aes_192_ctr, "aes-192-ctr", EVP_aes_192_ctr}, + {NID_aes_192_ecb, "aes-192-ecb", EVP_aes_192_ecb}, + {NID_aes_192_gcm, "aes-192-gcm", EVP_aes_192_gcm}, + {NID_aes_192_ofb128, "aes-192-ofb", EVP_aes_192_ofb}, + {NID_aes_256_cbc, "aes-256-cbc", EVP_aes_256_cbc}, + {NID_aes_256_cfb128, "aes-256-cfb", EVP_aes_256_cfb128}, ++ {NID_aes_256_cfb8, "aes-256-cfb8", EVP_aes_256_cfb8}, + {NID_aes_256_ctr, "aes-256-ctr", EVP_aes_256_ctr}, + {NID_aes_256_ecb, "aes-256-ecb", EVP_aes_256_ecb}, + {NID_aes_256_gcm, "aes-256-gcm", EVP_aes_256_gcm}, +--- a/decrepit/cfb/cfb.cc ++++ b/decrepit/cfb/cfb.cc +@@ -19,6 +19,7 @@ + #include + #include + ++#include "../../crypto/fipsmodule/aes/internal.h" + #include "../../crypto/fipsmodule/cipher/internal.h" + #include "../../crypto/internal.h" + +@@ -102,3 +103,74 @@ + const EVP_CIPHER *EVP_aes_192_cfb() { return &aes_192_cfb128; } + const EVP_CIPHER *EVP_aes_256_cfb128() { return &aes_256_cfb128; } + const EVP_CIPHER *EVP_aes_256_cfb() { return &aes_256_cfb128; } ++ ++// CFB8 mode — processes 8 bits (1 byte) at a time. ++ ++static void aes_encrypt_block(const uint8_t in[16], uint8_t out[16], ++ const AES_KEY *key) { ++ AES_encrypt(in, out, key); ++} ++ ++static int aes_cfb8_cipher_update(EVP_CIPHER_CTX *ctx, uint8_t *out, ++ const uint8_t *in, size_t len) { ++ if (!out || !in) { ++ return 0; ++ } ++ ++ EVP_CFB_CTX *cfb_ctx = reinterpret_cast(ctx->cipher_data); ++ unsigned num = 0; ++ bssl::CRYPTO_cfb128_8_encrypt(in, out, len, &cfb_ctx->ks, ctx->iv, &num, ++ ctx->encrypt ? AES_ENCRYPT : AES_DECRYPT, ++ aes_encrypt_block); ++ ++ return 1; ++} ++ ++static const EVP_CIPHER aes_128_cfb8 = { ++ /* nid= */ NID_aes_128_cfb8, ++ /* block_size= */ 1, ++ /* key_len= */ 16, ++ /* iv_len= */ 16, ++ /* ctx_size= */ sizeof(EVP_CFB_CTX), ++ /* flags= */ EVP_CIPH_CFB_MODE, ++ /* init= */ aes_cfb_init_key, ++ /* cipher_update= */ aes_cfb8_cipher_update, ++ /* cipher_final= */ nullptr, ++ /* update_aad= */ nullptr, ++ /* cleanup= */ nullptr, ++ /* ctrl= */ nullptr, ++}; ++ ++static const EVP_CIPHER aes_192_cfb8 = { ++ /* nid= */ NID_aes_192_cfb8, ++ /* block_size= */ 1, ++ /* key_len= */ 24, ++ /* iv_len= */ 16, ++ /* ctx_size= */ sizeof(EVP_CFB_CTX), ++ /* flags= */ EVP_CIPH_CFB_MODE, ++ /* init= */ aes_cfb_init_key, ++ /* cipher_update= */ aes_cfb8_cipher_update, ++ /* cipher_final= */ nullptr, ++ /* update_aad= */ nullptr, ++ /* cleanup= */ nullptr, ++ /* ctrl= */ nullptr, ++}; ++ ++static const EVP_CIPHER aes_256_cfb8 = { ++ /* nid= */ NID_aes_256_cfb8, ++ /* block_size= */ 1, ++ /* key_len= */ 32, ++ /* iv_len= */ 16, ++ /* ctx_size= */ sizeof(EVP_CFB_CTX), ++ /* flags= */ EVP_CIPH_CFB_MODE, ++ /* init= */ aes_cfb_init_key, ++ /* cipher_update= */ aes_cfb8_cipher_update, ++ /* cipher_final= */ nullptr, ++ /* update_aad= */ nullptr, ++ /* cleanup= */ nullptr, ++ /* ctrl= */ nullptr, ++}; ++ ++const EVP_CIPHER *EVP_aes_128_cfb8() { return &aes_128_cfb8; } ++const EVP_CIPHER *EVP_aes_192_cfb8() { return &aes_192_cfb8; } ++const EVP_CIPHER *EVP_aes_256_cfb8() { return &aes_256_cfb8; } +--- a/decrepit/evp/evp_do_all.cc ++++ b/decrepit/evp/evp_do_all.cc +@@ -23,17 +23,20 @@ + // Cipher lookups are case-insensitive, so uppercase names are not needed. + callback(EVP_aes_128_cbc(), "aes-128-cbc", nullptr, arg); + callback(EVP_aes_128_cfb128(), "aes-128-cfb", nullptr, arg); ++ callback(EVP_aes_128_cfb8(), "aes-128-cfb8", nullptr, arg); + callback(EVP_aes_128_ctr(), "aes-128-ctr", nullptr, arg); + callback(EVP_aes_128_ecb(), "aes-128-ecb", nullptr, arg); + callback(EVP_aes_128_gcm(), "aes-128-gcm", nullptr, arg); + callback(EVP_aes_128_ofb(), "aes-128-ofb", nullptr, arg); + callback(EVP_aes_192_cbc(), "aes-192-cbc", nullptr, arg); ++ callback(EVP_aes_192_cfb8(), "aes-192-cfb8", nullptr, arg); + callback(EVP_aes_192_ctr(), "aes-192-ctr", nullptr, arg); + callback(EVP_aes_192_ecb(), "aes-192-ecb", nullptr, arg); + callback(EVP_aes_192_gcm(), "aes-192-gcm", nullptr, arg); + callback(EVP_aes_192_ofb(), "aes-192-ofb", nullptr, arg); + callback(EVP_aes_256_cbc(), "aes-256-cbc", nullptr, arg); + callback(EVP_aes_256_cfb128(), "aes-256-cfb", nullptr, arg); ++ callback(EVP_aes_256_cfb8(), "aes-256-cfb8", nullptr, arg); + callback(EVP_aes_256_ctr(), "aes-256-ctr", nullptr, arg); + callback(EVP_aes_256_ecb(), "aes-256-ecb", nullptr, arg); + callback(EVP_aes_256_gcm(), "aes-256-gcm", nullptr, arg); +--- a/include/openssl/cipher.h ++++ b/include/openssl/cipher.h +@@ -572,6 +572,15 @@ + // decrepit. + OPENSSL_EXPORT const EVP_CIPHER *EVP_aes_256_cfb(void); + ++// EVP_aes_128_cfb8 is only available in decrepit. ++OPENSSL_EXPORT const EVP_CIPHER *EVP_aes_128_cfb8(void); ++ ++// EVP_aes_192_cfb8 is only available in decrepit. ++OPENSSL_EXPORT const EVP_CIPHER *EVP_aes_192_cfb8(void); ++ ++// EVP_aes_256_cfb8 is only available in decrepit. ++OPENSSL_EXPORT const EVP_CIPHER *EVP_aes_256_cfb8(void); ++ + // EVP_bf_ecb is Blowfish in ECB mode and is only available in decrepit. + OPENSSL_EXPORT const EVP_CIPHER *EVP_bf_ecb(void); + diff --git a/scripts/build/deps/boringssl.ts b/scripts/build/deps/boringssl.ts index 9703b4cea646..7d36cc40364b 100644 --- a/scripts/build/deps/boringssl.ts +++ b/scripts/build/deps/boringssl.ts @@ -36,6 +36,12 @@ export const boringssl: Dependency = { commit: BORINGSSL_COMMIT, }), + patches: [ + // Upstream has CRYPTO_cfb128_8_encrypt but no EVP wrappers for CFB8, so + // createCipheriv("aes-*-cfb8") fails. See oven-sh/bun#28521. + "patches/boringssl/expose_aes-cfb8.patch", + ], + build: cfg => { // win-x64 uses NASM-syntax .asm; everything else (including win-aarch64) // uses gas .S that clang assembles. diff --git a/test/js/bun/crypto/cipheriv-decipheriv.test.ts b/test/js/bun/crypto/cipheriv-decipheriv.test.ts index 0c1a427fda99..ca6edad720bf 100644 --- a/test/js/bun/crypto/cipheriv-decipheriv.test.ts +++ b/test/js/bun/crypto/cipheriv-decipheriv.test.ts @@ -1,5 +1,5 @@ import { expect, it } from "bun:test"; -import { BinaryLike, CipherGCM, createCipheriv, createDecipheriv, DecipherGCM, randomBytes } from "crypto"; +import { BinaryLike, CipherGCM, createCipheriv, createDecipheriv, DecipherGCM, getCiphers, randomBytes } from "crypto"; /** * Perform a sample encryption and decryption @@ -163,13 +163,13 @@ const references = { authTag: null, }, - // BoringSSL does not support these modes - // "aes-128-cfb8": { - // iv: "3021d44812302ae0312c9ef523f01bf5", - // key: "20787258b5d2a166262ecc6e3e917a58", - // ciphertext: "db4596b2f0d7a74bea91a1d715e1327ca149591f5bc64d19fde7138eacfa5dd0da503596dcc66bc771edcf14b6eb8f69", - // authTag: null, - // }, + "aes-128-cfb8": { + iv: "3021d44812302ae0312c9ef523f01bf5", + key: "20787258b5d2a166262ecc6e3e917a58", + ciphertext: "db4596b2f0d7a74bea91a1d715e1327ca149591f5bc64d19fde7138eacfa5dd0da503596dcc66bc771edcf14b6eb8f69", + authTag: null, + }, + // BoringSSL does not support CFB1 mode // "aes-128-cfb1": { // iv: "c91453a0182f1efeeb4525ed96b0aad3", // key: "26bfaea72f720475528cc5b2bfd5cf2e", @@ -261,3 +261,68 @@ it("should ignore authTagLength for non-authenticated cipher modes", () => { gcm.final(); expect(gcm.getAuthTag().length).toBe(12); }); + +// AES-CFB8 support (see issue #28521) — BoringSSL didn't expose CFB8 via EVP. +it("aes-128-cfb8 cipher creates successfully", () => { + const cipher = createCipheriv("aes-128-cfb8", Buffer.alloc(16), Buffer.alloc(16)); + expect(cipher).toBeDefined(); +}); + +it("aes-192-cfb8 cipher creates successfully", () => { + const cipher = createCipheriv("aes-192-cfb8", Buffer.alloc(24), Buffer.alloc(16)); + expect(cipher).toBeDefined(); +}); + +it("aes-256-cfb8 cipher creates successfully", () => { + const cipher = createCipheriv("aes-256-cfb8", Buffer.alloc(32), Buffer.alloc(16)); + expect(cipher).toBeDefined(); +}); + +it("aes-128-cfb8 encrypt/decrypt roundtrip", () => { + const key = Buffer.from("0123456789abcdef"); + const iv = Buffer.from("fedcba9876543210"); + const plaintext = Buffer.from("Hello, CFB8 world!"); + + const cipher = createCipheriv("aes-128-cfb8", key, iv); + const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]); + + const decipher = createDecipheriv("aes-128-cfb8", key, iv); + const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]); + + expect(decrypted).toEqual(plaintext); +}); + +it("aes-192-cfb8 encrypt/decrypt roundtrip", () => { + const key = Buffer.from("0123456789abcdef01234567"); // 24 bytes + const iv = Buffer.from("fedcba9876543210"); + const plaintext = Buffer.from("Test data for AES-192-CFB8 mode"); + + const cipher = createCipheriv("aes-192-cfb8", key, iv); + const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]); + + const decipher = createDecipheriv("aes-192-cfb8", key, iv); + const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]); + + expect(decrypted).toEqual(plaintext); +}); + +it("aes-256-cfb8 encrypt/decrypt roundtrip", () => { + const key = Buffer.from("0123456789abcdef0123456789abcdef"); + const iv = Buffer.from("fedcba9876543210"); + const plaintext = Buffer.from("Test data for AES-256-CFB8 mode"); + + const cipher = createCipheriv("aes-256-cfb8", key, iv); + const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]); + + const decipher = createDecipheriv("aes-256-cfb8", key, iv); + const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]); + + expect(decrypted).toEqual(plaintext); +}); + +it("cfb8 variants appear in getCiphers()", () => { + const ciphers = getCiphers(); + expect(ciphers).toContain("aes-128-cfb8"); + expect(ciphers).toContain("aes-192-cfb8"); + expect(ciphers).toContain("aes-256-cfb8"); +});