qr_reader-native 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-mruby.txt +22 -0
  3. data/LICENSE-spinel.txt +19 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +61 -0
  6. data/ext/qr_reader_native/SPINEL_VERSION +1 -0
  7. data/ext/qr_reader_native/extconf.rb +8 -0
  8. data/ext/qr_reader_native/mruby/error.h +13 -0
  9. data/ext/qr_reader_native/mruby/internal.h +145 -0
  10. data/ext/qr_reader_native/mruby/string.h +22 -0
  11. data/ext/qr_reader_native/mruby.h +93 -0
  12. data/ext/qr_reader_native/mruby_shim.h +118 -0
  13. data/ext/qr_reader_native/qr_reader_native.c +13513 -0
  14. data/ext/qr_reader_native/qr_reader_native.h +29 -0
  15. data/ext/qr_reader_native/qr_reader_native_ext.c +598 -0
  16. data/ext/qr_reader_native/re_cased.h +71 -0
  17. data/ext/qr_reader_native/re_compile.c +5784 -0
  18. data/ext/qr_reader_native/re_ctype.h +3540 -0
  19. data/ext/qr_reader_native/re_exec.c +2190 -0
  20. data/ext/qr_reader_native/re_internal.h +671 -0
  21. data/ext/qr_reader_native/re_prop.h +4629 -0
  22. data/ext/qr_reader_native/re_spinel.c +363 -0
  23. data/ext/qr_reader_native/re_spinel.h +22 -0
  24. data/ext/qr_reader_native/re_spinel_utf8.inc +97 -0
  25. data/ext/qr_reader_native/re_utf8.c +156 -0
  26. data/ext/qr_reader_native/sp_alloc.c +1447 -0
  27. data/ext/qr_reader_native/sp_alloc.h +1196 -0
  28. data/ext/qr_reader_native/sp_argf.h +33 -0
  29. data/ext/qr_reader_native/sp_array.c +1052 -0
  30. data/ext/qr_reader_native/sp_array.h +456 -0
  31. data/ext/qr_reader_native/sp_bigint.c +5826 -0
  32. data/ext/qr_reader_native/sp_bigint.h +117 -0
  33. data/ext/qr_reader_native/sp_cold.c +5259 -0
  34. data/ext/qr_reader_native/sp_compat.h +402 -0
  35. data/ext/qr_reader_native/sp_core.c +818 -0
  36. data/ext/qr_reader_native/sp_core.h +56 -0
  37. data/ext/qr_reader_native/sp_crypto.c +877 -0
  38. data/ext/qr_reader_native/sp_crypto.h +169 -0
  39. data/ext/qr_reader_native/sp_dtoa.c +1533 -0
  40. data/ext/qr_reader_native/sp_dtoa.h +29 -0
  41. data/ext/qr_reader_native/sp_encoding_names.h +172 -0
  42. data/ext/qr_reader_native/sp_enum.h +86 -0
  43. data/ext/qr_reader_native/sp_exc.c +1185 -0
  44. data/ext/qr_reader_native/sp_exc.h +186 -0
  45. data/ext/qr_reader_native/sp_exc_ctx.h +56 -0
  46. data/ext/qr_reader_native/sp_fiber.c +889 -0
  47. data/ext/qr_reader_native/sp_fiber.h +162 -0
  48. data/ext/qr_reader_native/sp_fiber_ctx.h +84 -0
  49. data/ext/qr_reader_native/sp_format.c +695 -0
  50. data/ext/qr_reader_native/sp_format.h +83 -0
  51. data/ext/qr_reader_native/sp_gc.c +1793 -0
  52. data/ext/qr_reader_native/sp_gc.h +762 -0
  53. data/ext/qr_reader_native/sp_hash.c +131 -0
  54. data/ext/qr_reader_native/sp_hash.h +131 -0
  55. data/ext/qr_reader_native/sp_inspect.c +215 -0
  56. data/ext/qr_reader_native/sp_inspect.h +128 -0
  57. data/ext/qr_reader_native/sp_io.c +1676 -0
  58. data/ext/qr_reader_native/sp_io.h +272 -0
  59. data/ext/qr_reader_native/sp_iobuffer.c +1014 -0
  60. data/ext/qr_reader_native/sp_iobuffer.h +167 -0
  61. data/ext/qr_reader_native/sp_marshal.c +487 -0
  62. data/ext/qr_reader_native/sp_marshal.h +60 -0
  63. data/ext/qr_reader_native/sp_net.c +1029 -0
  64. data/ext/qr_reader_native/sp_net.h +205 -0
  65. data/ext/qr_reader_native/sp_pack.c +1254 -0
  66. data/ext/qr_reader_native/sp_poly_cold.c +1220 -0
  67. data/ext/qr_reader_native/sp_proc.c +410 -0
  68. data/ext/qr_reader_native/sp_proc.h +327 -0
  69. data/ext/qr_reader_native/sp_process.c +547 -0
  70. data/ext/qr_reader_native/sp_process_status.c +211 -0
  71. data/ext/qr_reader_native/sp_process_status.h +66 -0
  72. data/ext/qr_reader_native/sp_random.c +287 -0
  73. data/ext/qr_reader_native/sp_random.h +52 -0
  74. data/ext/qr_reader_native/sp_range.h +144 -0
  75. data/ext/qr_reader_native/sp_re.c +1379 -0
  76. data/ext/qr_reader_native/sp_re.h +186 -0
  77. data/ext/qr_reader_native/sp_re_names.h +59 -0
  78. data/ext/qr_reader_native/sp_sched.c +4044 -0
  79. data/ext/qr_reader_native/sp_sched.h +295 -0
  80. data/ext/qr_reader_native/sp_slab.c +1507 -0
  81. data/ext/qr_reader_native/sp_str.c +1462 -0
  82. data/ext/qr_reader_native/sp_str.h +306 -0
  83. data/ext/qr_reader_native/sp_str_crypt.c +23 -0
  84. data/ext/qr_reader_native/sp_string.c +92 -0
  85. data/ext/qr_reader_native/sp_string.h +253 -0
  86. data/ext/qr_reader_native/sp_system.c +78 -0
  87. data/ext/qr_reader_native/sp_system.h +18 -0
  88. data/ext/qr_reader_native/sp_time.c +860 -0
  89. data/ext/qr_reader_native/sp_time.h +100 -0
  90. data/ext/qr_reader_native/sp_types.h +292 -0
  91. data/ext/qr_reader_native/spinel_rt.h +19017 -0
  92. data/ext/qr_reader_native/unicase.c +399 -0
  93. data/ext/qr_reader_native/unicase.h +901 -0
  94. data/lib/qr_reader/native.rb +6 -0
  95. metadata +153 -0
