commit 2f0b83a64d475a7c5572d6bc93c2b41b93dd4e2e
parent 277242cc9b4685b3520386b07478b287f362d2b7
Author: Nick Mathewson <nickm@torproject.org>
Date: Sun, 20 Apr 2025 18:17:22 -0400
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.)
Diffstat:
4 files changed, 198 insertions(+), 27 deletions(-)
diff --git 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
@@ -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
@@ -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 <b>len</b> bytes from <b>input</b>, storing the results in place.
* Uses the key in <b>cipher</b>, and advances the counter by <b>len</b> 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 <b>cipher</b> to the 16-bit big-endian value
- * in <b>iv</b>. */
-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
@@ -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
};