From: Nick Mathewson Date: Sun, 20 Apr 2025 22:17:22 +0000 (-0400) Subject: Support for counter mode with raw AES. X-Git-Tag: tor-0.4.9.3-alpha~49^2~6 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=2f0b83a64d475a7c5572d6bc93c2b41b93dd4e2e;p=thirdparty%2Ftor.git Support for counter mode with raw AES. We'll want this for CGO because we want the ability to use the same AES key several times with multiple different IVs: neither OpenSSL's EVP interface nor NSS's PK11 API has a good interface to do that. (This is usually expressed in terms of "seeking" to a new position on the stream, but there isn't an API for that either.) --- diff --git a/src/lib/crypt_ops/aes.h b/src/lib/crypt_ops/aes.h index e8a8f2cdd9..f9e95ccd0d 100644 --- a/src/lib/crypt_ops/aes.h +++ b/src/lib/crypt_ops/aes.h @@ -15,6 +15,7 @@ #include "lib/cc/torint.h" #include "lib/malloc/malloc.h" +#include "lib/testsupport/testsupport.h" typedef struct aes_cnt_cipher_t aes_cnt_cipher_t; @@ -37,6 +38,27 @@ void aes_raw_free_(aes_raw_t *cipher); FREE_AND_NULL(aes_raw_t, aes_raw_free_, (cipher)) void aes_raw_encrypt(const aes_raw_t *cipher, uint8_t *block); void aes_raw_decrypt(const aes_raw_t *cipher, uint8_t *block); + +void aes_raw_counter_xor(const aes_raw_t *aes, + const uint8_t *iv, uint32_t iv_offset, + uint8_t *data, size_t n); +#endif + +#ifdef TOR_AES_PRIVATE +#include "lib/arch/bytes.h" + +/** Increment the big-endian 128-bit counter in 'iv' by 'offset'. */ +static inline void +aes_ctr_add_iv_offset(uint8_t *iv, uint32_t offset) +{ + + uint64_t h_hi = tor_ntohll(get_uint64(iv + 0)); + uint64_t h_lo = tor_ntohll(get_uint64(iv + 8)); + h_lo += offset; + h_hi += (h_lo < offset); + set_uint64(iv + 0, tor_htonll(h_hi)); + set_uint64(iv + 8, tor_htonll(h_lo)); +} #endif #endif /* !defined(TOR_AES_H) */ diff --git a/src/lib/crypt_ops/aes_nss.c b/src/lib/crypt_ops/aes_nss.c index a8e99fcaf6..55ac5a2edf 100644 --- a/src/lib/crypt_ops/aes_nss.c +++ b/src/lib/crypt_ops/aes_nss.c @@ -10,6 +10,7 @@ **/ #define USE_AES_RAW +#define TOR_AES_PRIVATE #include "orconfig.h" #include "lib/crypt_ops/aes.h" @@ -152,7 +153,6 @@ aes_raw_new(const uint8_t *key, int key_bits, bool encrypt) tor_assert(result); return (aes_raw_t *)result; } - void aes_raw_free_(aes_raw_t *cipher_) { @@ -177,3 +177,37 @@ aes_raw_decrypt(const aes_raw_t *cipher, uint8_t *block) /* This is the same function call for NSS. */ aes_raw_encrypt(cipher, block); } + +static inline void +xor_bytes(uint8_t *outp, const uint8_t *inp, size_t n) +{ + for (size_t i = 0; i < n; ++i) { + outp[i] ^= inp[i]; + } +} + +void +aes_raw_counter_xor(const aes_raw_t *cipher, + const uint8_t *iv, uint32_t iv_offset, + uint8_t *data, size_t n) +{ + uint8_t counter[16]; + uint8_t buf[16]; + + memcpy(counter, iv, 16); + aes_ctr_add_iv_offset(counter, iv_offset); + + while (n) { + memcpy(buf, counter, 16); + aes_raw_encrypt(cipher, buf); + if (n >= 16) { + xor_bytes(data, buf, 16); + n -= 16; + data += 16; + } else { + xor_bytes(data, buf, n); + break; + } + aes_ctr_add_iv_offset(counter, 1); + } +} diff --git a/src/lib/crypt_ops/aes_openssl.c b/src/lib/crypt_ops/aes_openssl.c index f68d5e69b9..f28b9211c2 100644 --- a/src/lib/crypt_ops/aes_openssl.c +++ b/src/lib/crypt_ops/aes_openssl.c @@ -10,6 +10,7 @@ **/ #define USE_AES_RAW +#define TOR_AES_PRIVATE #include "orconfig.h" #include "lib/crypt_ops/aes.h" @@ -90,6 +91,17 @@ ENABLE_GCC_WARNING("-Wredundant-decls") * make sure that we have a fixed version.) */ +/* Helper function to use EVP with openssl's counter-mode wrapper. */ +static void +evp_block128_fn(const uint8_t in[16], + uint8_t out[16], + const void *key) +{ + EVP_CIPHER_CTX *ctx = (void*)key; + int inl=16, outl=16; + EVP_EncryptUpdate(ctx, out, &outl, in, inl); +} + #ifdef USE_EVP_AES_CTR /* We don't actually define the struct here. */ @@ -342,17 +354,6 @@ aes_cipher_free_(aes_cnt_cipher_t *cipher) #define UPDATE_CTR_BUF(c, n) #endif /* defined(USING_COUNTER_VARS) */ -/* Helper function to use EVP with openssl's counter-mode wrapper. */ -static void -evp_block128_fn(const uint8_t in[16], - uint8_t out[16], - const void *key) -{ - EVP_CIPHER_CTX *ctx = (void*)key; - int inl=16, outl=16; - EVP_EncryptUpdate(ctx, out, &outl, in, inl); -} - /** Encrypt len bytes from input, storing the results in place. * Uses the key in cipher, and advances the counter by len bytes * as it encrypts. @@ -385,21 +386,6 @@ aes_crypt_inplace(aes_cnt_cipher_t *cipher, char *data, size_t len) } } -/** Reset the 128-bit counter of cipher to the 16-bit big-endian value - * in iv. */ -static void -aes_set_iv(aes_cnt_cipher_t *cipher, const uint8_t *iv) -{ -#ifdef USING_COUNTER_VARS - cipher->counter3 = tor_ntohl(get_uint32(iv)); - cipher->counter2 = tor_ntohl(get_uint32(iv+4)); - cipher->counter1 = tor_ntohl(get_uint32(iv+8)); - cipher->counter0 = tor_ntohl(get_uint32(iv+12)); -#endif /* defined(USING_COUNTER_VARS) */ - cipher->pos = 0; - memcpy(cipher->ctr_buf.buf, iv, 16); -} - #endif /* defined(USE_EVP_AES_CTR) */ /* ======== @@ -477,3 +463,30 @@ aes_raw_decrypt(const aes_raw_t *cipher, uint8_t *block) tor_assert(r == 1); tor_assert(outl == 16); } + +/** + * Use the AES encryption key AES in counter mode, + * starting at the position (iv + iv_offset)*16, + * to encrypt the 'n' bytes of data in 'data'. + * + * Unlike aes_crypt_inplace, this function can re-use the same key repeatedly + * with diferent IVs. + */ +void +aes_raw_counter_xor(const aes_raw_t *cipher, + const uint8_t *iv, uint32_t iv_offset, + uint8_t *data, size_t n) +{ + uint8_t counter[16]; + uint8_t buf[16]; + unsigned int pos = 0; + + memcpy(counter, iv, 16); + if (iv_offset) { + aes_ctr_add_iv_offset(counter, iv_offset); + } + + CRYPTO_ctr128_encrypt(data, data, n, + (EVP_CIPHER_CTX *)cipher, + counter, buf, &pos, evp_block128_fn); +} diff --git a/src/test/test_crypto.c b/src/test/test_crypto.c index f20f1ae747..5b8d7f6db5 100644 --- a/src/test/test_crypto.c +++ b/src/test/test_crypto.c @@ -7,6 +7,7 @@ #define CRYPTO_CURVE25519_PRIVATE #define CRYPTO_RAND_PRIVATE #define USE_AES_RAW +#define TOR_AES_PRIVATE #include "core/or/or.h" #include "test/test.h" #include "lib/crypt_ops/aes.h" @@ -3308,6 +3309,105 @@ test_crypto_aes_raw(void *arg) #undef T } +static void +test_crypto_aes_raw_ctr_equiv(void *arg) +{ + (void) arg; + size_t buflen = 65536; + uint8_t *buf = tor_malloc_zero(buflen); + aes_cnt_cipher_t *c = NULL; + aes_raw_t *c_raw = NULL; + + const uint8_t iv[16]; + const uint8_t key[16]; + + // Simple case, IV with zero offset. + for (int i = 0; i < 32; ++i) { + crypto_rand((char*)iv, sizeof(iv)); + crypto_rand((char*)key, sizeof(key)); + c = aes_new_cipher(key, iv, 128); + c_raw = aes_raw_new(key, 128, true); + + aes_crypt_inplace(c, (char*)buf, buflen); + aes_raw_counter_xor(c_raw, iv, 0, buf, buflen); + tt_assert(fast_mem_is_zero((char*)buf, buflen)); + + aes_cipher_free(c); + aes_raw_free(c_raw); + } + // Trickier case, IV with offset == 31. + for (int i = 0; i < 32; ++i) { + crypto_rand((char*)iv, sizeof(iv)); + crypto_rand((char*)key, sizeof(key)); + c = aes_new_cipher(key, iv, 128); + c_raw = aes_raw_new(key, 128, true); + + aes_crypt_inplace(c, (char*)buf, buflen); + size_t off = 31*16; + aes_raw_counter_xor(c_raw, iv, 31, buf + off, buflen - off); + tt_assert(fast_mem_is_zero((char*)buf + off, buflen - off)); + + aes_cipher_free(c); + aes_raw_free(c_raw); + } + + done: + aes_cipher_free(c); + aes_raw_free(c_raw); + tor_free(buf); +} + +/* Make sure that our IV addition code is correct. + * + * We test this function separately to make sure we handle corner cases well; + * the corner cases are rare enough that we shouldn't expect to see them in + * randomized testing. + */ +static void +test_crypto_aes_cnt_iv_manip(void *arg) +{ + (void)arg; + uint8_t buf[16]; + uint8_t expect[16]; + int n; +#define T(pre, off, post) STMT_BEGIN { \ + n = base16_decode((char*)buf, sizeof(buf), \ + (pre), strlen(pre)); \ + tt_int_op(n, OP_EQ, sizeof(buf)); \ + n = base16_decode((char*)expect, sizeof(expect), \ + (post), strlen(post)); \ + tt_int_op(n, OP_EQ, sizeof(expect)); \ + aes_ctr_add_iv_offset(buf, (off)); \ + tt_mem_op(buf, OP_EQ, expect, 16); \ + } STMT_END + + T("00000000000000000000000000000000", 0x4032, + "00000000000000000000000000004032"); + T("0000000000000000000000000000ffff", 0x4032, + "00000000000000000000000000014031"); + // We focus on "31" here because that's what CGO uses. + T("000000000000000000000000ffffffe0", 31, + "000000000000000000000000ffffffff"); + T("000000000000000000000000ffffffe1", 31, + "00000000000000000000000100000000"); + T("0000000100000000ffffffffffffffe0", 31, + "0000000100000000ffffffffffffffff"); + T("0000000100000000ffffffffffffffe1", 31, + "00000001000000010000000000000000"); + T("0000000ffffffffffffffffffffffff0", 31, + "0000001000000000000000000000000f"); + T("ffffffffffffffffffffffffffffffe0", 31, + "ffffffffffffffffffffffffffffffff"); + T("ffffffffffffffffffffffffffffffe1", 31, + "00000000000000000000000000000000"); + T("ffffffffffffffffffffffffffffffe8", 31, + "00000000000000000000000000000007"); + +#undef T + done: + ; +} + #ifndef COCCI #define CRYPTO_LEGACY(name) \ { #name, test_crypto_ ## name , 0, NULL, NULL } @@ -3377,5 +3477,7 @@ struct testcase_t crypto_tests[] = { { "failure_modes", test_crypto_failure_modes, TT_FORK, NULL, NULL }, { "polyval", test_crypto_polyval, 0, NULL, NULL }, { "aes_raw", test_crypto_aes_raw, 0, NULL, NULL }, + { "aes_raw_ctr_equiv", test_crypto_aes_raw_ctr_equiv, 0, NULL, NULL }, + { "aes_cnt_iv_manip", test_crypto_aes_cnt_iv_manip, 0, NULL, NULL }, END_OF_TESTCASES };