]> git.ipfire.org Git - thirdparty/tor.git/commitdiff
Support for counter mode with raw AES.
authorNick Mathewson <nickm@torproject.org>
Sun, 20 Apr 2025 22:17:22 +0000 (18:17 -0400)
committerNick Mathewson <nickm@torproject.org>
Wed, 21 May 2025 13:43:51 +0000 (09:43 -0400)
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.)

src/lib/crypt_ops/aes.h
src/lib/crypt_ops/aes_nss.c
src/lib/crypt_ops/aes_openssl.c
src/test/test_crypto.c

index e8a8f2cdd98c745f808cae9446012222b23f3bc8..f9e95ccd0d5898beb91a0c48fdc9ff5ae0992854 100644 (file)
@@ -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) */
index a8e99fcaf6edfeb11d2ef8e9592ced17f5892ce0..55ac5a2edfc777263a760e4aea3be937bb23ec48 100644 (file)
@@ -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);
+  }
+}
index f68d5e69b9f36b1aa1c0253a43c429fb3e49a947..f28b9211c2529f1cc87071542a0c9e646f249c4b 100644 (file)
@@ -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);
+}
index f20f1ae7476222dfd243bebbefc3ecd0647bc729..5b8d7f6db5717383fb77b984c8328ad8c04a02d9 100644 (file)
@@ -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
 };