@@ -0,0 +1,877 @@
1
+ /* sp_crypto.c -- SHA-256 / HMAC-SHA256 / PBKDF2 / Base64URL / CSPRNG.
2
+ *
3
+ * See sp_crypto.h for the public surface. Pure C, no spinel-runtime
4
+ * dependency -- archived into libspinel_rt.a alongside sp_bigint.o
5
+ * so spinel programs can ffi_func against it (or, longer term, have
6
+ * codegen pick up `Digest::SHA256.hexdigest`-shaped Ruby calls and
7
+ * lower them to these primitives directly).
8
+ *
9
+ * Implementations
10
+ * ---------------
11
+ * - SHA-256: compact public-domain implementation; the canonical
12
+ * FIPS-180-4 reference (Wikipedia pseudocode + Go's crypto/sha256
13
+ * match this byte-for-byte on the standard test vectors).
14
+ * - HMAC-SHA256: RFC 2104 / RFC 4231 on top of the SHA-256 above.
15
+ * - PBKDF2-HMAC-SHA256: RFC 8018, dkLen fixed at 32 (one block).
16
+ * - Base64URL: RFC 4648 §5 (URL/filename-safe alphabet), no padding.
17
+ * - CSPRNG: arc4random_buf on BSD/macOS; getrandom(2) then
18
+ * /dev/urandom on Linux, failing closed (NULL) with no weak fallback.
19
+ *
20
+ * State buffers
21
+ * -------------
22
+ * Each public function owns a per-function SP_TLS return buffer.
23
+ * The next call to the SAME function on the SAME thread clobbers it.
24
+ * Distinct functions use distinct buffers, so chained calls
25
+ * (b64url_encode then hex) don't trample each other, and distinct
26
+ * threads never share one -- the call site copies out of the buffer
27
+ * after the call has returned, and that window is long enough for
28
+ * another thread to enter the same function and overwrite it.
29
+ */
30
+ #include "sp_crypto.h"
31
+ #include "sp_alloc.h" /* sp_str_byte_len: hash/HMAC/PBKDF2 inputs are spinel
32
+ Strings that can carry embedded NULs (binary keys,
33
+ salts, digests), so use the header byte length rather
34
+ than strlen, which truncates at the first NUL (#1779). */
35
+
36
+ #include <stdio.h>
37
+ #include <stdlib.h>
38
+ #include <string.h>
39
+ #include <stdint.h>
40
+ #include <time.h>
41
+ #if defined(__linux__)
42
+ #include <unistd.h> /* syscall */
43
+ #include <sys/syscall.h> /* SYS_getrandom (the CSPRNG's first choice) */
44
+ #endif
45
+ #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
46
+ # include <stdlib.h> /* arc4random_buf */
47
+ #endif
48
+
49
+ /* ---------- SHA-256 ----------
50
+ * FIPS-180-4. Compact reference implementation -- public domain.
51
+ */
52
+
53
+ static const uint32_t sp_crypto_sha256_k[64] = {
54
+ 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
55
+ 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
56
+ 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
57
+ 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
58
+ 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
59
+ 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
60
+ 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
61
+ 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
62
+ };
63
+
64
+ #define SPC_ROTR(x,n) (((x) >> (n)) | ((x) << (32 - (n))))
65
+ #define SPC_S0(x) (SPC_ROTR(x, 2) ^ SPC_ROTR(x,13) ^ SPC_ROTR(x,22))
66
+ #define SPC_S1(x) (SPC_ROTR(x, 6) ^ SPC_ROTR(x,11) ^ SPC_ROTR(x,25))
67
+ #define SPC_s0(x) (SPC_ROTR(x, 7) ^ SPC_ROTR(x,18) ^ ((x) >> 3))
68
+ #define SPC_s1(x) (SPC_ROTR(x,17) ^ SPC_ROTR(x,19) ^ ((x) >> 10))
69
+ #define SPC_CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
70
+ #define SPC_MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
71
+
72
+ static void sp_crypto_sha256_block(uint32_t H[8], const uint8_t b[64]) {
73
+ uint32_t w[64], sa, sb, sc, sd, se, sf, sg, sh, t1, t2;
74
+ int i;
75
+ for (i = 0; i < 16; i++) {
76
+ w[i] = ((uint32_t)b[i*4] << 24) | ((uint32_t)b[i*4+1] << 16) |
77
+ ((uint32_t)b[i*4+2] << 8) | (uint32_t)b[i*4+3];
78
+ }
79
+ for (i = 16; i < 64; i++) {
80
+ w[i] = SPC_s1(w[i-2]) + w[i-7] + SPC_s0(w[i-15]) + w[i-16];
81
+ }
82
+ sa=H[0]; sb=H[1]; sc=H[2]; sd=H[3]; se=H[4]; sf=H[5]; sg=H[6]; sh=H[7];
83
+ for (i = 0; i < 64; i++) {
84
+ t1 = sh + SPC_S1(se) + SPC_CH(se,sf,sg) + sp_crypto_sha256_k[i] + w[i];
85
+ t2 = SPC_S0(sa) + SPC_MAJ(sa,sb,sc);
86
+ sh = sg; sg = sf; sf = se; se = sd + t1;
87
+ sd = sc; sc = sb; sb = sa; sa = t1 + t2;
88
+ }
89
+ H[0]+=sa; H[1]+=sb; H[2]+=sc; H[3]+=sd;
90
+ H[4]+=se; H[5]+=sf; H[6]+=sg; H[7]+=sh;
91
+ }
92
+
93
+ static void sp_crypto_store_be32(uint8_t *out, const uint32_t *H, int n) {
94
+ for (int i = 0; i < n; i++) {
95
+ out[i*4] = (uint8_t)(H[i] >> 24);
96
+ out[i*4+1] = (uint8_t)(H[i] >> 16);
97
+ out[i*4+2] = (uint8_t)(H[i] >> 8);
98
+ out[i*4+3] = (uint8_t)(H[i]);
99
+ }
100
+ }
101
+
102
+ static void sp_crypto_sha256(const uint8_t *msg, size_t len, uint8_t out[32]) {
103
+ uint32_t H[8] = {
104
+ 0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,
105
+ 0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19
106
+ };
107
+ uint8_t buf[64];
108
+ size_t i, full = len & ~((size_t)63);
109
+ for (i = 0; i < full; i += 64) sp_crypto_sha256_block(H, msg + i);
110
+ size_t rem = len - full;
111
+ for (i = 0; i < rem; i++) buf[i] = msg[full + i];
112
+ buf[rem] = 0x80;
113
+ if (rem >= 56) {
114
+ for (i = rem + 1; i < 64; i++) buf[i] = 0;
115
+ sp_crypto_sha256_block(H, buf);
116
+ for (i = 0; i < 56; i++) buf[i] = 0;
117
+ }
118
+ else {
119
+ for (i = rem + 1; i < 56; i++) buf[i] = 0;
120
+ }
121
+ uint64_t bits = (uint64_t)len * 8;
122
+ for (i = 0; i < 8; i++) buf[56 + i] = (uint8_t)(bits >> (56 - 8*i));
123
+ sp_crypto_sha256_block(H, buf);
124
+ sp_crypto_store_be32(out, H, 8);
125
+ }
126
+
127
+ /* ---------- SHA-1 ----------
128
+ * FIPS-180-4 (legacy hash, kept for WebSocket handshake -- RFC 6455
129
+ * requires SHA-1 specifically for Sec-WebSocket-Accept). Do not use
130
+ * for new security designs; SHA-256 is the right primitive for those.
131
+ * Compact reference implementation -- public domain.
132
+ */
133
+
134
+ static void sp_crypto_sha1_block(uint32_t H[5], const uint8_t b[64]) {
135
+ uint32_t w[80], a, sa, sb, sc, sd, se, f, k;
136
+ int i;
137
+ for (i = 0; i < 16; i++) {
138
+ w[i] = ((uint32_t)b[i*4] << 24) | ((uint32_t)b[i*4+1] << 16) |
139
+ ((uint32_t)b[i*4+2] << 8) | (uint32_t)b[i*4+3];
140
+ }
141
+ for (i = 16; i < 80; i++) {
142
+ a = w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16];
143
+ w[i] = (a << 1) | (a >> 31);
144
+ }
145
+ sa = H[0]; sb = H[1]; sc = H[2]; sd = H[3]; se = H[4];
146
+ for (i = 0; i < 80; i++) {
147
+ if (i < 20) { f = (sb & sc) | (~sb & sd); k = 0x5A827999; }
148
+ else if (i < 40) { f = sb ^ sc ^ sd; k = 0x6ED9EBA1; }
149
+ else if (i < 60) { f = (sb & sc) | (sb & sd) | (sc & sd); k = 0x8F1BBCDC; }
150
+ else { f = sb ^ sc ^ sd; k = 0xCA62C1D6; }
151
+ uint32_t t = ((sa << 5) | (sa >> 27)) + f + se + k + w[i];
152
+ se = sd; sd = sc; sc = (sb << 30) | (sb >> 2); sb = sa; sa = t;
153
+ }
154
+ H[0] += sa; H[1] += sb; H[2] += sc; H[3] += sd; H[4] += se;
155
+ }
156
+
157
+ static void sp_crypto_sha1(const uint8_t *msg, size_t len, uint8_t out[20]) {
158
+ uint32_t H[5] = {
159
+ 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0
160
+ };
161
+ uint8_t buf[64];
162
+ size_t i, full = len & ~((size_t)63);
163
+ for (i = 0; i < full; i += 64) sp_crypto_sha1_block(H, msg + i);
164
+ size_t rem = len - full;
165
+ for (i = 0; i < rem; i++) buf[i] = msg[full + i];
166
+ buf[rem] = 0x80;
167
+ if (rem >= 56) {
168
+ for (i = rem + 1; i < 64; i++) buf[i] = 0;
169
+ sp_crypto_sha1_block(H, buf);
170
+ for (i = 0; i < 56; i++) buf[i] = 0;
171
+ }
172
+ else {
173
+ for (i = rem + 1; i < 56; i++) buf[i] = 0;
174
+ }
175
+ uint64_t bits = (uint64_t)len * 8;
176
+ for (i = 0; i < 8; i++) buf[56 + i] = (uint8_t)(bits >> (56 - 8*i));
177
+ sp_crypto_sha1_block(H, buf);
178
+ sp_crypto_store_be32(out, H, 5);
179
+ }
180
+
181
+ static SP_TLS char sp_crypto_sha256_hex_buf[65];
182
+
183
+ /* SHA-256(msg) -> 64-char lowercase hex. The `digest` spin package binds
184
+ Digest::SHA256.hexdigest to this. Same static-buffer contract as the
185
+ sibling helpers. */
186
+ const char *sp_crypto_sha256_hex(const char *msg) {SP_GC_ROOT_STR(msg);
187
+ uint8_t out[32];
188
+ sp_crypto_sha256((const uint8_t *)msg, sp_str_byte_len(msg), out);
189
+ static const char H[] = "0123456789abcdef";
190
+ int i;
191
+ for (i = 0; i < 32; i++) {
192
+ sp_crypto_sha256_hex_buf[i*2] = H[(out[i] >> 4) & 0xf];
193
+ sp_crypto_sha256_hex_buf[i*2+1] = H[out[i] & 0xf];
194
+ }
195
+ sp_crypto_sha256_hex_buf[64] = '\0';
196
+ return sp_crypto_sha256_hex_buf;
197
+ }
198
+
199
+ /* SHA-256(msg) -> the raw 32 digest bytes. Digest::SHA256.digest binds here
200
+ through the native binding's `:cbinstr` return mode, which reads the exact
201
+ length from sp_ffi_bin_len rather than calling strlen -- a binary digest
202
+ contains NUL bytes roughly one time in eight. Same static-buffer contract as
203
+ the hex siblings. */
204
+ static SP_TLS char sp_crypto_sha256_bin_buf[32];
205
+
206
+ const char *sp_crypto_sha256_bin(const char *msg) {SP_GC_ROOT_STR(msg);
207
+ sp_crypto_sha256((const uint8_t *)msg, sp_str_byte_len(msg),
208
+ (uint8_t *)sp_crypto_sha256_bin_buf);
209
+ sp_ffi_bin_len = 32;
210
+ return sp_crypto_sha256_bin_buf;
211
+ }
212
+
213
+ static SP_TLS char sp_crypto_sha1_hex_buf[41];
214
+
215
+ const char *sp_crypto_sha1_hex(const char *msg) {SP_GC_ROOT_STR(msg);
216
+ uint8_t out[20];
217
+ sp_crypto_sha1((const uint8_t *)msg, sp_str_byte_len(msg), out);
218
+ static const char H[] = "0123456789abcdef";
219
+ int i;
220
+ for (i = 0; i < 20; i++) {
221
+ sp_crypto_sha1_hex_buf[i*2] = H[(out[i] >> 4) & 0xf];
222
+ sp_crypto_sha1_hex_buf[i*2+1] = H[out[i] & 0xf];
223
+ }
224
+ sp_crypto_sha1_hex_buf[40] = '\0';
225
+ return sp_crypto_sha1_hex_buf;
226
+ }
227
+
228
+ /* SHA-1(msg) -> the raw 20 digest bytes; see sp_crypto_sha256_bin. */
229
+ static SP_TLS char sp_crypto_sha1_bin_buf[20];
230
+
231
+ const char *sp_crypto_sha1_bin(const char *msg) {SP_GC_ROOT_STR(msg);
232
+ sp_crypto_sha1((const uint8_t *)msg, sp_str_byte_len(msg),
233
+ (uint8_t *)sp_crypto_sha1_bin_buf);
234
+ sp_ffi_bin_len = 20;
235
+ return sp_crypto_sha1_bin_buf;
236
+ }
237
+
238
+ /* ---------- MD5 (RFC 1321) ----------
239
+ * Legacy hash, bound for Digest::MD5: Active Storage's direct-upload
240
+ * protocol checks a blob by the base64 MD5 of its bytes (#4631). Do not use
241
+ * for new security designs. Compact reference implementation, public domain. */
242
+
243
+ static const uint32_t sp_crypto_md5_k[64] = {
244
+ 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
245
+ 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
246
+ 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
247
+ 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
248
+ 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
249
+ 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
250
+ 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
251
+ 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
252
+ };
253
+ static const uint8_t sp_crypto_md5_r[64] = {
254
+ 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
255
+ 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
256
+ 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
257
+ 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
258
+ };
259
+
260
+ static void sp_crypto_md5_block(uint32_t H[4], const uint8_t b[64]) {
261
+ uint32_t w[16], a, bb, c, d, f, t;
262
+ int i, g;
263
+ for (i = 0; i < 16; i++) {
264
+ w[i] = (uint32_t)b[i*4] | ((uint32_t)b[i*4+1] << 8) |
265
+ ((uint32_t)b[i*4+2] << 16) | ((uint32_t)b[i*4+3] << 24);
266
+ }
267
+ a = H[0]; bb = H[1]; c = H[2]; d = H[3];
268
+ for (i = 0; i < 64; i++) {
269
+ if (i < 16) { f = (bb & c) | (~bb & d); g = i; }
270
+ else if (i < 32) { f = (d & bb) | (~d & c); g = (5*i + 1) & 15; }
271
+ else if (i < 48) { f = bb ^ c ^ d; g = (3*i + 5) & 15; }
272
+ else { f = c ^ (bb | ~d); g = (7*i) & 15; }
273
+ t = d; d = c; c = bb;
274
+ uint32_t x = a + f + sp_crypto_md5_k[i] + w[g];
275
+ bb = bb + ((x << sp_crypto_md5_r[i]) | (x >> (32 - sp_crypto_md5_r[i])));
276
+ a = t;
277
+ }
278
+ H[0] += a; H[1] += bb; H[2] += c; H[3] += d;
279
+ }
280
+
281
+ static void sp_crypto_md5(const uint8_t *msg, size_t len, uint8_t out[16]) {
282
+ uint32_t H[4] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 };
283
+ uint8_t buf[64];
284
+ size_t i, full = len & ~((size_t)63);
285
+ for (i = 0; i < full; i += 64) sp_crypto_md5_block(H, msg + i);
286
+ size_t rem = len - full;
287
+ for (i = 0; i < rem; i++) buf[i] = msg[full + i];
288
+ buf[rem] = 0x80;
289
+ if (rem >= 56) {
290
+ for (i = rem + 1; i < 64; i++) buf[i] = 0;
291
+ sp_crypto_md5_block(H, buf);
292
+ for (i = 0; i < 56; i++) buf[i] = 0;
293
+ }
294
+ else {
295
+ for (i = rem + 1; i < 56; i++) buf[i] = 0;
296
+ }
297
+ uint64_t bits = (uint64_t)len * 8; /* little-endian length, unlike SHA */
298
+ for (i = 0; i < 8; i++) buf[56 + i] = (uint8_t)(bits >> (8*i));
299
+ sp_crypto_md5_block(H, buf);
300
+ for (i = 0; i < 4; i++) {
301
+ out[i*4] = (uint8_t)(H[i]);
302
+ out[i*4+1] = (uint8_t)(H[i] >> 8);
303
+ out[i*4+2] = (uint8_t)(H[i] >> 16);
304
+ out[i*4+3] = (uint8_t)(H[i] >> 24);
305
+ }
306
+ }
307
+
308
+ static SP_TLS char sp_crypto_md5_hex_buf[33];
309
+
310
+ const char *sp_crypto_md5_hex(const char *msg) {SP_GC_ROOT_STR(msg);
311
+ uint8_t out[16];
312
+ sp_crypto_md5((const uint8_t *)msg, sp_str_byte_len(msg), out);
313
+ static const char H[] = "0123456789abcdef";
314
+ int i;
315
+ for (i = 0; i < 16; i++) {
316
+ sp_crypto_md5_hex_buf[i*2] = H[(out[i] >> 4) & 0xf];
317
+ sp_crypto_md5_hex_buf[i*2+1] = H[out[i] & 0xf];
318
+ }
319
+ sp_crypto_md5_hex_buf[32] = '\0';
320
+ return sp_crypto_md5_hex_buf;
321
+ }
322
+
323
+ /* MD5(msg) -> the raw 16 digest bytes; see sp_crypto_sha256_bin. */
324
+ static SP_TLS char sp_crypto_md5_bin_buf[16];
325
+
326
+ const char *sp_crypto_md5_bin(const char *msg) {SP_GC_ROOT_STR(msg);
327
+ sp_crypto_md5((const uint8_t *)msg, sp_str_byte_len(msg),
328
+ (uint8_t *)sp_crypto_md5_bin_buf);
329
+ sp_ffi_bin_len = 16;
330
+ return sp_crypto_md5_bin_buf;
331
+ }
332
+
333
+ /* RFC 6455 §1.3 Sec-WebSocket-Accept:
334
+ * base64(SHA-1(client_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
335
+ * Standard Base64 (RFC 4648 §4, `+/` alphabet, `=` padding) -- NOT
336
+ * the URL-safe variant. 20 bytes of SHA-1 -> 28-char output ("xxx=").
337
+ * The GUID is the WebSocket protocol's fixed magic string.
338
+ */
339
+ static const char SPC_B64[64] =
340
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
341
+
342
+ static SP_TLS char sp_crypto_websocket_accept_buf[29];
343
+ static void sp_crypto_b64_strict(const uint8_t *d, size_t n, char *out);
344
+
345
+ const char *sp_crypto_websocket_accept(const char *client_key) {
346
+ /* client_key is the 24-char base64 from the request header;
347
+ * GUID is 36 chars. Total <= 60; cap input at 128 for safety. */
348
+ char in[128 + 36 + 1];
349
+ size_t klen = strlen(client_key);
350
+ if (klen > 128) klen = 128;
351
+ size_t i;
352
+ for (i = 0; i < klen; i++) in[i] = client_key[i];
353
+ static const char guid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
354
+ for (i = 0; i < 36; i++) in[klen + i] = guid[i];
355
+ uint8_t digest[20];
356
+ sp_crypto_sha1((const uint8_t *)in, klen + 36, digest);
357
+ /* base64(20 bytes) = 28 chars: 6 full triplets + 2 leftover bytes
358
+ * -> 3 chars + 1 padding `=`. */
359
+ sp_crypto_b64_strict(digest, 20, sp_crypto_websocket_accept_buf);
360
+ return sp_crypto_websocket_accept_buf;
361
+ }
362
+
363
+ /* Digest::X.base64digest: strict base64 (RFC 4648 §4, `=` padded) of the
364
+ raw digest, which Ruby's Digest::Class defines for every digest. One
365
+ buffer per digest so the three can be held at once. */
366
+ static void sp_crypto_b64_strict(const uint8_t *d, size_t n, char *out) {
367
+ size_t i; int j = 0;
368
+ for (i = 0; i + 3 <= n; i += 3) {
369
+ uint32_t v = ((uint32_t)d[i] << 16) | ((uint32_t)d[i+1] << 8) | (uint32_t)d[i+2];
370
+ out[j++] = SPC_B64[(v >> 18) & 0x3f];
371
+ out[j++] = SPC_B64[(v >> 12) & 0x3f];
372
+ out[j++] = SPC_B64[(v >> 6) & 0x3f];
373
+ out[j++] = SPC_B64[ v & 0x3f];
374
+ }
375
+ if (n - i == 2) {
376
+ uint32_t v = ((uint32_t)d[i] << 16) | ((uint32_t)d[i+1] << 8);
377
+ out[j++] = SPC_B64[(v >> 18) & 0x3f];
378
+ out[j++] = SPC_B64[(v >> 12) & 0x3f];
379
+ out[j++] = SPC_B64[(v >> 6) & 0x3f];
380
+ out[j++] = '=';
381
+ }
382
+ else if (n - i == 1) {
383
+ uint32_t v = ((uint32_t)d[i] << 16);
384
+ out[j++] = SPC_B64[(v >> 18) & 0x3f];
385
+ out[j++] = SPC_B64[(v >> 12) & 0x3f];
386
+ out[j++] = '=';
387
+ out[j++] = '=';
388
+ }
389
+ out[j] = '\0';
390
+ }
391
+
392
+ static SP_TLS char sp_crypto_md5_b64_buf[25];
393
+ static SP_TLS char sp_crypto_sha1_b64_buf[29];
394
+ static SP_TLS char sp_crypto_sha256_b64_buf[45];
395
+
396
+ const char *sp_crypto_md5_b64(const char *msg) {SP_GC_ROOT_STR(msg);
397
+ uint8_t out[16];
398
+ sp_crypto_md5((const uint8_t *)msg, sp_str_byte_len(msg), out);
399
+ sp_crypto_b64_strict(out, 16, sp_crypto_md5_b64_buf);
400
+ return sp_crypto_md5_b64_buf;
401
+ }
402
+ const char *sp_crypto_sha1_b64(const char *msg) {SP_GC_ROOT_STR(msg);
403
+ uint8_t out[20];
404
+ sp_crypto_sha1((const uint8_t *)msg, sp_str_byte_len(msg), out);
405
+ sp_crypto_b64_strict(out, 20, sp_crypto_sha1_b64_buf);
406
+ return sp_crypto_sha1_b64_buf;
407
+ }
408
+ const char *sp_crypto_sha256_b64(const char *msg) {SP_GC_ROOT_STR(msg);
409
+ uint8_t out[32];
410
+ sp_crypto_sha256((const uint8_t *)msg, sp_str_byte_len(msg), out);
411
+ sp_crypto_b64_strict(out, 32, sp_crypto_sha256_b64_buf);
412
+ return sp_crypto_sha256_b64_buf;
413
+ }
414
+
415
+ /* ---------- HMAC-SHA256 (RFC 2104) ---------- */
416
+
417
+ static void sp_crypto_hmac_sha256(const uint8_t *key, size_t klen,
418
+ const uint8_t *msg, size_t mlen,
419
+ uint8_t out[32]) {
420
+ uint8_t kpad[64], ipad[64], opad[64], inner[32];
421
+ size_t i;
422
+ if (klen > 64) {
423
+ sp_crypto_sha256(key, klen, kpad);
424
+ for (i = 32; i < 64; i++) kpad[i] = 0;
425
+ }
426
+ else {
427
+ for (i = 0; i < klen; i++) kpad[i] = key[i];
428
+ for (i = klen; i < 64; i++) kpad[i] = 0;
429
+ }
430
+ for (i = 0; i < 64; i++) {
431
+ ipad[i] = kpad[i] ^ 0x36;
432
+ opad[i] = kpad[i] ^ 0x5c;
433
+ }
434
+ /* inner = SHA256(ipad || msg). Stream both segments through the
435
+ * compression to avoid allocating ipad||msg. */
436
+ {
437
+ uint32_t H[8] = {
438
+ 0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,
439
+ 0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19
440
+ };
441
+ sp_crypto_sha256_block(H, ipad);
442
+ uint8_t buf[64];
443
+ size_t full = mlen & ~((size_t)63);
444
+ for (i = 0; i < full; i += 64) sp_crypto_sha256_block(H, msg + i);
445
+ size_t rem = mlen - full;
446
+ for (i = 0; i < rem; i++) buf[i] = msg[full + i];
447
+ buf[rem] = 0x80;
448
+ if (rem >= 56) {
449
+ for (i = rem + 1; i < 64; i++) buf[i] = 0;
450
+ sp_crypto_sha256_block(H, buf);
451
+ for (i = 0; i < 56; i++) buf[i] = 0;
452
+ }
453
+ else {
454
+ for (i = rem + 1; i < 56; i++) buf[i] = 0;
455
+ }
456
+ uint64_t bits = (uint64_t)(64 + mlen) * 8;
457
+ for (i = 0; i < 8; i++) buf[56 + i] = (uint8_t)(bits >> (56 - 8*i));
458
+ sp_crypto_sha256_block(H, buf);
459
+ for (i = 0; i < 8; i++) {
460
+ inner[i*4] = (uint8_t)(H[i] >> 24);
461
+ inner[i*4+1] = (uint8_t)(H[i] >> 16);
462
+ inner[i*4+2] = (uint8_t)(H[i] >> 8);
463
+ inner[i*4+3] = (uint8_t)(H[i]);
464
+ }
465
+ }
466
+ /* outer = SHA256(opad || inner) */
467
+ {
468
+ uint32_t H[8] = {
469
+ 0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,
470
+ 0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19
471
+ };
472
+ sp_crypto_sha256_block(H, opad);
473
+ uint8_t buf[64];
474
+ for (i = 0; i < 32; i++) buf[i] = inner[i];
475
+ buf[32] = 0x80;
476
+ for (i = 33; i < 56; i++) buf[i] = 0;
477
+ uint64_t bits = (uint64_t)(64 + 32) * 8;
478
+ for (i = 0; i < 8; i++) buf[56 + i] = (uint8_t)(bits >> (56 - 8*i));
479
+ sp_crypto_sha256_block(H, buf);
480
+ sp_crypto_store_be32(out, H, 8);
481
+ }
482
+ }
483
+
484
+ static SP_TLS char sp_crypto_hmac_hex_buf[65];
485
+
486
+ const char *sp_crypto_hmac_sha256_hex(const char *key, const char *msg) {SP_GC_ROOT_STR(key);SP_GC_ROOT_STR(msg);
487
+ uint8_t out[32];
488
+ sp_crypto_hmac_sha256((const uint8_t *)key, sp_str_byte_len(key),
489
+ (const uint8_t *)msg, sp_str_byte_len(msg),
490
+ out);
491
+ static const char H[] = "0123456789abcdef";
492
+ int i;
493
+ for (i = 0; i < 32; i++) {
494
+ sp_crypto_hmac_hex_buf[i*2] = H[(out[i] >> 4) & 0xf];
495
+ sp_crypto_hmac_hex_buf[i*2+1] = H[out[i] & 0xf];
496
+ }
497
+ sp_crypto_hmac_hex_buf[64] = '\0';
498
+ return sp_crypto_hmac_hex_buf;
499
+ }
500
+
501
+ /* The same MAC as its own bytes rather than as hex. Its own buffer, per
502
+ * the file header's rule: a caller that hexes one MAC and takes the raw
503
+ * bytes of another must not have the second clobber the first. */
504
+ static SP_TLS char sp_crypto_hmac_sha256_bin_buf[32];
505
+
506
+ const char *sp_crypto_hmac_sha256_bin(const char *key, const char *msg) {SP_GC_ROOT_STR(key);SP_GC_ROOT_STR(msg);
507
+ sp_crypto_hmac_sha256((const uint8_t *)key, sp_str_byte_len(key),
508
+ (const uint8_t *)msg, sp_str_byte_len(msg),
509
+ (uint8_t *)sp_crypto_hmac_sha256_bin_buf);
510
+ sp_ffi_bin_len = 32;
511
+ return sp_crypto_hmac_sha256_bin_buf;
512
+ }
513
+
514
+ /* HMAC-SHA1 (RFC 2104). SHA-1 is here for the same reason
515
+ * sp_crypto_websocket_accept is: an existing protocol names it, and
516
+ * reproducing that protocol is not a new security design. Rails signs
517
+ * its cookies with HMAC-SHA1 unless the app overrides
518
+ * config.action_dispatch.cookies_digest, so a spinel program that reads
519
+ * one has no choice of digest. Same streaming structure as the SHA-256
520
+ * sibling above -- ipad/opad are compressed straight in rather than
521
+ * concatenated with the message. */
522
+ static void sp_crypto_hmac_sha1(const uint8_t *key, size_t klen,
523
+ const uint8_t *msg, size_t mlen,
524
+ uint8_t out[20]) {
525
+ uint8_t kpad[64], ipad[64], opad[64], inner[20];
526
+ size_t i;
527
+ if (klen > 64) {
528
+ sp_crypto_sha1(key, klen, kpad);
529
+ for (i = 20; i < 64; i++) kpad[i] = 0;
530
+ }
531
+ else {
532
+ for (i = 0; i < klen; i++) kpad[i] = key[i];
533
+ for (i = klen; i < 64; i++) kpad[i] = 0;
534
+ }
535
+ for (i = 0; i < 64; i++) {
536
+ ipad[i] = kpad[i] ^ 0x36;
537
+ opad[i] = kpad[i] ^ 0x5c;
538
+ }
539
+ /* inner = SHA1(ipad || msg) */
540
+ {
541
+ uint32_t H[5] = {
542
+ 0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0
543
+ };
544
+ sp_crypto_sha1_block(H, ipad);
545
+ uint8_t buf[64];
546
+ size_t full = mlen & ~((size_t)63);
547
+ for (i = 0; i < full; i += 64) sp_crypto_sha1_block(H, msg + i);
548
+ size_t rem = mlen - full;
549
+ for (i = 0; i < rem; i++) buf[i] = msg[full + i];
550
+ buf[rem] = 0x80;
551
+ if (rem >= 56) {
552
+ for (i = rem + 1; i < 64; i++) buf[i] = 0;
553
+ sp_crypto_sha1_block(H, buf);
554
+ for (i = 0; i < 56; i++) buf[i] = 0;
555
+ }
556
+ else {
557
+ for (i = rem + 1; i < 56; i++) buf[i] = 0;
558
+ }
559
+ uint64_t bits = (uint64_t)(64 + mlen) * 8;
560
+ for (i = 0; i < 8; i++) buf[56 + i] = (uint8_t)(bits >> (56 - 8*i));
561
+ sp_crypto_sha1_block(H, buf);
562
+ for (i = 0; i < 5; i++) {
563
+ inner[i*4] = (uint8_t)(H[i] >> 24);
564
+ inner[i*4+1] = (uint8_t)(H[i] >> 16);
565
+ inner[i*4+2] = (uint8_t)(H[i] >> 8);
566
+ inner[i*4+3] = (uint8_t)(H[i]);
567
+ }
568
+ }
569
+ /* outer = SHA1(opad || inner) */
570
+ {
571
+ uint32_t H[5] = {
572
+ 0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0
573
+ };
574
+ sp_crypto_sha1_block(H, opad);
575
+ uint8_t buf[64];
576
+ for (i = 0; i < 20; i++) buf[i] = inner[i];
577
+ buf[20] = 0x80;
578
+ for (i = 21; i < 56; i++) buf[i] = 0;
579
+ uint64_t bits = (uint64_t)(64 + 20) * 8;
580
+ for (i = 0; i < 8; i++) buf[56 + i] = (uint8_t)(bits >> (56 - 8*i));
581
+ sp_crypto_sha1_block(H, buf);
582
+ sp_crypto_store_be32(out, H, 5);
583
+ }
584
+ }
585
+
586
+ static SP_TLS char sp_crypto_hmac_sha1_hex_buf[41];
587
+
588
+ const char *sp_crypto_hmac_sha1_hex(const char *key, const char *msg) {SP_GC_ROOT_STR(key);SP_GC_ROOT_STR(msg);
589
+ uint8_t out[20];
590
+ sp_crypto_hmac_sha1((const uint8_t *)key, sp_str_byte_len(key),
591
+ (const uint8_t *)msg, sp_str_byte_len(msg),
592
+ out);
593
+ static const char H[] = "0123456789abcdef";
594
+ int i;
595
+ for (i = 0; i < 20; i++) {
596
+ sp_crypto_hmac_sha1_hex_buf[i*2] = H[(out[i] >> 4) & 0xf];
597
+ sp_crypto_hmac_sha1_hex_buf[i*2+1] = H[out[i] & 0xf];
598
+ }
599
+ sp_crypto_hmac_sha1_hex_buf[40] = '\0';
600
+ return sp_crypto_hmac_sha1_hex_buf;
601
+ }
602
+
603
+ /* HMAC-SHA1 as its own bytes; see the SHA-256 sibling. */
604
+ static SP_TLS char sp_crypto_hmac_sha1_bin_buf[20];
605
+
606
+ const char *sp_crypto_hmac_sha1_bin(const char *key, const char *msg) {SP_GC_ROOT_STR(key);SP_GC_ROOT_STR(msg);
607
+ sp_crypto_hmac_sha1((const uint8_t *)key, sp_str_byte_len(key),
608
+ (const uint8_t *)msg, sp_str_byte_len(msg),
609
+ (uint8_t *)sp_crypto_hmac_sha1_bin_buf);
610
+ sp_ffi_bin_len = 20;
611
+ return sp_crypto_hmac_sha1_bin_buf;
612
+ }
613
+
614
+ /* ---------- Base64URL (RFC 4648 §5) ---------- */
615
+
616
+ static const char SPC_B64U[64] =
617
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
618
+
619
+ static SP_TLS char sp_crypto_hmac_b64url_buf[44];
620
+ static void sp_crypto_b64url_bytes(const uint8_t *src, int n, char *out);
621
+
622
+ const char *sp_crypto_hmac_sha256_b64url(const char *key, const char *msg) {SP_GC_ROOT_STR(key);SP_GC_ROOT_STR(msg);
623
+ uint8_t out[32];
624
+ sp_crypto_hmac_sha256((const uint8_t *)key, sp_str_byte_len(key),
625
+ (const uint8_t *)msg, sp_str_byte_len(msg),
626
+ out);
627
+ sp_crypto_b64url_bytes(out, 32, sp_crypto_hmac_b64url_buf);
628
+ return sp_crypto_hmac_b64url_buf;
629
+ }
630
+
631
+ #define SPC_B64U_BUFSIZE (16 * 1024)
632
+ static SP_TLS char sp_crypto_b64url_buf[SPC_B64U_BUFSIZE];
633
+
634
+ const char *sp_crypto_b64url_encode(const char *src) {
635
+ size_t n = sp_str_byte_len(src);
636
+ size_t i = 0, j = 0;
637
+ if (4 * ((n + 2) / 3) + 1 > SPC_B64U_BUFSIZE) {
638
+ sp_crypto_b64url_buf[0] = '\0';
639
+ return sp_crypto_b64url_buf;
640
+ }
641
+ while (i + 3 <= n) {
642
+ uint32_t v = ((uint32_t)(uint8_t)src[i] << 16)
643
+ | ((uint32_t)(uint8_t)src[i+1] << 8)
644
+ | (uint32_t)(uint8_t)src[i+2];
645
+ sp_crypto_b64url_buf[j++] = SPC_B64U[(v >> 18) & 0x3f];
646
+ sp_crypto_b64url_buf[j++] = SPC_B64U[(v >> 12) & 0x3f];
647
+ sp_crypto_b64url_buf[j++] = SPC_B64U[(v >> 6) & 0x3f];
648
+ sp_crypto_b64url_buf[j++] = SPC_B64U[ v & 0x3f];
649
+ i += 3;
650
+ }
651
+ size_t rem = n - i;
652
+ if (rem == 1) {
653
+ uint32_t v = (uint32_t)(uint8_t)src[i] << 16;
654
+ sp_crypto_b64url_buf[j++] = SPC_B64U[(v >> 18) & 0x3f];
655
+ sp_crypto_b64url_buf[j++] = SPC_B64U[(v >> 12) & 0x3f];
656
+ }
657
+ else if (rem == 2) {
658
+ uint32_t v = ((uint32_t)(uint8_t)src[i] << 16)
659
+ | ((uint32_t)(uint8_t)src[i+1] << 8);
660
+ sp_crypto_b64url_buf[j++] = SPC_B64U[(v >> 18) & 0x3f];
661
+ sp_crypto_b64url_buf[j++] = SPC_B64U[(v >> 12) & 0x3f];
662
+ sp_crypto_b64url_buf[j++] = SPC_B64U[(v >> 6) & 0x3f];
663
+ }
664
+ sp_crypto_b64url_buf[j] = '\0';
665
+ return sp_crypto_b64url_buf;
666
+ }
667
+
668
+ static SP_TLS char sp_crypto_b64u_dec_buf[SPC_B64U_BUFSIZE];
669
+
670
+ static int sp_crypto_b64u_val(char c) {
671
+ if (c >= 'A' && c <= 'Z') return c - 'A';
672
+ if (c >= 'a' && c <= 'z') return c - 'a' + 26;
673
+ if (c >= '0' && c <= '9') return c - '0' + 52;
674
+ if (c == '-') return 62;
675
+ if (c == '_') return 63;
676
+ return -1;
677
+ }
678
+
679
+ const char *sp_crypto_b64url_decode(const char *src) {SP_GC_ROOT_STR(src);
680
+ size_t n = strlen(src);
681
+ while (n > 0 && src[n - 1] == '=') n--; /* tolerate padded input */
682
+ size_t i = 0, j = 0;
683
+ if (n / 4 * 3 + 3 > SPC_B64U_BUFSIZE) {
684
+ sp_crypto_b64u_dec_buf[0] = '\0'; sp_ffi_bin_len = 0;
685
+ return sp_crypto_b64u_dec_buf;
686
+ }
687
+ while (i + 4 <= n) {
688
+ int a = sp_crypto_b64u_val(src[i]);
689
+ int b = sp_crypto_b64u_val(src[i+1]);
690
+ int c = sp_crypto_b64u_val(src[i+2]);
691
+ int d = sp_crypto_b64u_val(src[i+3]);
692
+ if (a < 0 || b < 0 || c < 0 || d < 0) {
693
+ sp_crypto_b64u_dec_buf[0] = '\0'; sp_ffi_bin_len = 0;
694
+ return sp_crypto_b64u_dec_buf;
695
+ }
696
+ uint32_t v = (uint32_t)a << 18 | (uint32_t)b << 12
697
+ | (uint32_t)c << 6 | (uint32_t)d;
698
+ sp_crypto_b64u_dec_buf[j++] = (v >> 16) & 0xff;
699
+ sp_crypto_b64u_dec_buf[j++] = (v >> 8) & 0xff;
700
+ sp_crypto_b64u_dec_buf[j++] = v & 0xff;
701
+ i += 4;
702
+ }
703
+ size_t rem = n - i;
704
+ if (rem == 2) {
705
+ int a = sp_crypto_b64u_val(src[i]);
706
+ int b = sp_crypto_b64u_val(src[i+1]);
707
+ if (a < 0 || b < 0) { sp_crypto_b64u_dec_buf[0] = '\0'; sp_ffi_bin_len = 0; return sp_crypto_b64u_dec_buf; }
708
+ sp_crypto_b64u_dec_buf[j++] = (a << 2) | (b >> 4);
709
+ }
710
+ else if (rem == 3) {
711
+ int a = sp_crypto_b64u_val(src[i]);
712
+ int b = sp_crypto_b64u_val(src[i+1]);
713
+ int c = sp_crypto_b64u_val(src[i+2]);
714
+ if (a < 0 || b < 0 || c < 0) { sp_crypto_b64u_dec_buf[0] = '\0'; sp_ffi_bin_len = 0; return sp_crypto_b64u_dec_buf; }
715
+ sp_crypto_b64u_dec_buf[j++] = (a << 2) | (b >> 4);
716
+ sp_crypto_b64u_dec_buf[j++] = ((b & 0xf) << 4) | (c >> 2);
717
+ }
718
+ sp_crypto_b64u_dec_buf[j] = '\0';
719
+ sp_ffi_bin_len = (int)j; /* the decoded bytes may hold a NUL: bind it `:binstr` */
720
+ return sp_crypto_b64u_dec_buf;
721
+ }
722
+
723
+ /* ---------- PBKDF2-HMAC-SHA256 (RFC 8018) ----------
724
+ * dkLen up to 64 bytes (two HMAC-SHA256 output blocks).
725
+ */
726
+
727
+ /* 64 derived bytes -> 86 unpadded b64url chars + NUL. */
728
+ static SP_TLS char sp_crypto_pbkdf2_b64url_buf[88];
729
+
730
+ /* Unpadded base64url of `n` bytes into `out` (NUL-terminated). */
731
+ static void sp_crypto_b64url_bytes(const uint8_t *src, int n, char *out) {
732
+ int i, j = 0;
733
+ for (i = 0; i + 3 <= n; i += 3) {
734
+ uint32_t v = ((uint32_t)src[i] << 16)
735
+ | ((uint32_t)src[i+1] << 8)
736
+ | (uint32_t)src[i+2];
737
+ out[j++] = SPC_B64U[(v >> 18) & 0x3f];
738
+ out[j++] = SPC_B64U[(v >> 12) & 0x3f];
739
+ out[j++] = SPC_B64U[(v >> 6) & 0x3f];
740
+ out[j++] = SPC_B64U[v & 0x3f];
741
+ }
742
+ if (i < n) {
743
+ uint32_t v = ((uint32_t)src[i] << 16)
744
+ | (i + 1 < n ? ((uint32_t)src[i+1] << 8) : 0);
745
+ out[j++] = SPC_B64U[(v >> 18) & 0x3f];
746
+ out[j++] = SPC_B64U[(v >> 12) & 0x3f];
747
+ if (i + 1 < n) {
748
+ out[j++] = SPC_B64U[(v >> 6) & 0x3f];
749
+ }
750
+ }
751
+ out[j] = '\0';
752
+ }
753
+
754
+ const char *sp_crypto_pbkdf2_sha256_b64url_len(const char *password, const char *salt,
755
+ int iters, int dklen) {SP_GC_ROOT_STR(password);
756
+ if (iters < 1) iters = 1;
757
+ if (dklen < 1) dklen = 32;
758
+ if (dklen > 64) dklen = 64;
759
+ size_t plen = sp_str_byte_len(password);
760
+ size_t slen = sp_str_byte_len(salt);
761
+ uint8_t salted[256];
762
+ if (slen + 4 > sizeof(salted)) {
763
+ sp_crypto_pbkdf2_b64url_buf[0] = '\0';
764
+ return sp_crypto_pbkdf2_b64url_buf;
765
+ }
766
+ memcpy(salted, salt, slen);
767
+ /* PBKDF2 (RFC 8018 sec 5.2): DK = T(1) || T(2) || ... , each block
768
+ T(i) = F(password, salt, iters, i) and 32 bytes wide for SHA-256.
769
+ Blocks are independent -- only the INT(i) suffix on the salt
770
+ changes -- so one loop covers any dkLen. */
771
+ uint8_t dk[64];
772
+ int block, done = 0;
773
+ for (block = 1; done < dklen; block++) {
774
+ uint8_t U[32], T[32];
775
+ int b, it, take;
776
+ salted[slen+0] = (uint8_t)((block >> 24) & 0xff);
777
+ salted[slen+1] = (uint8_t)((block >> 16) & 0xff);
778
+ salted[slen+2] = (uint8_t)((block >> 8) & 0xff);
779
+ salted[slen+3] = (uint8_t)(block & 0xff);
780
+ sp_crypto_hmac_sha256((const uint8_t *)password, plen, salted, slen + 4, U);
781
+ memcpy(T, U, 32);
782
+ for (it = 1; it < iters; it++) {
783
+ sp_crypto_hmac_sha256((const uint8_t *)password, plen, U, 32, U);
784
+ for (b = 0; b < 32; b++) T[b] ^= U[b];
785
+ }
786
+ take = (dklen - done < 32) ? (dklen - done) : 32;
787
+ memcpy(dk + done, T, take);
788
+ done += take;
789
+ }
790
+ sp_crypto_b64url_bytes(dk, dklen, sp_crypto_pbkdf2_b64url_buf);
791
+ return sp_crypto_pbkdf2_b64url_buf;
792
+ }
793
+
794
+ const char *sp_crypto_pbkdf2_sha256_b64url(const char *password, const char *salt, int iters) {
795
+ return sp_crypto_pbkdf2_sha256_b64url_len(password, salt, iters, 32);
796
+ }
797
+
798
+ /* ---------- CSPRNG ---------- */
799
+
800
+ static SP_TLS char sp_crypto_random_b64url_buf[90];
801
+
802
+ /* Fill `out` with `nbytes` CSPRNG bytes. 1 on success, 0 when no secure
803
+ source is available -- the ONE place the entropy decision is made, so the
804
+ raw-bytes and base64url entry points below cannot drift apart about what
805
+ counts as secure. */
806
+ int sp_crypto_entropy(uint8_t *out, int nbytes) {
807
+ #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
808
+ arc4random_buf(out, nbytes);
809
+ return 1;
810
+ #elif defined(__wasi__) || defined(_WIN32)
811
+ /* the host's entropy, through WASI's random_get or (Windows, lib/win32)
812
+ BCryptGenRandom; there is no device */
813
+ return getentropy(out, (size_t)nbytes) == 0;
814
+ #else
815
+ /* getrandom(2) first: kernel-sourced, works where /dev/urandom is not
816
+ even visible (chroot, minimal containers) -- the very environments
817
+ whose old time-based fallback made "CSPRNG" a lie. Then the device;
818
+ then fail CLOSED, never weak randomness. */
819
+ #if defined(__linux__) && defined(SYS_getrandom)
820
+ {
821
+ long n = syscall(SYS_getrandom, out, (size_t)nbytes, 0);
822
+ if (n == (long)nbytes) return 1;
823
+ }
824
+ #endif
825
+ {
826
+ FILE *f = fopen("/dev/urandom", "rb");
827
+ if (!f) return 0;
828
+ size_t got = fread(out, 1, (size_t)nbytes, f);
829
+ fclose(f);
830
+ if (got != (size_t)nbytes) return 0;
831
+ }
832
+ return 1;
833
+ #endif
834
+ }
835
+
836
+ /* nbytes raw CSPRNG bytes. SecureRandom's one primitive: every other method
837
+ in the bundled `securerandom` package is a rendering of these bytes, so
838
+ this is the only place that has to be right about entropy.
839
+
840
+ RAISES rather than returning NULL when no secure source is available.
841
+ The b64url sibling below predates it and keeps its NULL contract, but a
842
+ caller reaching for random BYTES is minting a token or a key -- there is
843
+ no answer to give, and a nil flowing into a token is the failure nothing
844
+ notices. Length rides sp_ffi_bin_len for the `:cbinstr` return mode:
845
+ random bytes contain NULs about one time in 256.
846
+
847
+ SP_TLS, unlike its siblings in this file. The `:cbinstr` call site copies
848
+ the returned pointer AFTER the call, so under -DSP_THREADS a second worker
849
+ drawing in that window would overwrite the first one's bytes -- and here
850
+ that means two sessions handed the same token, which is the one failure
851
+ mode that never shows up as a crash. sp_ffi_bin_len, which carries this
852
+ buffer's length, is already SP_TLS: a value and its length in different
853
+ storage classes cannot both be right. sp_random.c keeps its generator
854
+ state per-worker for the same reason. Costs SPC_RANDOM_MAX bytes per
855
+ thread. */
856
+ static SP_TLS char sp_crypto_random_bin_buf[SPC_RANDOM_MAX];
857
+
858
+ const char *sp_crypto_random_bin(int nbytes) {
859
+ if (nbytes < 0) sp_raise_cls("ArgumentError", "negative string size");
860
+ if (nbytes > SPC_RANDOM_MAX)
861
+ sp_raise_cls("ArgumentError",
862
+ "securerandom: request exceeds the bundled maximum draw");
863
+ if (nbytes == 0) { sp_ffi_bin_len = 0; sp_crypto_random_bin_buf[0] = '\0'; return sp_crypto_random_bin_buf; }
864
+ if (!sp_crypto_entropy((uint8_t *)sp_crypto_random_bin_buf, nbytes))
865
+ sp_raise_cls("RuntimeError", "securerandom: no secure random source available");
866
+ sp_ffi_bin_len = nbytes;
867
+ return sp_crypto_random_bin_buf;
868
+ }
869
+
870
+ const char *sp_crypto_random_b64url(int nbytes) {
871
+ if (nbytes < 1) nbytes = 16;
872
+ if (nbytes > 64) nbytes = 64;
873
+ uint8_t r[64];
874
+ if (!sp_crypto_entropy(r, nbytes)) return NULL;
875
+ sp_crypto_b64url_bytes(r, nbytes, sp_crypto_random_b64url_buf);
876
+ return sp_crypto_random_b64url_buf;
877
+ }