From 1d94d678b375c4316cc3375b5a3fc1197de8d3b1 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Mon, 16 Dec 2024 19:13:26 +0100 Subject: [PATCH] LibCrypto: Implement AES-KW Add the AES-KW (Key Wrap) implementation as of https://www.rfc-editor.org/rfc/rfc3394#section-4.2. Tests are taken from section 4 of RFC3394. --- Libraries/LibCrypto/Cipher/AES.h | 2 + Libraries/LibCrypto/Cipher/Mode/KW.h | 132 ++++++++++++++++++++++ Tests/LibCrypto/TestAES.cpp | 157 ++++++++++++++++++++++++++- 3 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 Libraries/LibCrypto/Cipher/Mode/KW.h diff --git a/Libraries/LibCrypto/Cipher/AES.h b/Libraries/LibCrypto/Cipher/AES.h index eb0dcec2ae2..ab2b7b91d73 100644 --- a/Libraries/LibCrypto/Cipher/AES.h +++ b/Libraries/LibCrypto/Cipher/AES.h @@ -12,6 +12,7 @@ #include #include #include +#include namespace Crypto::Cipher { @@ -96,6 +97,7 @@ public: using CBCMode = CBC; using CTRMode = CTR; using GCMMode = GCM; + using KWMode = KW; constexpr static size_t BlockSizeInBits = BlockType::BlockSizeInBits; diff --git a/Libraries/LibCrypto/Cipher/Mode/KW.h b/Libraries/LibCrypto/Cipher/Mode/KW.h new file mode 100644 index 00000000000..866b56607e9 --- /dev/null +++ b/Libraries/LibCrypto/Cipher/Mode/KW.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2024, Altomani Gianluca + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Crypto::Cipher { + +template +class KW : public Mode { +public: + constexpr static size_t IVSizeInBits = 128; + constexpr static u8 default_iv[8] = { 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6 }; + + virtual ~KW() = default; + template + explicit constexpr KW(Args... args) + : Mode(args...) + { + } + + virtual ByteString class_name() const override + { + StringBuilder builder; + builder.append(this->cipher().class_name()); + builder.append("_KW"sv); + return builder.to_byte_string(); + } + + virtual size_t IV_length() const override + { + return IVSizeInBits / 8; + } + + // FIXME: This overload throws away the validation, think up a better way to return more than a single bytebuffer. + virtual void encrypt(ReadonlyBytes in, Bytes& out, [[maybe_unused]] ReadonlyBytes ivec = {}, [[maybe_unused]] Bytes* ivec_out = nullptr) override + { + this->wrap(in, out); + } + + virtual void decrypt(ReadonlyBytes in, Bytes& out, [[maybe_unused]] ReadonlyBytes ivec = {}) override + { + this->unwrap(in, out); + } + + void wrap(ReadonlyBytes in, Bytes& out) + { + // The plaintext consists of n 64-bit blocks, containing the key data being wrapped. + VERIFY(in.size() % 8 == 0); + VERIFY(out.size() >= in.size() + 8); + + auto& cipher = this->cipher(); + + auto iv = MUST(ByteBuffer::copy(default_iv, 8)); + auto data = MUST(ByteBuffer::copy(in)); + auto data_blocks = data.size() / 8; + + // For j = 0 to 5 + for (size_t j = 0; j < 6; ++j) { + // For i=1 to n + for (size_t i = 0; i < data_blocks; ++i) { + // B = AES(K, A | R[i]) + m_cipher_block.bytes().overwrite(0, iv.data(), 8); + m_cipher_block.bytes().overwrite(8, data.data() + i * 8, 8); + cipher.encrypt_block(m_cipher_block, m_cipher_block); + + // A = MSB(64, B) ^ t where t = (n*j)+i + u64 a = AK::convert_between_host_and_big_endian(ByteReader::load64(m_cipher_block.bytes().data())) ^ ((data_blocks * j) + i + 1); + ByteReader::store(iv.data(), AK::convert_between_host_and_big_endian(a)); + + // R[i] = LSB(64, B) + data.overwrite(i * 8, m_cipher_block.bytes().data() + 8, 8); + } + } + + out.overwrite(0, iv.data(), 8); + out.overwrite(8, data.data(), data.size()); + } + + VerificationConsistency unwrap(ReadonlyBytes in, Bytes& out) + { + // The inputs to the unwrap process are the KEK and (n+1) 64-bit blocks + // of ciphertext consisting of previously wrapped key. + VERIFY(in.size() % 8 == 0); + VERIFY(in.size() > 8); + + // It returns n blocks of plaintext consisting of the n 64 - bit blocks of the decrypted key data. + VERIFY(out.size() >= in.size() - 8); + + auto& cipher = this->cipher(); + + auto iv = MUST(ByteBuffer::copy(in.slice(0, 8))); + auto data = MUST(ByteBuffer::copy(in.slice(8, in.size() - 8))); + auto data_blocks = data.size() / 8; + + // For j = 5 to 0 + for (size_t j = 6; j > 0; --j) { + // For i = n to 1 + for (size_t i = data_blocks; i > 0; --i) { + // B = AES-1(K, (A ^ t) | R[i]) where t = n*j+i + u64 a = AK::convert_between_host_and_big_endian(ByteReader::load64(iv.data())) ^ ((data_blocks * (j - 1)) + i); + ByteReader::store(m_cipher_block.bytes().data(), AK::convert_between_host_and_big_endian(a)); + m_cipher_block.bytes().overwrite(8, data.data() + ((i - 1) * 8), 8); + cipher.decrypt_block(m_cipher_block, m_cipher_block); + + // A = MSB(64, B) + iv.overwrite(0, m_cipher_block.bytes().data(), 8); + + // R[i] = LSB(64, B) + data.overwrite((i - 1) * 8, m_cipher_block.bytes().data() + 8, 8); + } + } + + if (ReadonlyBytes { default_iv, 8 } != iv.bytes()) + return VerificationConsistency::Inconsistent; + + out.overwrite(0, data.data(), data.size()); + return VerificationConsistency::Consistent; + } + +private: + typename T::BlockType m_cipher_block {}; +}; + +} diff --git a/Tests/LibCrypto/TestAES.cpp b/Tests/LibCrypto/TestAES.cpp index cb9f0d14e8a..c84c033d49b 100644 --- a/Tests/LibCrypto/TestAES.cpp +++ b/Tests/LibCrypto/TestAES.cpp @@ -5,7 +5,6 @@ */ #include -#include #include #include #include @@ -572,3 +571,159 @@ TEST_CASE(test_AES_GCM_128bit_decrypt_multiple_blocks_with_aad) EXPECT(memcmp(result_pt, out.data(), out.size()) == 0); EXPECT_EQ(consistency, Crypto::VerificationConsistency::Consistent); } + +TEST_CASE(test_AES_KW_encrypt_128bits_with_128bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"_b, 128, Crypto::Cipher::Intent::Encryption); + + auto wrap_result = "\x1F\xA6\x8B\x0A\x81\x12\xB4\x47\xAE\xF3\x4B\xD8\xFB\x5A\x7B\x82\x9D\x3E\x86\x23\x71\xD2\xCF\xE5"_b; + auto in = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"_b; + + auto wrapped = MUST(ByteBuffer::create_zeroed(in.size() + 8)); + auto wrapped_bytes = wrapped.bytes(); + cipher.wrap(in, wrapped_bytes); + EXPECT(memcmp(wrapped_bytes.data(), wrap_result.data(), wrapped_bytes.size()) == 0); +} + +TEST_CASE(test_AES_KW_decrypt_128bits_with_128bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"_b, 128, Crypto::Cipher::Intent::Decryption); + + auto unwrap_result = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"_b; + auto in = "\x1F\xA6\x8B\x0A\x81\x12\xB4\x47\xAE\xF3\x4B\xD8\xFB\x5A\x7B\x82\x9D\x3E\x86\x23\x71\xD2\xCF\xE5"_b; + + auto unwrapped = MUST(ByteBuffer::create_zeroed(in.size() - 8)); + auto unwrapped_bytes = unwrapped.bytes(); + EXPECT_EQ(cipher.unwrap(in, unwrapped_bytes), Crypto::VerificationConsistency::Consistent); + EXPECT(memcmp(unwrapped_bytes.data(), unwrap_result.data(), unwrap_result.size()) == 0); +} + +TEST_CASE(test_AES_KW_encrypt_128bits_with_192bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17"_b, 192, Crypto::Cipher::Intent::Encryption); + + auto wrap_result = "\x96\x77\x8B\x25\xAE\x6C\xA4\x35\xF9\x2B\x5B\x97\xC0\x50\xAE\xD2\x46\x8A\xB8\xA1\x7A\xD8\x4E\x5D"_b; + auto in = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"_b; + + auto wrapped = MUST(ByteBuffer::create_zeroed(in.size() + 8)); + auto wrapped_bytes = wrapped.bytes(); + cipher.wrap(in, wrapped_bytes); + EXPECT(memcmp(wrapped_bytes.data(), wrap_result.data(), wrapped_bytes.size()) == 0); +} + +TEST_CASE(test_AES_KW_decrypt_128bits_with_192bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17"_b, 192, Crypto::Cipher::Intent::Decryption); + + auto unwrap_result = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"_b; + auto in = "\x96\x77\x8B\x25\xAE\x6C\xA4\x35\xF9\x2B\x5B\x97\xC0\x50\xAE\xD2\x46\x8A\xB8\xA1\x7A\xD8\x4E\x5D"_b; + + auto unwrapped = MUST(ByteBuffer::create_zeroed(in.size() - 8)); + auto unwrapped_bytes = unwrapped.bytes(); + EXPECT_EQ(cipher.unwrap(in, unwrapped_bytes), Crypto::VerificationConsistency::Consistent); + EXPECT(memcmp(unwrapped_bytes.data(), unwrap_result.data(), unwrap_result.size()) == 0); +} + +TEST_CASE(test_AES_KW_encrypt_128bits_with_256bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"_b, 256, Crypto::Cipher::Intent::Encryption); + + auto wrap_result = "\x64\xE8\xC3\xF9\xCE\x0F\x5B\xA2\x63\xE9\x77\x79\x05\x81\x8A\x2A\x93\xC8\x19\x1E\x7D\x6E\x8A\xE7"_b; + auto in = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"_b; + + auto wrapped = MUST(ByteBuffer::create_zeroed(in.size() + 8)); + auto wrapped_bytes = wrapped.bytes(); + cipher.wrap(in, wrapped_bytes); + EXPECT(memcmp(wrapped_bytes.data(), wrap_result.data(), wrapped_bytes.size()) == 0); +} + +TEST_CASE(test_AES_KW_decrypt_128bits_with_256bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"_b, 256, Crypto::Cipher::Intent::Decryption); + + auto unwrap_result = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"_b; + auto in = "\x64\xE8\xC3\xF9\xCE\x0F\x5B\xA2\x63\xE9\x77\x79\x05\x81\x8A\x2A\x93\xC8\x19\x1E\x7D\x6E\x8A\xE7"_b; + + auto unwrapped = MUST(ByteBuffer::create_zeroed(in.size() - 8)); + auto unwrapped_bytes = unwrapped.bytes(); + EXPECT_EQ(cipher.unwrap(in, unwrapped_bytes), Crypto::VerificationConsistency::Consistent); + EXPECT(memcmp(unwrapped_bytes.data(), unwrap_result.data(), unwrap_result.size()) == 0); +} + +TEST_CASE(test_AES_KW_encrypt_192bits_with_192bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17"_b, 192, Crypto::Cipher::Intent::Encryption); + + auto wrap_result = "\x03\x1D\x33\x26\x4E\x15\xD3\x32\x68\xF2\x4E\xC2\x60\x74\x3E\xDC\xE1\xC6\xC7\xDD\xEE\x72\x5A\x93\x6B\xA8\x14\x91\x5C\x67\x62\xD2"_b; + auto in = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF\x00\x01\x02\x03\x04\x05\x06\x07"_b; + + auto wrapped = MUST(ByteBuffer::create_zeroed(in.size() + 8)); + auto wrapped_bytes = wrapped.bytes(); + cipher.wrap(in, wrapped_bytes); + EXPECT(memcmp(wrapped_bytes.data(), wrap_result.data(), wrapped_bytes.size()) == 0); +} + +TEST_CASE(test_AES_KW_decrypt_192bits_with_192bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17"_b, 192, Crypto::Cipher::Intent::Decryption); + + auto unwrap_result = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF\x00\x01\x02\x03\x04\x05\x06\x07"_b; + auto in = "\x03\x1D\x33\x26\x4E\x15\xD3\x32\x68\xF2\x4E\xC2\x60\x74\x3E\xDC\xE1\xC6\xC7\xDD\xEE\x72\x5A\x93\x6B\xA8\x14\x91\x5C\x67\x62\xD2"_b; + + auto unwrapped = MUST(ByteBuffer::create_zeroed(in.size() - 8)); + auto unwrapped_bytes = unwrapped.bytes(); + EXPECT_EQ(cipher.unwrap(in, unwrapped_bytes), Crypto::VerificationConsistency::Consistent); + EXPECT(memcmp(unwrapped_bytes.data(), unwrap_result.data(), unwrap_result.size()) == 0); +} + +TEST_CASE(test_AES_KW_encrypt_192bits_with_256bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"_b, 256, Crypto::Cipher::Intent::Encryption); + + auto wrap_result = "\xA8\xF9\xBC\x16\x12\xC6\x8B\x3F\xF6\xE6\xF4\xFB\xE3\x0E\x71\xE4\x76\x9C\x8B\x80\xA3\x2C\xB8\x95\x8C\xD5\xD1\x7D\x6B\x25\x4D\xA1"_b; + auto in = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF\x00\x01\x02\x03\x04\x05\x06\x07"_b; + + auto wrapped = MUST(ByteBuffer::create_zeroed(in.size() + 8)); + auto wrapped_bytes = wrapped.bytes(); + cipher.wrap(in, wrapped_bytes); + EXPECT(memcmp(wrapped_bytes.data(), wrap_result.data(), wrapped_bytes.size()) == 0); +} + +TEST_CASE(test_AES_KW_decrypt_192bits_with_256bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"_b, 256, Crypto::Cipher::Intent::Decryption); + + auto unwrap_result = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF\x00\x01\x02\x03\x04\x05\x06\x07"_b; + auto in = "\xA8\xF9\xBC\x16\x12\xC6\x8B\x3F\xF6\xE6\xF4\xFB\xE3\x0E\x71\xE4\x76\x9C\x8B\x80\xA3\x2C\xB8\x95\x8C\xD5\xD1\x7D\x6B\x25\x4D\xA1"_b; + + auto unwrapped = MUST(ByteBuffer::create_zeroed(in.size() - 8)); + auto unwrapped_bytes = unwrapped.bytes(); + EXPECT_EQ(cipher.unwrap(in, unwrapped_bytes), Crypto::VerificationConsistency::Consistent); + EXPECT(memcmp(unwrapped_bytes.data(), unwrap_result.data(), unwrap_result.size()) == 0); +} + +TEST_CASE(test_AES_KW_encrypt_256bits_with_256bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"_b, 256, Crypto::Cipher::Intent::Encryption); + + auto wrap_result = "\x28\xC9\xF4\x04\xC4\xB8\x10\xF4\xCB\xCC\xB3\x5C\xFB\x87\xF8\x26\x3F\x57\x86\xE2\xD8\x0E\xD3\x26\xCB\xC7\xF0\xE7\x1A\x99\xF4\x3B\xFB\x98\x8B\x9B\x7A\x02\xDD\x21"_b; + auto in = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"_b; + + auto wrapped = MUST(ByteBuffer::create_zeroed(in.size() + 8)); + auto wrapped_bytes = wrapped.bytes(); + cipher.wrap(in, wrapped_bytes); + EXPECT(memcmp(wrapped_bytes.data(), wrap_result.data(), wrapped_bytes.size()) == 0); +} + +TEST_CASE(test_AES_KW_decrypt_256bits_with_256bit_key) +{ + Crypto::Cipher::AESCipher::KWMode cipher("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"_b, 256, Crypto::Cipher::Intent::Decryption); + + auto unwrap_result = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"_b; + auto in = "\x28\xC9\xF4\x04\xC4\xB8\x10\xF4\xCB\xCC\xB3\x5C\xFB\x87\xF8\x26\x3F\x57\x86\xE2\xD8\x0E\xD3\x26\xCB\xC7\xF0\xE7\x1A\x99\xF4\x3B\xFB\x98\x8B\x9B\x7A\x02\xDD\x21"_b; + + auto unwrapped = MUST(ByteBuffer::create_zeroed(in.size() - 8)); + auto unwrapped_bytes = unwrapped.bytes(); + EXPECT_EQ(cipher.unwrap(in, unwrapped_bytes), Crypto::VerificationConsistency::Consistent); + EXPECT(memcmp(unwrapped_bytes.data(), unwrap_result.data(), unwrap_result.size()) == 0); +}