diff --git a/Libraries/LibCrypto/Authentication/HMAC.h b/Libraries/LibCrypto/Authentication/HMAC.h new file mode 100644 index 0000000000..acf906f25b --- /dev/null +++ b/Libraries/LibCrypto/Authentication/HMAC.h @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +constexpr static auto IPAD = 0x36; +constexpr static auto OPAD = 0x5c; + +namespace Crypto { +namespace Authentication { + template + class HMAC { + public: + using HashType = HashT; + using TagType = typename HashType::DigestType; + static constexpr size_t BlockSize = HashType::BlockSize; + static constexpr size_t DigestSize = HashType::DigestSize; + + template + HMAC(KeyBufferType key, Args... args) + : m_inner_hasher(args...) + , m_outer_hasher(args...) + { + derive_key(key); + reset(); + } + + TagType process(const u8* message, size_t length) + { + reset(); + update(message, length); + return digest(); + } + + void update(const u8* message, size_t length) + { + m_inner_hasher.update(message, length); + } + + TagType process(const ByteBuffer& buffer) { return process(buffer.data(), buffer.size()); } + TagType process(const StringView& string) { return process((const u8*)string.characters_without_null_termination(), string.length()); } + void update(const ByteBuffer& buffer) { return update(buffer.data(), buffer.size()); } + void update(const StringView& string) { return update((const u8*)string.characters_without_null_termination(), string.length()); } + + TagType digest() + { + m_outer_hasher.update(m_inner_hasher.digest().data, m_inner_hasher.DigestSize); + auto result = m_outer_hasher.digest(); + reset(); + return result; + } + + void reset() + { + m_inner_hasher.reset(); + m_outer_hasher.reset(); + m_inner_hasher.update(m_key_data, BlockSize); + m_outer_hasher.update(m_key_data + BlockSize, BlockSize); + } + + String class_name() const + { + StringBuilder builder; + builder.append("HMAC-"); + builder.append(m_inner_hasher.class_name()); + return builder.build(); + } + + private: + void derive_key(const u8* key, size_t length) + { + u8 v_key[BlockSize]; + __builtin_memset(v_key, 0, BlockSize); + ByteBuffer key_buffer = ByteBuffer::wrap(v_key, BlockSize); + // m_key_data is zero'd, so copying the data in + // the first few bytes leaves the rest zero, which + // is exactly what we want (zero padding) + if (length > BlockSize) { + m_inner_hasher.update(key, length); + auto digest = m_inner_hasher.digest(); + // FIXME: should we check if the hash function creates more data than its block size? + key_buffer.overwrite(0, digest.data, sizeof(TagType)); + } else { + key_buffer.overwrite(0, key, length); + } + + // fill out the inner and outer padded keys + auto* i_key = m_key_data; + auto* o_key = m_key_data + BlockSize; + for (size_t i = 0; i < BlockSize; ++i) { + auto key_byte = key_buffer[i]; + i_key[i] = key_byte ^ IPAD; + o_key[i] = key_byte ^ OPAD; + } + } + + void derive_key(const ByteBuffer& key) { derive_key(key.data(), key.size()); } + void derive_key(const StringView& key) { derive_key((const u8*)key.characters_without_null_termination(), key.length()); } + + HashType m_inner_hasher, m_outer_hasher; + u8 m_key_data[BlockSize * 2]; + }; +} +} diff --git a/Libraries/LibCrypto/Cipher/AES.h b/Libraries/LibCrypto/Cipher/AES.h index 19d6caac09..67d9cb2715 100644 --- a/Libraries/LibCrypto/Cipher/AES.h +++ b/Libraries/LibCrypto/Cipher/AES.h @@ -120,6 +120,8 @@ namespace Cipher { virtual void encrypt_block(const BlockType& in, BlockType& out) override; virtual void decrypt_block(const BlockType& in, BlockType& out) override; + virtual String class_name() const override { return "AES"; } + protected: AESCipherKey m_key; }; diff --git a/Libraries/LibCrypto/Cipher/Cipher.h b/Libraries/LibCrypto/Cipher/Cipher.h index 8ef496d589..873f419709 100644 --- a/Libraries/LibCrypto/Cipher/Cipher.h +++ b/Libraries/LibCrypto/Cipher/Cipher.h @@ -129,6 +129,8 @@ namespace Cipher { virtual void encrypt_block(const BlockType& in, BlockType& out) = 0; virtual void decrypt_block(const BlockType& in, BlockType& out) = 0; + virtual String class_name() const = 0; + private: PaddingMode m_padding_mode; }; diff --git a/Libraries/LibCrypto/Cipher/Mode/CBC.h b/Libraries/LibCrypto/Cipher/Mode/CBC.h index 2a93a18b69..32e9834d24 100644 --- a/Libraries/LibCrypto/Cipher/Mode/CBC.h +++ b/Libraries/LibCrypto/Cipher/Mode/CBC.h @@ -26,6 +26,8 @@ #pragma once +#include +#include #include namespace Crypto { @@ -40,6 +42,14 @@ namespace Cipher { { } + virtual String class_name() const override + { + StringBuilder builder; + builder.append(this->cipher().class_name()); + builder.append("_CBC"); + return builder.build(); + } + virtual Optional encrypt(const ByteBuffer& in, ByteBuffer& out, Optional ivec = {}) override { auto length = in.size(); diff --git a/Libraries/LibCrypto/Cipher/Mode/Mode.h b/Libraries/LibCrypto/Cipher/Mode/Mode.h index 49257b30d1..e2d78fc578 100644 --- a/Libraries/LibCrypto/Cipher/Mode/Mode.h +++ b/Libraries/LibCrypto/Cipher/Mode/Mode.h @@ -50,6 +50,8 @@ namespace Cipher { return ByteBuffer::create_uninitialized(input_size + T::block_size() - remainder); } + virtual String class_name() const = 0; + protected: T& cipher() { return m_cipher; } diff --git a/Libraries/LibCrypto/Hash/HashFunction.h b/Libraries/LibCrypto/Hash/HashFunction.h index a7e3a8ae25..bdb132543c 100644 --- a/Libraries/LibCrypto/Hash/HashFunction.h +++ b/Libraries/LibCrypto/Hash/HashFunction.h @@ -49,6 +49,7 @@ namespace Hash { virtual void update(const StringView& string) = 0; virtual DigestType digest() = 0; + virtual String class_name() const = 0; }; } } diff --git a/Libraries/LibCrypto/Hash/MD5.h b/Libraries/LibCrypto/Hash/MD5.h index 89da2629dd..61700c75ad 100644 --- a/Libraries/LibCrypto/Hash/MD5.h +++ b/Libraries/LibCrypto/Hash/MD5.h @@ -26,6 +26,7 @@ #pragma once +#include #include #include @@ -79,6 +80,8 @@ namespace Hash { virtual void update(const StringView& string) override { update((const u8*)string.characters_without_null_termination(), string.length()); }; virtual DigestType digest() override; + virtual String class_name() const override { return "MD5"; } + inline static DigestType hash(const u8* data, size_t length) { MD5 md5; diff --git a/Userland/test-crypto.cpp b/Userland/test-crypto.cpp index dfd0d0ec29..5c5ce75603 100644 --- a/Userland/test-crypto.cpp +++ b/Userland/test-crypto.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -15,6 +16,7 @@ static bool run_tests = false; static bool encrypting = true; +constexpr const char* DEFAULT_DIGEST_SUITE { "HMAC-MD5" }; constexpr const char* DEFAULT_HASH_SUITE { "MD5" }; constexpr const char* DEFAULT_CIPHER_SUITE { "AES_CBC" }; @@ -25,6 +27,9 @@ int aes_cbc_tests(); // Hash int md5_tests(); +// Authentication +int hmac_md5_tests(); + // stop listing tests void print_buffer(const ByteBuffer& buffer, int split) @@ -113,7 +118,7 @@ auto main(int argc, char** argv) -> int Core::ArgsParser parser; parser.add_positional_argument(mode, "mode to operate in ('list' to see modes and descriptions)", "mode"); - parser.add_option(secret_key, "Set the secret key", "secret-key", 'k', "secret key"); + parser.add_option(secret_key, "Set the secret key (default key is 'WellHelloFriends')", "secret-key", 'k', "secret key"); parser.add_option(key_bits, "Size of the key", "key-bits", 'b', "key-bits"); parser.add_option(filename, "Read from file", "file", 'f', "from file"); parser.add_option(binary, "Force binary output", "force-binary", 0); @@ -124,7 +129,8 @@ auto main(int argc, char** argv) -> int StringView mode_sv { mode }; if (mode_sv == "list") { - puts("Crypt modes"); + puts("test-crypto modes"); + puts("\tdigest - Access digest (authentication) functions"); puts("\thash - Access hash functions"); puts("\tencrypt -- Access encryption functions"); puts("\tdecrypt -- Access decryption functions"); @@ -144,6 +150,19 @@ auto main(int argc, char** argv) -> int return 1; } } + if (mode_sv == "digest") { + if (suite == nullptr) + suite = DEFAULT_DIGEST_SUITE; + + if (StringView(suite) == "HMAC-MD5") { + if (run_tests) + return hmac_md5_tests(); + return run(hmac_md5); + } else { + printf("unknown hash function '%s'\n", suite); + return 1; + } + } encrypting = mode_sv == "encrypt"; if (encrypting || mode_sv == "decrypt") { if (suite == nullptr) @@ -187,14 +206,20 @@ ByteBuffer operator""_b(const char* string, size_t length) // tests go after here // please be reasonable with orders kthx +void aes_cbc_test_name(); void aes_cbc_test_encrypt(); void aes_cbc_test_decrypt(); +void md5_test_name(); void md5_test_hash(); void md5_test_consecutive_updates(); +void hmac_md5_test_name(); +void hmac_md5_test_process(); + int aes_cbc_tests() { + aes_cbc_test_name(); if (encrypting) { aes_cbc_test_encrypt(); } else { @@ -204,6 +229,16 @@ int aes_cbc_tests() return 0; } +void aes_cbc_test_name() +{ + I_TEST((AES CBC class name)); + Crypto::Cipher::AESCipher::CBCMode cipher("WellHelloFriends", 128, Crypto::Cipher::Intent::Encryption); + if (cipher.class_name() != "AES_CBC") + FAIL(Invalid class name); + else + PASS; +} + void aes_cbc_test_encrypt() { auto test_it = [](auto& cipher, auto& result) { @@ -309,11 +344,22 @@ void aes_cbc_test_decrypt() int md5_tests() { + md5_test_name(); md5_test_hash(); md5_test_consecutive_updates(); return 0; } +void md5_test_name() +{ + I_TEST((MD5 class name)); + Crypto::Hash::MD5 md5; + if (md5.class_name() != "MD5") + FAIL(Invalid class name); + else + PASS; +} + void md5_test_hash() { { @@ -422,9 +468,55 @@ void md5_test_consecutive_updates() md5.update("friends"); auto digest1 = md5.digest(); - if (memcmp(digest0.data, digest1.data, Crypto::Hash::MD5::block_size()) != 0) + if (memcmp(digest0.data, digest1.data, Crypto::Hash::MD5::digest_size()) != 0) FAIL(Cannot reuse); else PASS; } } + +int hmac_md5_tests() +{ + hmac_md5_test_name(); + hmac_md5_test_process(); + return 0; +} + +void hmac_md5_test_name() +{ + I_TEST((HMAC - MD5 | Class name)); + Crypto::Authentication::HMAC hmac("Well Hello Friends"); + if (hmac.class_name() != "HMAC-MD5") + FAIL(Invalid class name); + else + PASS; +} + +void hmac_md5_test_process() +{ + { + I_TEST((HMAC - MD5 | Basic)); + Crypto::Authentication::HMAC hmac("Well Hello Friends"); + u8 result[] { + 0x3b, 0x5b, 0xde, 0x30, 0x3a, 0x54, 0x7b, 0xbb, 0x09, 0xfe, 0x78, 0x89, 0xbc, 0x9f, 0x22, 0xa3 + }; + auto mac = hmac.process("Some bogus data"); + if (memcmp(result, mac.data, hmac.DigestSize) != 0) { + FAIL(Invalid mac); + print_buffer(ByteBuffer::wrap(mac.data, hmac.DigestSize), -1); + } else + PASS; + } + { + I_TEST((HMAC - MD5 | Reuse)); + Crypto::Authentication::HMAC hmac("Well Hello Friends"); + + auto mac_0 = hmac.process("Some bogus data"); + auto mac_1 = hmac.process("Some bogus data"); + + if (memcmp(mac_0.data, mac_1.data, hmac.DigestSize) != 0) { + FAIL(Cannot reuse); + } else + PASS; + } +}