Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions patches/boringssl/expose_aes-cfb8.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
--- a/crypto/cipher/get_cipher.cc

Check warning on line 1 in patches/boringssl/expose_aes-cfb8.patch

View check run for this annotation

Claude / Claude Code Review

PR description is stale after review reworks

The PR description is stale after review reworks: the Fix section still says "Also registers the missing `aes-192-cfb` (CFB128) variant" (dropped in 5ddaffb) and the Verification section still references `test/regression/issue/28521.test.ts` (moved into `cipheriv-decipheriv.test.ts`). Per `.claude/docs/landing-prs.md` (PR process), the description becomes the squash-commit message — please drop the aes-192-cfb sentence and update the test path.
Comment thread
robobun marked this conversation as resolved.
+++ 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 <openssl/aes.h>
#include <openssl/obj.h>

+#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<EVP_CFB_CTX *>(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);

6 changes: 6 additions & 0 deletions scripts/build/deps/boringssl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
81 changes: 73 additions & 8 deletions test/js/bun/crypto/cipheriv-decipheriv.test.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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");
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Loading