Line data Source code
1 : /**
2 : * @file test_srp_math_functional.c
3 : * @brief Real-crypto smoke test for the SRP math pieces used by
4 : * auth_2fa_check_password (P3-03).
5 : *
6 : * We don't have access to server-generated reference vectors, so instead
7 : * we verify the *properties* that must hold independent of the exact
8 : * numbers:
9 : * 1. mod_mul + mod_sub + mod_exp agree on a known-small identity.
10 : * 2. mod_exp outputs are always reduced to the modulus length.
11 : * 3. The full PH1/PH2 chain is deterministic for the same password
12 : * and salt pair.
13 : *
14 : * Known-answer for the full inputCheckPasswordSRP requires reproducing
15 : * the server's private ephemeral b, which we can't. Instead we land
16 : * just the SRP-adjacent primitive checks.
17 : */
18 :
19 : #include "test_helpers.h"
20 : #include "crypto.h"
21 :
22 : #include <stdint.h>
23 : #include <string.h>
24 :
25 : static const uint8_t PRIME_8[1] = { 0x0B }; /* 11 decimal */
26 :
27 : /* 2^5 mod 11 = 10 (=0x0a) */
28 2 : static void test_mod_exp_small_known(void) {
29 2 : uint8_t base = 2, exp = 5;
30 2 : uint8_t out[1] = {0};
31 2 : size_t out_len = sizeof(out);
32 2 : CryptoBnCtx *ctx = crypto_bn_ctx_new();
33 2 : int rc = crypto_bn_mod_exp(out, &out_len, &base, 1, &exp, 1,
34 : PRIME_8, 1, ctx);
35 2 : crypto_bn_ctx_free(ctx);
36 2 : ASSERT(rc == 0, "mod_exp returns ok");
37 2 : ASSERT(out_len == 1, "output padded to modulus length");
38 2 : ASSERT(out[0] == 10, "2^5 mod 11 == 10");
39 : }
40 :
41 : /* 7 * 8 mod 11 == 1 */
42 2 : static void test_mod_mul_small_known(void) {
43 2 : uint8_t a = 7, b = 8, out[1] = {0};
44 2 : size_t out_len = sizeof(out);
45 2 : CryptoBnCtx *ctx = crypto_bn_ctx_new();
46 2 : int rc = crypto_bn_mod_mul(out, &out_len, &a, 1, &b, 1,
47 : PRIME_8, 1, ctx);
48 2 : crypto_bn_ctx_free(ctx);
49 2 : ASSERT(rc == 0, "mod_mul ok");
50 2 : ASSERT(out[0] == 1, "7*8 mod 11 == 1");
51 : }
52 :
53 : /* (3 - 9) mod 11 = 5 (always non-negative) */
54 2 : static void test_mod_sub_small_known(void) {
55 2 : uint8_t a = 3, b = 9, out[1] = {0};
56 2 : size_t out_len = sizeof(out);
57 2 : CryptoBnCtx *ctx = crypto_bn_ctx_new();
58 2 : int rc = crypto_bn_mod_sub(out, &out_len, &a, 1, &b, 1,
59 : PRIME_8, 1, ctx);
60 2 : crypto_bn_ctx_free(ctx);
61 2 : ASSERT(rc == 0, "mod_sub ok");
62 2 : ASSERT(out[0] == 5, "(3 - 9) mod 11 == 5");
63 : }
64 :
65 : /* Modular reduction fills to the modulus byte count even for small values. */
66 2 : static void test_mod_exp_pads_output(void) {
67 2 : uint8_t base = 2, exp = 1;
68 2 : uint8_t prime[32] = {0};
69 2 : prime[31] = 0x0B;
70 2 : uint8_t out[32] = {0};
71 2 : size_t out_len = sizeof(out);
72 2 : CryptoBnCtx *ctx = crypto_bn_ctx_new();
73 2 : int rc = crypto_bn_mod_exp(out, &out_len, &base, 1, &exp, 1,
74 : prime, sizeof(prime), ctx);
75 2 : crypto_bn_ctx_free(ctx);
76 2 : ASSERT(rc == 0, "mod_exp padded ok");
77 2 : ASSERT(out_len == 32, "full modulus width returned");
78 64 : for (int i = 0; i < 31; i++) ASSERT(out[i] == 0, "leading zero pad");
79 2 : ASSERT(out[31] == 2, "last byte carries the small result");
80 : }
81 :
82 : /* ucmp semantics */
83 2 : static void test_ucmp(void) {
84 2 : uint8_t a[2] = { 0x01, 0x00 };
85 2 : uint8_t b[2] = { 0x00, 0xFF };
86 2 : ASSERT(crypto_bn_ucmp(a, 2, b, 2) == 1, "a > b");
87 2 : ASSERT(crypto_bn_ucmp(b, 2, a, 2) == -1, "b < a");
88 2 : ASSERT(crypto_bn_ucmp(a, 2, a, 2) == 0, "eq");
89 : }
90 :
91 : /* PBKDF2 driven by the same salts / password must be deterministic. */
92 2 : static void test_srp_x_deterministic(void) {
93 2 : const uint8_t salt1[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
94 2 : const uint8_t salt2[16] = {16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1};
95 : uint8_t a[32], b[32];
96 :
97 : /* Simulate PH2 core: pbkdf2(password, salt1, 100000, 64) folded
98 : * through an outer SHA-256 salted by salt2. Deterministic across
99 : * runs given the same inputs. */
100 : uint8_t pbkdf[64];
101 2 : int rc = crypto_pbkdf2_hmac_sha512((const uint8_t *)"hunter2", 7,
102 : salt1, sizeof(salt1),
103 : 100, pbkdf, sizeof(pbkdf));
104 2 : ASSERT(rc == 0, "pbkdf ok");
105 :
106 : uint8_t buf[16 + 64 + 16];
107 2 : memcpy(buf, salt2, 16);
108 2 : memcpy(buf + 16, pbkdf, 64);
109 2 : memcpy(buf + 16 + 64, salt2, 16);
110 2 : crypto_sha256(buf, sizeof(buf), a);
111 :
112 : /* Call again — same inputs must yield same hash. */
113 2 : rc = crypto_pbkdf2_hmac_sha512((const uint8_t *)"hunter2", 7,
114 : salt1, sizeof(salt1),
115 : 100, pbkdf, sizeof(pbkdf));
116 2 : ASSERT(rc == 0, "pbkdf ok (2nd)");
117 2 : memcpy(buf, salt2, 16);
118 2 : memcpy(buf + 16, pbkdf, 64);
119 2 : memcpy(buf + 16 + 64, salt2, 16);
120 2 : crypto_sha256(buf, sizeof(buf), b);
121 :
122 2 : ASSERT(memcmp(a, b, 32) == 0, "PH2-like chain is deterministic");
123 : }
124 :
125 2 : void run_srp_math_functional_tests(void) {
126 2 : RUN_TEST(test_mod_exp_small_known);
127 2 : RUN_TEST(test_mod_mul_small_known);
128 2 : RUN_TEST(test_mod_sub_small_known);
129 2 : RUN_TEST(test_mod_exp_pads_output);
130 2 : RUN_TEST(test_ucmp);
131 2 : RUN_TEST(test_srp_x_deterministic);
132 2 : }
|