iodine 0.7.59 → 0.8.0
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.
- checksums.yaml +4 -4
- data/.clang-format +15 -0
- data/.rspec +1 -0
- data/.yardopts +1 -3
- data/CHANGELOG.md +132 -12
- data/Gemfile +5 -7
- data/LICENSE.txt +1 -1
- data/README.md +147 -671
- data/Rakefile +9 -16
- data/examples/chatroom.nru +9 -0
- data/examples/client +74 -0
- data/examples/config-hello.nru +9 -0
- data/examples/config-hello.ru +11 -0
- data/examples/config-hijack.ru +13 -0
- data/examples/config-shootout.ru +43 -0
- data/examples/config.nru +120 -0
- data/examples/config.rb +37 -0
- data/examples/config.ru +42 -53
- data/examples/fake +24 -0
- data/examples/hello_neorack +137 -0
- data/examples/routes.nru +29 -0
- data/examples/routes.ru +42 -0
- data/exe/iodine +218 -258
- data/ext/iodine/extconf.rb +27 -100
- data/ext/iodine/fio-stl.h +128336 -0
- data/ext/iodine/fio-stl.md +26396 -0
- data/ext/iodine/iodine.c +213 -1393
- data/ext/iodine/iodine.h +250 -55
- data/ext/iodine/iodine_arg_helper.h +329 -0
- data/ext/iodine/iodine_caller.h +234 -25
- data/ext/iodine/iodine_cli.h +434 -0
- data/ext/iodine/iodine_compression.h +312 -0
- data/ext/iodine/iodine_connection.h +3484 -34
- data/ext/iodine/iodine_core.h +363 -0
- data/ext/iodine/iodine_crypto.h +1441 -0
- data/ext/iodine/iodine_defer.h +225 -4
- data/ext/iodine/iodine_json.h +738 -4
- data/ext/iodine/iodine_listener.h +263 -0
- data/ext/iodine/iodine_log.h +297 -0
- data/ext/iodine/iodine_minimap.h +543 -0
- data/ext/iodine/iodine_mustache.h +461 -4
- data/ext/iodine/iodine_pubsub_eng.h +729 -0
- data/ext/iodine/iodine_pubsub_history.h +547 -0
- data/ext/iodine/iodine_pubsub_msg.h +228 -0
- data/ext/iodine/iodine_redis.h +322 -0
- data/ext/iodine/iodine_store.h +418 -16
- data/ext/iodine/iodine_threads.h +279 -0
- data/ext/iodine/iodine_tls.h +515 -6
- data/ext/iodine/iodine_utils.h +923 -0
- data/iodine.gemspec +37 -42
- data/lib/iodine/benchmark.rb +1690 -0
- data/lib/iodine/documentation.rb +3085 -0
- data/lib/iodine/version.rb +17 -1
- data/lib/iodine.rb +54 -255
- data/logo.svg +36 -0
- data/mustache.md +55 -0
- data/sig/iodine.rbs +4 -0
- metadata +58 -184
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
- data/.github/workflows/ruby.yml +0 -42
- data/.gitignore +0 -20
- data/LIMITS.md +0 -41
- data/SPEC-PubSub-Draft.md +0 -159
- data/SPEC-WebSocket-Draft.md +0 -239
- data/bin/console +0 -22
- data/bin/info.md +0 -353
- data/bin/mustache_bench.rb +0 -100
- data/bin/poc/Gemfile.lock +0 -23
- data/bin/poc/README.md +0 -37
- data/bin/poc/config.ru +0 -66
- data/bin/poc/gemfile +0 -1
- data/bin/poc/www/index.html +0 -57
- data/examples/async_task.ru +0 -92
- data/examples/bates/README.md +0 -3
- data/examples/bates/config.ru +0 -342
- data/examples/bates/david+bold.pdf +0 -0
- data/examples/bates/public/drop-pdf.png +0 -0
- data/examples/bates/public/index.html +0 -600
- data/examples/echo.ru +0 -59
- data/examples/etag.ru +0 -16
- data/examples/hello.ru +0 -29
- data/examples/pubsub_engine.ru +0 -81
- data/examples/rack3.ru +0 -12
- data/examples/redis.ru +0 -70
- data/examples/shootout.ru +0 -73
- data/examples/sub-protocols.ru +0 -90
- data/examples/tcp_client.rb +0 -66
- data/examples/x-sendfile.ru +0 -14
- data/ext/iodine/fio.c +0 -11995
- data/ext/iodine/fio.h +0 -6371
- data/ext/iodine/fio_cli.c +0 -431
- data/ext/iodine/fio_cli.h +0 -189
- data/ext/iodine/fio_json_parser.h +0 -688
- data/ext/iodine/fio_siphash.c +0 -157
- data/ext/iodine/fio_siphash.h +0 -37
- data/ext/iodine/fio_tls.h +0 -129
- data/ext/iodine/fio_tls_missing.c +0 -649
- data/ext/iodine/fio_tls_openssl.c +0 -1106
- data/ext/iodine/fio_tmpfile.h +0 -50
- data/ext/iodine/fiobj.h +0 -44
- data/ext/iodine/fiobj4fio.h +0 -21
- data/ext/iodine/fiobj_ary.c +0 -333
- data/ext/iodine/fiobj_ary.h +0 -139
- data/ext/iodine/fiobj_data.c +0 -1187
- data/ext/iodine/fiobj_data.h +0 -167
- data/ext/iodine/fiobj_hash.c +0 -409
- data/ext/iodine/fiobj_hash.h +0 -176
- data/ext/iodine/fiobj_json.c +0 -622
- data/ext/iodine/fiobj_json.h +0 -68
- data/ext/iodine/fiobj_mem.h +0 -71
- data/ext/iodine/fiobj_mustache.c +0 -317
- data/ext/iodine/fiobj_mustache.h +0 -62
- data/ext/iodine/fiobj_numbers.c +0 -344
- data/ext/iodine/fiobj_numbers.h +0 -127
- data/ext/iodine/fiobj_str.c +0 -433
- data/ext/iodine/fiobj_str.h +0 -172
- data/ext/iodine/fiobject.c +0 -620
- data/ext/iodine/fiobject.h +0 -655
- data/ext/iodine/hpack.h +0 -1923
- data/ext/iodine/http.c +0 -2743
- data/ext/iodine/http.h +0 -1002
- data/ext/iodine/http1.c +0 -910
- data/ext/iodine/http1.h +0 -29
- data/ext/iodine/http1_parser.h +0 -878
- data/ext/iodine/http_internal.c +0 -1279
- data/ext/iodine/http_internal.h +0 -237
- data/ext/iodine/http_mime_parser.h +0 -350
- data/ext/iodine/iodine_caller.c +0 -230
- data/ext/iodine/iodine_connection.c +0 -941
- data/ext/iodine/iodine_defer.c +0 -420
- data/ext/iodine/iodine_fiobj2rb.h +0 -120
- data/ext/iodine/iodine_helpers.c +0 -282
- data/ext/iodine/iodine_helpers.h +0 -12
- data/ext/iodine/iodine_http.c +0 -1181
- data/ext/iodine/iodine_http.h +0 -23
- data/ext/iodine/iodine_json.c +0 -302
- data/ext/iodine/iodine_mustache.c +0 -567
- data/ext/iodine/iodine_pubsub.c +0 -580
- data/ext/iodine/iodine_pubsub.h +0 -26
- data/ext/iodine/iodine_rack_io.c +0 -273
- data/ext/iodine/iodine_rack_io.h +0 -20
- data/ext/iodine/iodine_store.c +0 -146
- data/ext/iodine/iodine_tcp.c +0 -346
- data/ext/iodine/iodine_tcp.h +0 -13
- data/ext/iodine/iodine_tls.c +0 -261
- data/ext/iodine/mustache_parser.h +0 -1546
- data/ext/iodine/redis_engine.c +0 -957
- data/ext/iodine/redis_engine.h +0 -79
- data/ext/iodine/resp_parser.h +0 -317
- data/ext/iodine/websocket_parser.h +0 -506
- data/ext/iodine/websockets.c +0 -743
- data/ext/iodine/websockets.h +0 -185
- data/lib/iodine/connection.rb +0 -61
- data/lib/iodine/json.rb +0 -42
- data/lib/iodine/mustache.rb +0 -113
- data/lib/iodine/pubsub.rb +0 -55
- data/lib/iodine/rack_utils.rb +0 -43
- data/lib/iodine/tls.rb +0 -16
- data/lib/rack/handler/iodine.rb +0 -33
- data/logo.png +0 -0
|
@@ -0,0 +1,1441 @@
|
|
|
1
|
+
#ifndef H___IODINE_CRYPTO___H
|
|
2
|
+
#define H___IODINE_CRYPTO___H
|
|
3
|
+
#include "iodine.h"
|
|
4
|
+
|
|
5
|
+
/* *****************************************************************************
|
|
6
|
+
Iodine::Base::Crypto - Advanced Cryptographic Operations
|
|
7
|
+
|
|
8
|
+
Provides access to modern cryptographic primitives:
|
|
9
|
+
- ChaCha20-Poly1305: AEAD symmetric encryption (12-byte nonce)
|
|
10
|
+
- XChaCha20-Poly1305: AEAD symmetric encryption (24-byte nonce, safe for random)
|
|
11
|
+
- AES-128-GCM: AEAD symmetric encryption (16-byte key, 12-byte nonce)
|
|
12
|
+
- AES-256-GCM: AEAD symmetric encryption (32-byte key, 12-byte nonce)
|
|
13
|
+
- Ed25519: Digital signatures
|
|
14
|
+
- X25519: Key exchange and public-key encryption (ECIES with ChaCha20/AES)
|
|
15
|
+
- HKDF: Key derivation (RFC 5869)
|
|
16
|
+
***************************************************************************** */
|
|
17
|
+
|
|
18
|
+
static VALUE iodine_rb_CRYPTO;
|
|
19
|
+
static VALUE iodine_rb_CHACHA20POLY1305;
|
|
20
|
+
static VALUE iodine_rb_XCHACHA20POLY1305;
|
|
21
|
+
static VALUE iodine_rb_AES128GCM;
|
|
22
|
+
static VALUE iodine_rb_AES256GCM;
|
|
23
|
+
static VALUE iodine_rb_ED25519;
|
|
24
|
+
static VALUE iodine_rb_X25519;
|
|
25
|
+
static VALUE iodine_rb_HKDF;
|
|
26
|
+
static VALUE iodine_rb_X25519MLKEM768;
|
|
27
|
+
static VALUE iodine_rb_ARGON2;
|
|
28
|
+
static VALUE iodine_rb_LYRA2;
|
|
29
|
+
|
|
30
|
+
/* *****************************************************************************
|
|
31
|
+
ChaCha20-Poly1305 AEAD Encryption
|
|
32
|
+
***************************************************************************** */
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Encrypts data using ChaCha20-Poly1305 AEAD.
|
|
36
|
+
*
|
|
37
|
+
* @param data [String] Plaintext to encrypt
|
|
38
|
+
* @param key: [String] 32-byte encryption key
|
|
39
|
+
* @param nonce: [String] 12-byte nonce (must be unique per key)
|
|
40
|
+
* @param ad: [String, nil] Optional additional authenticated data
|
|
41
|
+
* @return [Array<String, String>] [ciphertext, mac] where mac is 16 bytes
|
|
42
|
+
*/
|
|
43
|
+
FIO_SFUNC VALUE iodine_crypto_chacha_encrypt(int argc,
|
|
44
|
+
VALUE *argv,
|
|
45
|
+
VALUE self) {
|
|
46
|
+
fio_buf_info_s data = FIO_BUF_INFO0;
|
|
47
|
+
fio_buf_info_s key = FIO_BUF_INFO0;
|
|
48
|
+
fio_buf_info_s nonce = FIO_BUF_INFO0;
|
|
49
|
+
fio_buf_info_s ad = FIO_BUF_INFO0;
|
|
50
|
+
|
|
51
|
+
iodine_rb2c_arg(argc,
|
|
52
|
+
argv,
|
|
53
|
+
IODINE_ARG_BUF(data, 0, "data", 1),
|
|
54
|
+
IODINE_ARG_BUF(key, 0, "key", 1),
|
|
55
|
+
IODINE_ARG_BUF(nonce, 0, "nonce", 1),
|
|
56
|
+
IODINE_ARG_BUF(ad, 0, "ad", 0));
|
|
57
|
+
|
|
58
|
+
if (key.len != 32)
|
|
59
|
+
rb_raise(rb_eArgError, "key must be 32 bytes (got %zu)", key.len);
|
|
60
|
+
if (nonce.len != 12)
|
|
61
|
+
rb_raise(rb_eArgError, "nonce must be 12 bytes (got %zu)", nonce.len);
|
|
62
|
+
|
|
63
|
+
/* Copy data for in-place encryption */
|
|
64
|
+
VALUE ciphertext = rb_str_new(data.buf, data.len);
|
|
65
|
+
uint8_t mac[16];
|
|
66
|
+
|
|
67
|
+
fio_chacha20_poly1305_enc(mac,
|
|
68
|
+
RSTRING_PTR(ciphertext),
|
|
69
|
+
data.len,
|
|
70
|
+
ad.buf,
|
|
71
|
+
ad.len,
|
|
72
|
+
(void *)key.buf,
|
|
73
|
+
(void *)nonce.buf);
|
|
74
|
+
|
|
75
|
+
VALUE mac_str = rb_str_new((const char *)mac, 16);
|
|
76
|
+
return rb_ary_new_from_args(2, ciphertext, mac_str);
|
|
77
|
+
(void)self;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Decrypts data using ChaCha20-Poly1305 AEAD.
|
|
82
|
+
*
|
|
83
|
+
* @param data [String] Ciphertext to decrypt
|
|
84
|
+
* @param mac: [String] 16-byte authentication tag
|
|
85
|
+
* @param key: [String] 32-byte encryption key
|
|
86
|
+
* @param nonce: [String] 12-byte nonce
|
|
87
|
+
* @param ad: [String, nil] Optional additional authenticated data
|
|
88
|
+
* @return [String] Decrypted plaintext
|
|
89
|
+
* @raise [RuntimeError] if authentication fails
|
|
90
|
+
*/
|
|
91
|
+
FIO_SFUNC VALUE iodine_crypto_chacha_decrypt(int argc,
|
|
92
|
+
VALUE *argv,
|
|
93
|
+
VALUE self) {
|
|
94
|
+
fio_buf_info_s data = FIO_BUF_INFO0;
|
|
95
|
+
fio_buf_info_s mac = FIO_BUF_INFO0;
|
|
96
|
+
fio_buf_info_s key = FIO_BUF_INFO0;
|
|
97
|
+
fio_buf_info_s nonce = FIO_BUF_INFO0;
|
|
98
|
+
fio_buf_info_s ad = FIO_BUF_INFO0;
|
|
99
|
+
|
|
100
|
+
iodine_rb2c_arg(argc,
|
|
101
|
+
argv,
|
|
102
|
+
IODINE_ARG_BUF(data, 0, "data", 1),
|
|
103
|
+
IODINE_ARG_BUF(mac, 0, "mac", 1),
|
|
104
|
+
IODINE_ARG_BUF(key, 0, "key", 1),
|
|
105
|
+
IODINE_ARG_BUF(nonce, 0, "nonce", 1),
|
|
106
|
+
IODINE_ARG_BUF(ad, 0, "ad", 0));
|
|
107
|
+
|
|
108
|
+
if (key.len != 32)
|
|
109
|
+
rb_raise(rb_eArgError, "key must be 32 bytes (got %zu)", key.len);
|
|
110
|
+
if (nonce.len != 12)
|
|
111
|
+
rb_raise(rb_eArgError, "nonce must be 12 bytes (got %zu)", nonce.len);
|
|
112
|
+
if (mac.len != 16)
|
|
113
|
+
rb_raise(rb_eArgError, "mac must be 16 bytes (got %zu)", mac.len);
|
|
114
|
+
|
|
115
|
+
/* Copy data for in-place decryption, also copy mac since it's modified */
|
|
116
|
+
VALUE plaintext = rb_str_new(data.buf, data.len);
|
|
117
|
+
uint8_t mac_copy[16];
|
|
118
|
+
FIO_MEMCPY(mac_copy, mac.buf, 16);
|
|
119
|
+
|
|
120
|
+
int result = fio_chacha20_poly1305_dec(mac_copy,
|
|
121
|
+
RSTRING_PTR(plaintext),
|
|
122
|
+
data.len,
|
|
123
|
+
ad.buf,
|
|
124
|
+
ad.len,
|
|
125
|
+
(void *)key.buf,
|
|
126
|
+
(void *)nonce.buf);
|
|
127
|
+
|
|
128
|
+
if (result != 0)
|
|
129
|
+
rb_raise(rb_eRuntimeError, "Authentication failed");
|
|
130
|
+
|
|
131
|
+
return plaintext;
|
|
132
|
+
(void)self;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* *****************************************************************************
|
|
136
|
+
XChaCha20-Poly1305 AEAD Encryption (Extended Nonce)
|
|
137
|
+
***************************************************************************** */
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Encrypts data using XChaCha20-Poly1305 AEAD.
|
|
141
|
+
*
|
|
142
|
+
* XChaCha20-Poly1305 uses a 24-byte nonce (vs 12-byte for ChaCha20-Poly1305),
|
|
143
|
+
* making it safe to use randomly generated nonces without risk of collision.
|
|
144
|
+
*
|
|
145
|
+
* @param data [String] Plaintext to encrypt
|
|
146
|
+
* @param key: [String] 32-byte encryption key
|
|
147
|
+
* @param nonce: [String] 24-byte nonce (safe for random generation)
|
|
148
|
+
* @param ad: [String, nil] Optional additional authenticated data
|
|
149
|
+
* @return [Array<String, String>] [ciphertext, mac] where mac is 16 bytes
|
|
150
|
+
*/
|
|
151
|
+
FIO_SFUNC VALUE iodine_crypto_xchacha_encrypt(int argc,
|
|
152
|
+
VALUE *argv,
|
|
153
|
+
VALUE self) {
|
|
154
|
+
fio_buf_info_s data = FIO_BUF_INFO0;
|
|
155
|
+
fio_buf_info_s key = FIO_BUF_INFO0;
|
|
156
|
+
fio_buf_info_s nonce = FIO_BUF_INFO0;
|
|
157
|
+
fio_buf_info_s ad = FIO_BUF_INFO0;
|
|
158
|
+
|
|
159
|
+
iodine_rb2c_arg(argc,
|
|
160
|
+
argv,
|
|
161
|
+
IODINE_ARG_BUF(data, 0, "data", 1),
|
|
162
|
+
IODINE_ARG_BUF(key, 0, "key", 1),
|
|
163
|
+
IODINE_ARG_BUF(nonce, 0, "nonce", 1),
|
|
164
|
+
IODINE_ARG_BUF(ad, 0, "ad", 0));
|
|
165
|
+
|
|
166
|
+
if (key.len != 32)
|
|
167
|
+
rb_raise(rb_eArgError, "key must be 32 bytes (got %zu)", key.len);
|
|
168
|
+
if (nonce.len != 24)
|
|
169
|
+
rb_raise(rb_eArgError, "nonce must be 24 bytes (got %zu)", nonce.len);
|
|
170
|
+
|
|
171
|
+
/* Copy data for in-place encryption */
|
|
172
|
+
VALUE ciphertext = rb_str_new(data.buf, data.len);
|
|
173
|
+
uint8_t mac[16];
|
|
174
|
+
|
|
175
|
+
fio_xchacha20_poly1305_enc(mac,
|
|
176
|
+
RSTRING_PTR(ciphertext),
|
|
177
|
+
data.len,
|
|
178
|
+
ad.buf,
|
|
179
|
+
ad.len,
|
|
180
|
+
(void *)key.buf,
|
|
181
|
+
(void *)nonce.buf);
|
|
182
|
+
|
|
183
|
+
VALUE mac_str = rb_str_new((const char *)mac, 16);
|
|
184
|
+
return rb_ary_new_from_args(2, ciphertext, mac_str);
|
|
185
|
+
(void)self;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Decrypts data using XChaCha20-Poly1305 AEAD.
|
|
190
|
+
*
|
|
191
|
+
* @param data [String] Ciphertext to decrypt
|
|
192
|
+
* @param mac: [String] 16-byte authentication tag
|
|
193
|
+
* @param key: [String] 32-byte encryption key
|
|
194
|
+
* @param nonce: [String] 24-byte nonce
|
|
195
|
+
* @param ad: [String, nil] Optional additional authenticated data
|
|
196
|
+
* @return [String] Decrypted plaintext
|
|
197
|
+
* @raise [RuntimeError] if authentication fails
|
|
198
|
+
*/
|
|
199
|
+
FIO_SFUNC VALUE iodine_crypto_xchacha_decrypt(int argc,
|
|
200
|
+
VALUE *argv,
|
|
201
|
+
VALUE self) {
|
|
202
|
+
fio_buf_info_s data = FIO_BUF_INFO0;
|
|
203
|
+
fio_buf_info_s mac = FIO_BUF_INFO0;
|
|
204
|
+
fio_buf_info_s key = FIO_BUF_INFO0;
|
|
205
|
+
fio_buf_info_s nonce = FIO_BUF_INFO0;
|
|
206
|
+
fio_buf_info_s ad = FIO_BUF_INFO0;
|
|
207
|
+
|
|
208
|
+
iodine_rb2c_arg(argc,
|
|
209
|
+
argv,
|
|
210
|
+
IODINE_ARG_BUF(data, 0, "data", 1),
|
|
211
|
+
IODINE_ARG_BUF(mac, 0, "mac", 1),
|
|
212
|
+
IODINE_ARG_BUF(key, 0, "key", 1),
|
|
213
|
+
IODINE_ARG_BUF(nonce, 0, "nonce", 1),
|
|
214
|
+
IODINE_ARG_BUF(ad, 0, "ad", 0));
|
|
215
|
+
|
|
216
|
+
if (key.len != 32)
|
|
217
|
+
rb_raise(rb_eArgError, "key must be 32 bytes (got %zu)", key.len);
|
|
218
|
+
if (nonce.len != 24)
|
|
219
|
+
rb_raise(rb_eArgError, "nonce must be 24 bytes (got %zu)", nonce.len);
|
|
220
|
+
if (mac.len != 16)
|
|
221
|
+
rb_raise(rb_eArgError, "mac must be 16 bytes (got %zu)", mac.len);
|
|
222
|
+
|
|
223
|
+
/* Copy data for in-place decryption, also copy mac since it's modified */
|
|
224
|
+
VALUE plaintext = rb_str_new(data.buf, data.len);
|
|
225
|
+
uint8_t mac_copy[16];
|
|
226
|
+
FIO_MEMCPY(mac_copy, mac.buf, 16);
|
|
227
|
+
|
|
228
|
+
int result = fio_xchacha20_poly1305_dec(mac_copy,
|
|
229
|
+
RSTRING_PTR(plaintext),
|
|
230
|
+
data.len,
|
|
231
|
+
ad.buf,
|
|
232
|
+
ad.len,
|
|
233
|
+
(void *)key.buf,
|
|
234
|
+
(void *)nonce.buf);
|
|
235
|
+
|
|
236
|
+
if (result != 0)
|
|
237
|
+
rb_raise(rb_eRuntimeError, "Authentication failed");
|
|
238
|
+
|
|
239
|
+
return plaintext;
|
|
240
|
+
(void)self;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* *****************************************************************************
|
|
244
|
+
AES-128-GCM AEAD Encryption
|
|
245
|
+
***************************************************************************** */
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Encrypts data using AES-128-GCM AEAD.
|
|
249
|
+
*
|
|
250
|
+
* @param data [String] Plaintext to encrypt
|
|
251
|
+
* @param key: [String] 16-byte encryption key
|
|
252
|
+
* @param nonce: [String] 12-byte nonce (must be unique per key)
|
|
253
|
+
* @param ad: [String, nil] Optional additional authenticated data
|
|
254
|
+
* @return [Array<String, String>] [ciphertext, mac] where mac is 16 bytes
|
|
255
|
+
*/
|
|
256
|
+
FIO_SFUNC VALUE iodine_crypto_aes128gcm_encrypt(int argc,
|
|
257
|
+
VALUE *argv,
|
|
258
|
+
VALUE self) {
|
|
259
|
+
fio_buf_info_s data = FIO_BUF_INFO0;
|
|
260
|
+
fio_buf_info_s key = FIO_BUF_INFO0;
|
|
261
|
+
fio_buf_info_s nonce = FIO_BUF_INFO0;
|
|
262
|
+
fio_buf_info_s ad = FIO_BUF_INFO0;
|
|
263
|
+
|
|
264
|
+
iodine_rb2c_arg(argc,
|
|
265
|
+
argv,
|
|
266
|
+
IODINE_ARG_BUF(data, 0, "data", 1),
|
|
267
|
+
IODINE_ARG_BUF(key, 0, "key", 1),
|
|
268
|
+
IODINE_ARG_BUF(nonce, 0, "nonce", 1),
|
|
269
|
+
IODINE_ARG_BUF(ad, 0, "ad", 0));
|
|
270
|
+
|
|
271
|
+
if (key.len != 16)
|
|
272
|
+
rb_raise(rb_eArgError, "key must be 16 bytes (got %zu)", key.len);
|
|
273
|
+
if (nonce.len != 12)
|
|
274
|
+
rb_raise(rb_eArgError, "nonce must be 12 bytes (got %zu)", nonce.len);
|
|
275
|
+
|
|
276
|
+
/* Copy data for in-place encryption */
|
|
277
|
+
VALUE ciphertext = rb_str_new(data.buf, data.len);
|
|
278
|
+
uint8_t mac[16];
|
|
279
|
+
|
|
280
|
+
fio_aes128_gcm_enc(mac,
|
|
281
|
+
RSTRING_PTR(ciphertext),
|
|
282
|
+
data.len,
|
|
283
|
+
ad.buf,
|
|
284
|
+
ad.len,
|
|
285
|
+
(void *)key.buf,
|
|
286
|
+
(void *)nonce.buf);
|
|
287
|
+
|
|
288
|
+
VALUE mac_str = rb_str_new((const char *)mac, 16);
|
|
289
|
+
return rb_ary_new_from_args(2, ciphertext, mac_str);
|
|
290
|
+
(void)self;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Decrypts data using AES-128-GCM AEAD.
|
|
295
|
+
*
|
|
296
|
+
* @param data [String] Ciphertext to decrypt
|
|
297
|
+
* @param mac: [String] 16-byte authentication tag
|
|
298
|
+
* @param key: [String] 16-byte encryption key
|
|
299
|
+
* @param nonce: [String] 12-byte nonce
|
|
300
|
+
* @param ad: [String, nil] Optional additional authenticated data
|
|
301
|
+
* @return [String] Decrypted plaintext
|
|
302
|
+
* @raise [RuntimeError] if authentication fails
|
|
303
|
+
*/
|
|
304
|
+
FIO_SFUNC VALUE iodine_crypto_aes128gcm_decrypt(int argc,
|
|
305
|
+
VALUE *argv,
|
|
306
|
+
VALUE self) {
|
|
307
|
+
fio_buf_info_s data = FIO_BUF_INFO0;
|
|
308
|
+
fio_buf_info_s mac = FIO_BUF_INFO0;
|
|
309
|
+
fio_buf_info_s key = FIO_BUF_INFO0;
|
|
310
|
+
fio_buf_info_s nonce = FIO_BUF_INFO0;
|
|
311
|
+
fio_buf_info_s ad = FIO_BUF_INFO0;
|
|
312
|
+
|
|
313
|
+
iodine_rb2c_arg(argc,
|
|
314
|
+
argv,
|
|
315
|
+
IODINE_ARG_BUF(data, 0, "data", 1),
|
|
316
|
+
IODINE_ARG_BUF(mac, 0, "mac", 1),
|
|
317
|
+
IODINE_ARG_BUF(key, 0, "key", 1),
|
|
318
|
+
IODINE_ARG_BUF(nonce, 0, "nonce", 1),
|
|
319
|
+
IODINE_ARG_BUF(ad, 0, "ad", 0));
|
|
320
|
+
|
|
321
|
+
if (key.len != 16)
|
|
322
|
+
rb_raise(rb_eArgError, "key must be 16 bytes (got %zu)", key.len);
|
|
323
|
+
if (nonce.len != 12)
|
|
324
|
+
rb_raise(rb_eArgError, "nonce must be 12 bytes (got %zu)", nonce.len);
|
|
325
|
+
if (mac.len != 16)
|
|
326
|
+
rb_raise(rb_eArgError, "mac must be 16 bytes (got %zu)", mac.len);
|
|
327
|
+
|
|
328
|
+
/* Copy data for in-place decryption, also copy mac since it's modified */
|
|
329
|
+
VALUE plaintext = rb_str_new(data.buf, data.len);
|
|
330
|
+
uint8_t mac_copy[16];
|
|
331
|
+
FIO_MEMCPY(mac_copy, mac.buf, 16);
|
|
332
|
+
|
|
333
|
+
int result = fio_aes128_gcm_dec(mac_copy,
|
|
334
|
+
RSTRING_PTR(plaintext),
|
|
335
|
+
data.len,
|
|
336
|
+
ad.buf,
|
|
337
|
+
ad.len,
|
|
338
|
+
(void *)key.buf,
|
|
339
|
+
(void *)nonce.buf);
|
|
340
|
+
|
|
341
|
+
if (result != 0)
|
|
342
|
+
rb_raise(rb_eRuntimeError, "Authentication failed");
|
|
343
|
+
|
|
344
|
+
return plaintext;
|
|
345
|
+
(void)self;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/* *****************************************************************************
|
|
349
|
+
AES-256-GCM AEAD Encryption
|
|
350
|
+
***************************************************************************** */
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Encrypts data using AES-256-GCM AEAD.
|
|
354
|
+
*
|
|
355
|
+
* @param data [String] Plaintext to encrypt
|
|
356
|
+
* @param key: [String] 32-byte encryption key
|
|
357
|
+
* @param nonce: [String] 12-byte nonce (must be unique per key)
|
|
358
|
+
* @param ad: [String, nil] Optional additional authenticated data
|
|
359
|
+
* @return [Array<String, String>] [ciphertext, mac] where mac is 16 bytes
|
|
360
|
+
*/
|
|
361
|
+
FIO_SFUNC VALUE iodine_crypto_aes256gcm_encrypt(int argc,
|
|
362
|
+
VALUE *argv,
|
|
363
|
+
VALUE self) {
|
|
364
|
+
fio_buf_info_s data = FIO_BUF_INFO0;
|
|
365
|
+
fio_buf_info_s key = FIO_BUF_INFO0;
|
|
366
|
+
fio_buf_info_s nonce = FIO_BUF_INFO0;
|
|
367
|
+
fio_buf_info_s ad = FIO_BUF_INFO0;
|
|
368
|
+
|
|
369
|
+
iodine_rb2c_arg(argc,
|
|
370
|
+
argv,
|
|
371
|
+
IODINE_ARG_BUF(data, 0, "data", 1),
|
|
372
|
+
IODINE_ARG_BUF(key, 0, "key", 1),
|
|
373
|
+
IODINE_ARG_BUF(nonce, 0, "nonce", 1),
|
|
374
|
+
IODINE_ARG_BUF(ad, 0, "ad", 0));
|
|
375
|
+
|
|
376
|
+
if (key.len != 32)
|
|
377
|
+
rb_raise(rb_eArgError, "key must be 32 bytes (got %zu)", key.len);
|
|
378
|
+
if (nonce.len != 12)
|
|
379
|
+
rb_raise(rb_eArgError, "nonce must be 12 bytes (got %zu)", nonce.len);
|
|
380
|
+
|
|
381
|
+
/* Copy data for in-place encryption */
|
|
382
|
+
VALUE ciphertext = rb_str_new(data.buf, data.len);
|
|
383
|
+
uint8_t mac[16];
|
|
384
|
+
|
|
385
|
+
fio_aes256_gcm_enc(mac,
|
|
386
|
+
RSTRING_PTR(ciphertext),
|
|
387
|
+
data.len,
|
|
388
|
+
ad.buf,
|
|
389
|
+
ad.len,
|
|
390
|
+
(void *)key.buf,
|
|
391
|
+
(void *)nonce.buf);
|
|
392
|
+
|
|
393
|
+
VALUE mac_str = rb_str_new((const char *)mac, 16);
|
|
394
|
+
return rb_ary_new_from_args(2, ciphertext, mac_str);
|
|
395
|
+
(void)self;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Decrypts data using AES-256-GCM AEAD.
|
|
400
|
+
*
|
|
401
|
+
* @param data [String] Ciphertext to decrypt
|
|
402
|
+
* @param mac: [String] 16-byte authentication tag
|
|
403
|
+
* @param key: [String] 32-byte encryption key
|
|
404
|
+
* @param nonce: [String] 12-byte nonce
|
|
405
|
+
* @param ad: [String, nil] Optional additional authenticated data
|
|
406
|
+
* @return [String] Decrypted plaintext
|
|
407
|
+
* @raise [RuntimeError] if authentication fails
|
|
408
|
+
*/
|
|
409
|
+
FIO_SFUNC VALUE iodine_crypto_aes256gcm_decrypt(int argc,
|
|
410
|
+
VALUE *argv,
|
|
411
|
+
VALUE self) {
|
|
412
|
+
fio_buf_info_s data = FIO_BUF_INFO0;
|
|
413
|
+
fio_buf_info_s mac = FIO_BUF_INFO0;
|
|
414
|
+
fio_buf_info_s key = FIO_BUF_INFO0;
|
|
415
|
+
fio_buf_info_s nonce = FIO_BUF_INFO0;
|
|
416
|
+
fio_buf_info_s ad = FIO_BUF_INFO0;
|
|
417
|
+
|
|
418
|
+
iodine_rb2c_arg(argc,
|
|
419
|
+
argv,
|
|
420
|
+
IODINE_ARG_BUF(data, 0, "data", 1),
|
|
421
|
+
IODINE_ARG_BUF(mac, 0, "mac", 1),
|
|
422
|
+
IODINE_ARG_BUF(key, 0, "key", 1),
|
|
423
|
+
IODINE_ARG_BUF(nonce, 0, "nonce", 1),
|
|
424
|
+
IODINE_ARG_BUF(ad, 0, "ad", 0));
|
|
425
|
+
|
|
426
|
+
if (key.len != 32)
|
|
427
|
+
rb_raise(rb_eArgError, "key must be 32 bytes (got %zu)", key.len);
|
|
428
|
+
if (nonce.len != 12)
|
|
429
|
+
rb_raise(rb_eArgError, "nonce must be 12 bytes (got %zu)", nonce.len);
|
|
430
|
+
if (mac.len != 16)
|
|
431
|
+
rb_raise(rb_eArgError, "mac must be 16 bytes (got %zu)", mac.len);
|
|
432
|
+
|
|
433
|
+
/* Copy data for in-place decryption, also copy mac since it's modified */
|
|
434
|
+
VALUE plaintext = rb_str_new(data.buf, data.len);
|
|
435
|
+
uint8_t mac_copy[16];
|
|
436
|
+
FIO_MEMCPY(mac_copy, mac.buf, 16);
|
|
437
|
+
|
|
438
|
+
int result = fio_aes256_gcm_dec(mac_copy,
|
|
439
|
+
RSTRING_PTR(plaintext),
|
|
440
|
+
data.len,
|
|
441
|
+
ad.buf,
|
|
442
|
+
ad.len,
|
|
443
|
+
(void *)key.buf,
|
|
444
|
+
(void *)nonce.buf);
|
|
445
|
+
|
|
446
|
+
if (result != 0)
|
|
447
|
+
rb_raise(rb_eRuntimeError, "Authentication failed");
|
|
448
|
+
|
|
449
|
+
return plaintext;
|
|
450
|
+
(void)self;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/* *****************************************************************************
|
|
454
|
+
Ed25519 Digital Signatures
|
|
455
|
+
***************************************************************************** */
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Generates a new Ed25519 key pair.
|
|
459
|
+
*
|
|
460
|
+
* @return [Array<String, String>] [secret_key, public_key] both 32 bytes
|
|
461
|
+
*/
|
|
462
|
+
FIO_SFUNC VALUE iodine_crypto_ed25519_keypair(VALUE self) {
|
|
463
|
+
uint8_t sk[32], pk[32];
|
|
464
|
+
fio_ed25519_keypair(sk, pk);
|
|
465
|
+
VALUE secret = rb_str_new((const char *)sk, 32);
|
|
466
|
+
VALUE public = rb_str_new((const char *)pk, 32);
|
|
467
|
+
/* Clear secret key from stack */
|
|
468
|
+
fio_memset(sk, 0, 32);
|
|
469
|
+
return rb_ary_new_from_args(2, secret, public);
|
|
470
|
+
(void)self;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Derives the public key from an Ed25519 secret key.
|
|
475
|
+
*
|
|
476
|
+
* @param secret_key: [String] 32-byte secret key
|
|
477
|
+
* @return [String] 32-byte public key
|
|
478
|
+
*/
|
|
479
|
+
FIO_SFUNC VALUE iodine_crypto_ed25519_public_key(int argc,
|
|
480
|
+
VALUE *argv,
|
|
481
|
+
VALUE self) {
|
|
482
|
+
fio_buf_info_s sk = FIO_BUF_INFO0;
|
|
483
|
+
iodine_rb2c_arg(argc, argv, IODINE_ARG_BUF(sk, 0, "secret_key", 1));
|
|
484
|
+
|
|
485
|
+
if (sk.len != 32)
|
|
486
|
+
rb_raise(rb_eArgError, "secret_key must be 32 bytes (got %zu)", sk.len);
|
|
487
|
+
|
|
488
|
+
uint8_t pk[32];
|
|
489
|
+
fio_ed25519_public_key(pk, (const uint8_t *)sk.buf);
|
|
490
|
+
return rb_str_new((const char *)pk, 32);
|
|
491
|
+
(void)self;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Signs a message using Ed25519.
|
|
496
|
+
*
|
|
497
|
+
* @param data [String] Message to sign
|
|
498
|
+
* @param secret_key: [String] 32-byte secret key
|
|
499
|
+
* @param public_key: [String] 32-byte public key
|
|
500
|
+
* @return [String] 64-byte signature
|
|
501
|
+
*/
|
|
502
|
+
FIO_SFUNC VALUE iodine_crypto_ed25519_sign(int argc, VALUE *argv, VALUE self) {
|
|
503
|
+
fio_buf_info_s data = FIO_BUF_INFO0;
|
|
504
|
+
fio_buf_info_s sk = FIO_BUF_INFO0;
|
|
505
|
+
fio_buf_info_s pk = FIO_BUF_INFO0;
|
|
506
|
+
|
|
507
|
+
iodine_rb2c_arg(argc,
|
|
508
|
+
argv,
|
|
509
|
+
IODINE_ARG_BUF(data, 0, "data", 1),
|
|
510
|
+
IODINE_ARG_BUF(sk, 0, "secret_key", 1),
|
|
511
|
+
IODINE_ARG_BUF(pk, 0, "public_key", 1));
|
|
512
|
+
|
|
513
|
+
if (sk.len != 32)
|
|
514
|
+
rb_raise(rb_eArgError, "secret_key must be 32 bytes (got %zu)", sk.len);
|
|
515
|
+
if (pk.len != 32)
|
|
516
|
+
rb_raise(rb_eArgError, "public_key must be 32 bytes (got %zu)", pk.len);
|
|
517
|
+
|
|
518
|
+
uint8_t sig[64];
|
|
519
|
+
fio_ed25519_sign(sig,
|
|
520
|
+
data.buf,
|
|
521
|
+
data.len,
|
|
522
|
+
(const uint8_t *)sk.buf,
|
|
523
|
+
(const uint8_t *)pk.buf);
|
|
524
|
+
return rb_str_new((const char *)sig, 64);
|
|
525
|
+
(void)self;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Verifies an Ed25519 signature.
|
|
530
|
+
*
|
|
531
|
+
* @param data [String] Original message
|
|
532
|
+
* @param signature [String] 64-byte signature
|
|
533
|
+
* @param public_key: [String] 32-byte public key
|
|
534
|
+
* @return [Boolean] true if valid, false otherwise
|
|
535
|
+
*/
|
|
536
|
+
FIO_SFUNC VALUE iodine_crypto_ed25519_verify(int argc,
|
|
537
|
+
VALUE *argv,
|
|
538
|
+
VALUE self) {
|
|
539
|
+
fio_buf_info_s sig = FIO_BUF_INFO0;
|
|
540
|
+
fio_buf_info_s data = FIO_BUF_INFO0;
|
|
541
|
+
fio_buf_info_s pk = FIO_BUF_INFO0;
|
|
542
|
+
|
|
543
|
+
iodine_rb2c_arg(argc,
|
|
544
|
+
argv,
|
|
545
|
+
IODINE_ARG_BUF(data, 0, "data", 1),
|
|
546
|
+
IODINE_ARG_BUF(sig, 0, "signature", 1),
|
|
547
|
+
IODINE_ARG_BUF(pk, 0, "public_key", 1));
|
|
548
|
+
|
|
549
|
+
if (sig.len != 64)
|
|
550
|
+
rb_raise(rb_eArgError, "signature must be 64 bytes (got %zu)", sig.len);
|
|
551
|
+
if (pk.len != 32)
|
|
552
|
+
rb_raise(rb_eArgError, "public_key must be 32 bytes (got %zu)", pk.len);
|
|
553
|
+
|
|
554
|
+
int result = fio_ed25519_verify((const uint8_t *)sig.buf,
|
|
555
|
+
data.buf,
|
|
556
|
+
data.len,
|
|
557
|
+
(const uint8_t *)pk.buf);
|
|
558
|
+
return result == 0 ? Qtrue : Qfalse;
|
|
559
|
+
(void)self;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Converts an Ed25519 secret key to an X25519 secret key.
|
|
564
|
+
*
|
|
565
|
+
* This allows using an Ed25519 signing key for X25519 key exchange.
|
|
566
|
+
*
|
|
567
|
+
* @param ed_secret_key: [String] 32-byte Ed25519 secret key
|
|
568
|
+
* @return [String] 32-byte X25519 secret key
|
|
569
|
+
*/
|
|
570
|
+
FIO_SFUNC VALUE iodine_crypto_ed25519_to_x25519_secret(int argc,
|
|
571
|
+
VALUE *argv,
|
|
572
|
+
VALUE self) {
|
|
573
|
+
fio_buf_info_s ed_sk = FIO_BUF_INFO0;
|
|
574
|
+
iodine_rb2c_arg(argc, argv, IODINE_ARG_BUF(ed_sk, 0, "ed_secret_key", 1));
|
|
575
|
+
|
|
576
|
+
if (ed_sk.len != 32)
|
|
577
|
+
rb_raise(rb_eArgError,
|
|
578
|
+
"ed_secret_key must be 32 bytes (got %zu)",
|
|
579
|
+
ed_sk.len);
|
|
580
|
+
|
|
581
|
+
uint8_t x_sk[32];
|
|
582
|
+
fio_ed25519_sk_to_x25519(x_sk, (const uint8_t *)ed_sk.buf);
|
|
583
|
+
VALUE result = rb_str_new((const char *)x_sk, 32);
|
|
584
|
+
/* Clear secret key from stack */
|
|
585
|
+
fio_memset(x_sk, 0, 32);
|
|
586
|
+
return result;
|
|
587
|
+
(void)self;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Converts an Ed25519 public key to an X25519 public key.
|
|
592
|
+
*
|
|
593
|
+
* This allows encrypting to someone who has only shared their Ed25519
|
|
594
|
+
* signing public key.
|
|
595
|
+
*
|
|
596
|
+
* @param ed_public_key: [String] 32-byte Ed25519 public key
|
|
597
|
+
* @return [String] 32-byte X25519 public key
|
|
598
|
+
*/
|
|
599
|
+
FIO_SFUNC VALUE iodine_crypto_ed25519_to_x25519_public(int argc,
|
|
600
|
+
VALUE *argv,
|
|
601
|
+
VALUE self) {
|
|
602
|
+
fio_buf_info_s ed_pk = FIO_BUF_INFO0;
|
|
603
|
+
iodine_rb2c_arg(argc, argv, IODINE_ARG_BUF(ed_pk, 0, "ed_public_key", 1));
|
|
604
|
+
|
|
605
|
+
if (ed_pk.len != 32)
|
|
606
|
+
rb_raise(rb_eArgError,
|
|
607
|
+
"ed_public_key must be 32 bytes (got %zu)",
|
|
608
|
+
ed_pk.len);
|
|
609
|
+
|
|
610
|
+
uint8_t x_pk[32];
|
|
611
|
+
fio_ed25519_pk_to_x25519(x_pk, (const uint8_t *)ed_pk.buf);
|
|
612
|
+
return rb_str_new((const char *)x_pk, 32);
|
|
613
|
+
(void)self;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/* *****************************************************************************
|
|
617
|
+
X25519 Key Exchange
|
|
618
|
+
***************************************************************************** */
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Generates a new X25519 key pair.
|
|
622
|
+
*
|
|
623
|
+
* @return [Array<String, String>] [secret_key, public_key] both 32 bytes
|
|
624
|
+
*/
|
|
625
|
+
FIO_SFUNC VALUE iodine_crypto_x25519_keypair(VALUE self) {
|
|
626
|
+
uint8_t sk[32], pk[32];
|
|
627
|
+
fio_x25519_keypair(sk, pk);
|
|
628
|
+
VALUE secret = rb_str_new((const char *)sk, 32);
|
|
629
|
+
VALUE public = rb_str_new((const char *)pk, 32);
|
|
630
|
+
/* Clear secret key from stack */
|
|
631
|
+
fio_memset(sk, 0, 32);
|
|
632
|
+
return rb_ary_new_from_args(2, secret, public);
|
|
633
|
+
(void)self;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Derives the public key from an X25519 secret key.
|
|
638
|
+
*
|
|
639
|
+
* @param secret_key: [String] 32-byte secret key
|
|
640
|
+
* @return [String] 32-byte public key
|
|
641
|
+
*/
|
|
642
|
+
FIO_SFUNC VALUE iodine_crypto_x25519_public_key(int argc,
|
|
643
|
+
VALUE *argv,
|
|
644
|
+
VALUE self) {
|
|
645
|
+
fio_buf_info_s sk = FIO_BUF_INFO0;
|
|
646
|
+
iodine_rb2c_arg(argc, argv, IODINE_ARG_BUF(sk, 0, "secret_key", 1));
|
|
647
|
+
|
|
648
|
+
if (sk.len != 32)
|
|
649
|
+
rb_raise(rb_eArgError, "secret_key must be 32 bytes (got %zu)", sk.len);
|
|
650
|
+
|
|
651
|
+
uint8_t pk[32];
|
|
652
|
+
fio_x25519_public_key(pk, (const uint8_t *)sk.buf);
|
|
653
|
+
return rb_str_new((const char *)pk, 32);
|
|
654
|
+
(void)self;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Computes a shared secret using X25519 (ECDH).
|
|
659
|
+
*
|
|
660
|
+
* Both parties compute the same shared secret:
|
|
661
|
+
* shared = X25519(my_secret, their_public)
|
|
662
|
+
*
|
|
663
|
+
* @param secret_key: [String] 32-byte own secret key
|
|
664
|
+
* @param their_public: [String] 32-byte other party's public key
|
|
665
|
+
* @return [String] 32-byte shared secret
|
|
666
|
+
* @raise [RuntimeError] if key exchange fails (e.g., low-order point)
|
|
667
|
+
*/
|
|
668
|
+
FIO_SFUNC VALUE iodine_crypto_x25519_shared_secret(int argc,
|
|
669
|
+
VALUE *argv,
|
|
670
|
+
VALUE self) {
|
|
671
|
+
fio_buf_info_s sk = FIO_BUF_INFO0;
|
|
672
|
+
fio_buf_info_s their_pk = FIO_BUF_INFO0;
|
|
673
|
+
|
|
674
|
+
iodine_rb2c_arg(argc,
|
|
675
|
+
argv,
|
|
676
|
+
IODINE_ARG_BUF(sk, 0, "secret_key", 1),
|
|
677
|
+
IODINE_ARG_BUF(their_pk, 0, "their_public", 1));
|
|
678
|
+
|
|
679
|
+
if (sk.len != 32)
|
|
680
|
+
rb_raise(rb_eArgError, "secret_key must be 32 bytes (got %zu)", sk.len);
|
|
681
|
+
if (their_pk.len != 32)
|
|
682
|
+
rb_raise(rb_eArgError,
|
|
683
|
+
"their_public must be 32 bytes (got %zu)",
|
|
684
|
+
their_pk.len);
|
|
685
|
+
|
|
686
|
+
uint8_t shared[32];
|
|
687
|
+
int result = fio_x25519_shared_secret(shared,
|
|
688
|
+
(const uint8_t *)sk.buf,
|
|
689
|
+
(const uint8_t *)their_pk.buf);
|
|
690
|
+
if (result != 0)
|
|
691
|
+
rb_raise(rb_eRuntimeError, "Key exchange failed (invalid public key)");
|
|
692
|
+
|
|
693
|
+
return rb_str_new((const char *)shared, 32);
|
|
694
|
+
(void)self;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Encrypts a message using X25519 public-key encryption (ECIES).
|
|
699
|
+
*
|
|
700
|
+
* Uses ephemeral key agreement + ChaCha20-Poly1305 for authenticated
|
|
701
|
+
* encryption. Only the recipient with the matching secret key can decrypt.
|
|
702
|
+
*
|
|
703
|
+
* @param data [String] Plaintext to encrypt
|
|
704
|
+
* @param recipient_pk: [String] 32-byte recipient's public key
|
|
705
|
+
* @return [String] Ciphertext (message.length + 48 bytes overhead)
|
|
706
|
+
* @raise [RuntimeError] if encryption fails
|
|
707
|
+
*/
|
|
708
|
+
FIO_SFUNC VALUE iodine_crypto_x25519_encrypt(int argc,
|
|
709
|
+
VALUE *argv,
|
|
710
|
+
VALUE self) {
|
|
711
|
+
fio_buf_info_s data = FIO_BUF_INFO0;
|
|
712
|
+
fio_buf_info_s recipient_pk = FIO_BUF_INFO0;
|
|
713
|
+
|
|
714
|
+
iodine_rb2c_arg(argc,
|
|
715
|
+
argv,
|
|
716
|
+
IODINE_ARG_BUF(data, 0, "data", 1),
|
|
717
|
+
IODINE_ARG_BUF(recipient_pk, 0, "recipient_pk", 1));
|
|
718
|
+
|
|
719
|
+
if (recipient_pk.len != 32)
|
|
720
|
+
rb_raise(rb_eArgError,
|
|
721
|
+
"recipient_pk must be 32 bytes (got %zu)",
|
|
722
|
+
recipient_pk.len);
|
|
723
|
+
|
|
724
|
+
/* Output is data + 48 bytes overhead (32 ephemeral pk + 16 mac) */
|
|
725
|
+
size_t out_len = data.len + 48;
|
|
726
|
+
VALUE ciphertext = rb_str_buf_new(out_len);
|
|
727
|
+
rb_str_set_len(ciphertext, out_len);
|
|
728
|
+
|
|
729
|
+
int result =
|
|
730
|
+
fio_x25519_encrypt((uint8_t *)RSTRING_PTR(ciphertext),
|
|
731
|
+
data.buf,
|
|
732
|
+
data.len,
|
|
733
|
+
(fio_crypto_enc_fn *)fio_chacha20_poly1305_enc,
|
|
734
|
+
(const uint8_t *)recipient_pk.buf);
|
|
735
|
+
|
|
736
|
+
if (result != 0)
|
|
737
|
+
rb_raise(rb_eRuntimeError, "Encryption failed");
|
|
738
|
+
|
|
739
|
+
return ciphertext;
|
|
740
|
+
(void)self;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Decrypts a message using X25519 public-key encryption (ECIES).
|
|
745
|
+
*
|
|
746
|
+
* @param data [String] Ciphertext from X25519.encrypt
|
|
747
|
+
* @param secret_key: [String] 32-byte recipient's secret key
|
|
748
|
+
* @return [String] Decrypted plaintext
|
|
749
|
+
* @raise [RuntimeError] if decryption fails (authentication error)
|
|
750
|
+
*/
|
|
751
|
+
FIO_SFUNC VALUE iodine_crypto_x25519_decrypt(int argc,
|
|
752
|
+
VALUE *argv,
|
|
753
|
+
VALUE self) {
|
|
754
|
+
fio_buf_info_s data = FIO_BUF_INFO0;
|
|
755
|
+
fio_buf_info_s sk = FIO_BUF_INFO0;
|
|
756
|
+
|
|
757
|
+
iodine_rb2c_arg(argc,
|
|
758
|
+
argv,
|
|
759
|
+
IODINE_ARG_BUF(data, 0, "data", 1),
|
|
760
|
+
IODINE_ARG_BUF(sk, 0, "secret_key", 1));
|
|
761
|
+
|
|
762
|
+
if (sk.len != 32)
|
|
763
|
+
rb_raise(rb_eArgError, "secret_key must be 32 bytes (got %zu)", sk.len);
|
|
764
|
+
if (data.len < 48)
|
|
765
|
+
rb_raise(rb_eArgError,
|
|
766
|
+
"data too short (minimum 48 bytes, got %zu)",
|
|
767
|
+
data.len);
|
|
768
|
+
|
|
769
|
+
size_t out_len = data.len - 48;
|
|
770
|
+
VALUE plaintext = rb_str_buf_new(out_len);
|
|
771
|
+
rb_str_set_len(plaintext, out_len);
|
|
772
|
+
|
|
773
|
+
int result =
|
|
774
|
+
fio_x25519_decrypt((uint8_t *)RSTRING_PTR(plaintext),
|
|
775
|
+
(const uint8_t *)data.buf,
|
|
776
|
+
data.len,
|
|
777
|
+
(fio_crypto_dec_fn *)fio_chacha20_poly1305_dec,
|
|
778
|
+
(const uint8_t *)sk.buf);
|
|
779
|
+
|
|
780
|
+
if (result != 0)
|
|
781
|
+
rb_raise(rb_eRuntimeError, "Decryption failed (authentication error)");
|
|
782
|
+
|
|
783
|
+
return plaintext;
|
|
784
|
+
(void)self;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Encrypts a message using X25519 public-key encryption (ECIES) with
|
|
789
|
+
* AES-128-GCM.
|
|
790
|
+
*
|
|
791
|
+
* Uses ephemeral key agreement + AES-128-GCM for authenticated encryption.
|
|
792
|
+
* Only the recipient with the matching secret key can decrypt.
|
|
793
|
+
*
|
|
794
|
+
* @param data [String] Plaintext to encrypt
|
|
795
|
+
* @param recipient_pk: [String] 32-byte recipient's public key
|
|
796
|
+
* @return [String] Ciphertext (message.length + 48 bytes overhead)
|
|
797
|
+
* @raise [RuntimeError] if encryption fails
|
|
798
|
+
*/
|
|
799
|
+
FIO_SFUNC VALUE iodine_crypto_x25519_encrypt_aes128(int argc,
|
|
800
|
+
VALUE *argv,
|
|
801
|
+
VALUE self) {
|
|
802
|
+
fio_buf_info_s data = FIO_BUF_INFO0;
|
|
803
|
+
fio_buf_info_s recipient_pk = FIO_BUF_INFO0;
|
|
804
|
+
|
|
805
|
+
iodine_rb2c_arg(argc,
|
|
806
|
+
argv,
|
|
807
|
+
IODINE_ARG_BUF(data, 0, "data", 1),
|
|
808
|
+
IODINE_ARG_BUF(recipient_pk, 0, "recipient_pk", 1));
|
|
809
|
+
|
|
810
|
+
if (recipient_pk.len != 32)
|
|
811
|
+
rb_raise(rb_eArgError,
|
|
812
|
+
"recipient_pk must be 32 bytes (got %zu)",
|
|
813
|
+
recipient_pk.len);
|
|
814
|
+
|
|
815
|
+
/* Output is data + 48 bytes overhead (32 ephemeral pk + 16 mac) */
|
|
816
|
+
size_t out_len = data.len + 48;
|
|
817
|
+
VALUE ciphertext = rb_str_buf_new(out_len);
|
|
818
|
+
rb_str_set_len(ciphertext, out_len);
|
|
819
|
+
|
|
820
|
+
int result = fio_x25519_encrypt((uint8_t *)RSTRING_PTR(ciphertext),
|
|
821
|
+
data.buf,
|
|
822
|
+
data.len,
|
|
823
|
+
(fio_crypto_enc_fn *)fio_aes128_gcm_enc,
|
|
824
|
+
(const uint8_t *)recipient_pk.buf);
|
|
825
|
+
|
|
826
|
+
if (result != 0)
|
|
827
|
+
rb_raise(rb_eRuntimeError, "Encryption failed");
|
|
828
|
+
|
|
829
|
+
return ciphertext;
|
|
830
|
+
(void)self;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Decrypts a message using X25519 public-key encryption (ECIES) with
|
|
835
|
+
* AES-128-GCM.
|
|
836
|
+
*
|
|
837
|
+
* @param data [String] Ciphertext from X25519.encrypt_aes128
|
|
838
|
+
* @param secret_key: [String] 32-byte recipient's secret key
|
|
839
|
+
* @return [String] Decrypted plaintext
|
|
840
|
+
* @raise [RuntimeError] if decryption fails (authentication error)
|
|
841
|
+
*/
|
|
842
|
+
FIO_SFUNC VALUE iodine_crypto_x25519_decrypt_aes128(int argc,
|
|
843
|
+
VALUE *argv,
|
|
844
|
+
VALUE self) {
|
|
845
|
+
fio_buf_info_s data = FIO_BUF_INFO0;
|
|
846
|
+
fio_buf_info_s sk = FIO_BUF_INFO0;
|
|
847
|
+
|
|
848
|
+
iodine_rb2c_arg(argc,
|
|
849
|
+
argv,
|
|
850
|
+
IODINE_ARG_BUF(data, 0, "data", 1),
|
|
851
|
+
IODINE_ARG_BUF(sk, 0, "secret_key", 1));
|
|
852
|
+
|
|
853
|
+
if (sk.len != 32)
|
|
854
|
+
rb_raise(rb_eArgError, "secret_key must be 32 bytes (got %zu)", sk.len);
|
|
855
|
+
if (data.len < 48)
|
|
856
|
+
rb_raise(rb_eArgError,
|
|
857
|
+
"data too short (minimum 48 bytes, got %zu)",
|
|
858
|
+
data.len);
|
|
859
|
+
|
|
860
|
+
size_t out_len = data.len - 48;
|
|
861
|
+
VALUE plaintext = rb_str_buf_new(out_len);
|
|
862
|
+
rb_str_set_len(plaintext, out_len);
|
|
863
|
+
|
|
864
|
+
int result = fio_x25519_decrypt((uint8_t *)RSTRING_PTR(plaintext),
|
|
865
|
+
(const uint8_t *)data.buf,
|
|
866
|
+
data.len,
|
|
867
|
+
(fio_crypto_dec_fn *)fio_aes128_gcm_dec,
|
|
868
|
+
(const uint8_t *)sk.buf);
|
|
869
|
+
|
|
870
|
+
if (result != 0)
|
|
871
|
+
rb_raise(rb_eRuntimeError, "Decryption failed (authentication error)");
|
|
872
|
+
|
|
873
|
+
return plaintext;
|
|
874
|
+
(void)self;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Encrypts a message using X25519 public-key encryption (ECIES) with
|
|
879
|
+
* AES-256-GCM.
|
|
880
|
+
*
|
|
881
|
+
* Uses ephemeral key agreement + AES-256-GCM for authenticated encryption.
|
|
882
|
+
* Only the recipient with the matching secret key can decrypt.
|
|
883
|
+
*
|
|
884
|
+
* @param data [String] Plaintext to encrypt
|
|
885
|
+
* @param recipient_pk: [String] 32-byte recipient's public key
|
|
886
|
+
* @return [String] Ciphertext (message.length + 48 bytes overhead)
|
|
887
|
+
* @raise [RuntimeError] if encryption fails
|
|
888
|
+
*/
|
|
889
|
+
FIO_SFUNC VALUE iodine_crypto_x25519_encrypt_aes256(int argc,
|
|
890
|
+
VALUE *argv,
|
|
891
|
+
VALUE self) {
|
|
892
|
+
fio_buf_info_s data = FIO_BUF_INFO0;
|
|
893
|
+
fio_buf_info_s recipient_pk = FIO_BUF_INFO0;
|
|
894
|
+
|
|
895
|
+
iodine_rb2c_arg(argc,
|
|
896
|
+
argv,
|
|
897
|
+
IODINE_ARG_BUF(data, 0, "data", 1),
|
|
898
|
+
IODINE_ARG_BUF(recipient_pk, 0, "recipient_pk", 1));
|
|
899
|
+
|
|
900
|
+
if (recipient_pk.len != 32)
|
|
901
|
+
rb_raise(rb_eArgError,
|
|
902
|
+
"recipient_pk must be 32 bytes (got %zu)",
|
|
903
|
+
recipient_pk.len);
|
|
904
|
+
|
|
905
|
+
/* Output is data + 48 bytes overhead (32 ephemeral pk + 16 mac) */
|
|
906
|
+
size_t out_len = data.len + 48;
|
|
907
|
+
VALUE ciphertext = rb_str_buf_new(out_len);
|
|
908
|
+
rb_str_set_len(ciphertext, out_len);
|
|
909
|
+
|
|
910
|
+
int result = fio_x25519_encrypt((uint8_t *)RSTRING_PTR(ciphertext),
|
|
911
|
+
data.buf,
|
|
912
|
+
data.len,
|
|
913
|
+
(fio_crypto_enc_fn *)fio_aes256_gcm_enc,
|
|
914
|
+
(const uint8_t *)recipient_pk.buf);
|
|
915
|
+
|
|
916
|
+
if (result != 0)
|
|
917
|
+
rb_raise(rb_eRuntimeError, "Encryption failed");
|
|
918
|
+
|
|
919
|
+
return ciphertext;
|
|
920
|
+
(void)self;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Decrypts a message using X25519 public-key encryption (ECIES) with
|
|
925
|
+
* AES-256-GCM.
|
|
926
|
+
*
|
|
927
|
+
* @param data [String] Ciphertext from X25519.encrypt_aes256
|
|
928
|
+
* @param secret_key: [String] 32-byte recipient's secret key
|
|
929
|
+
* @return [String] Decrypted plaintext
|
|
930
|
+
* @raise [RuntimeError] if decryption fails (authentication error)
|
|
931
|
+
*/
|
|
932
|
+
FIO_SFUNC VALUE iodine_crypto_x25519_decrypt_aes256(int argc,
|
|
933
|
+
VALUE *argv,
|
|
934
|
+
VALUE self) {
|
|
935
|
+
fio_buf_info_s data = FIO_BUF_INFO0;
|
|
936
|
+
fio_buf_info_s sk = FIO_BUF_INFO0;
|
|
937
|
+
|
|
938
|
+
iodine_rb2c_arg(argc,
|
|
939
|
+
argv,
|
|
940
|
+
IODINE_ARG_BUF(data, 0, "data", 1),
|
|
941
|
+
IODINE_ARG_BUF(sk, 0, "secret_key", 1));
|
|
942
|
+
|
|
943
|
+
if (sk.len != 32)
|
|
944
|
+
rb_raise(rb_eArgError, "secret_key must be 32 bytes (got %zu)", sk.len);
|
|
945
|
+
if (data.len < 48)
|
|
946
|
+
rb_raise(rb_eArgError,
|
|
947
|
+
"data too short (minimum 48 bytes, got %zu)",
|
|
948
|
+
data.len);
|
|
949
|
+
|
|
950
|
+
size_t out_len = data.len - 48;
|
|
951
|
+
VALUE plaintext = rb_str_buf_new(out_len);
|
|
952
|
+
rb_str_set_len(plaintext, out_len);
|
|
953
|
+
|
|
954
|
+
int result = fio_x25519_decrypt((uint8_t *)RSTRING_PTR(plaintext),
|
|
955
|
+
(const uint8_t *)data.buf,
|
|
956
|
+
data.len,
|
|
957
|
+
(fio_crypto_dec_fn *)fio_aes256_gcm_dec,
|
|
958
|
+
(const uint8_t *)sk.buf);
|
|
959
|
+
|
|
960
|
+
if (result != 0)
|
|
961
|
+
rb_raise(rb_eRuntimeError, "Decryption failed (authentication error)");
|
|
962
|
+
|
|
963
|
+
return plaintext;
|
|
964
|
+
(void)self;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/* *****************************************************************************
|
|
968
|
+
HKDF Key Derivation (RFC 5869)
|
|
969
|
+
***************************************************************************** */
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Derives keying material using HKDF (RFC 5869).
|
|
973
|
+
*
|
|
974
|
+
* @param ikm: [String] Input keying material
|
|
975
|
+
* @param salt: [String, nil] Optional salt (random value)
|
|
976
|
+
* @param info: [String, nil] Optional context/application info
|
|
977
|
+
* @param length: [Integer] Desired output length (default: 32)
|
|
978
|
+
* @param sha384: [Boolean] Use SHA-384 instead of SHA-256 (default: false)
|
|
979
|
+
* @return [String] Derived key material
|
|
980
|
+
*/
|
|
981
|
+
FIO_SFUNC VALUE iodine_crypto_hkdf_derive(int argc, VALUE *argv, VALUE self) {
|
|
982
|
+
fio_buf_info_s ikm = FIO_BUF_INFO0;
|
|
983
|
+
fio_buf_info_s salt = FIO_BUF_INFO0;
|
|
984
|
+
fio_buf_info_s info = FIO_BUF_INFO0;
|
|
985
|
+
int64_t length = 32;
|
|
986
|
+
uint8_t sha384 = 0;
|
|
987
|
+
|
|
988
|
+
iodine_rb2c_arg(argc,
|
|
989
|
+
argv,
|
|
990
|
+
IODINE_ARG_BUF(ikm, 0, "ikm", 1),
|
|
991
|
+
IODINE_ARG_BUF(salt, 0, "salt", 0),
|
|
992
|
+
IODINE_ARG_BUF(info, 0, "info", 0),
|
|
993
|
+
IODINE_ARG_NUM(length, 0, "length", 0),
|
|
994
|
+
IODINE_ARG_BOOL(sha384, 0, "sha384", 0));
|
|
995
|
+
|
|
996
|
+
/* Max output: 255 * hash_len (32 for SHA-256, 48 for SHA-384) */
|
|
997
|
+
size_t max_len = sha384 ? (size_t)(48 * 255) : (size_t)(32 * 255);
|
|
998
|
+
if (length < 1 || (size_t)length > max_len)
|
|
999
|
+
rb_raise(rb_eArgError,
|
|
1000
|
+
"length must be between 1 and %zu (got %" PRId64 ")",
|
|
1001
|
+
max_len,
|
|
1002
|
+
length);
|
|
1003
|
+
|
|
1004
|
+
VALUE okm = rb_str_buf_new(length);
|
|
1005
|
+
rb_str_set_len(okm, length);
|
|
1006
|
+
|
|
1007
|
+
fio_hkdf(RSTRING_PTR(okm),
|
|
1008
|
+
(size_t)length,
|
|
1009
|
+
salt.buf,
|
|
1010
|
+
salt.len,
|
|
1011
|
+
ikm.buf,
|
|
1012
|
+
ikm.len,
|
|
1013
|
+
info.buf,
|
|
1014
|
+
info.len,
|
|
1015
|
+
(int)sha384);
|
|
1016
|
+
|
|
1017
|
+
return okm;
|
|
1018
|
+
(void)self;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/* *****************************************************************************
|
|
1022
|
+
X25519MLKEM768 Post-Quantum Hybrid Key Encapsulation
|
|
1023
|
+
***************************************************************************** */
|
|
1024
|
+
|
|
1025
|
+
/**
|
|
1026
|
+
* Generates a new X25519MLKEM768 key pair.
|
|
1027
|
+
*
|
|
1028
|
+
* X25519MLKEM768 is a post-quantum hybrid KEM combining X25519 (classical)
|
|
1029
|
+
* with ML-KEM-768 (post-quantum). This provides security against both
|
|
1030
|
+
* classical and quantum attacks.
|
|
1031
|
+
*
|
|
1032
|
+
* @return [Array<String, String>] [secret_key, public_key]
|
|
1033
|
+
* - secret_key: 2432 bytes (ML-KEM-768 sk + X25519 sk)
|
|
1034
|
+
* - public_key: 1216 bytes (ML-KEM-768 pk + X25519 pk)
|
|
1035
|
+
*/
|
|
1036
|
+
FIO_SFUNC VALUE iodine_crypto_x25519mlkem768_keypair(VALUE self) {
|
|
1037
|
+
uint8_t pk[1216], sk[2432];
|
|
1038
|
+
int result = fio_x25519mlkem768_keypair(pk, sk);
|
|
1039
|
+
if (result != 0)
|
|
1040
|
+
rb_raise(rb_eRuntimeError, "Key generation failed");
|
|
1041
|
+
VALUE secret = rb_enc_str_new((const char *)sk, 2432, IodineBinaryEncoding);
|
|
1042
|
+
/* store to ring cache, a temporary thing */
|
|
1043
|
+
STORE.cache(secret);
|
|
1044
|
+
VALUE public = rb_enc_str_new((const char *)pk, 1216, IodineBinaryEncoding);
|
|
1045
|
+
/* Clear secret key from stack */
|
|
1046
|
+
fio_memset(sk, 0, 2432);
|
|
1047
|
+
return rb_ary_new_from_args(2, secret, public);
|
|
1048
|
+
(void)self;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
/**
|
|
1052
|
+
* Encapsulates a shared secret using X25519MLKEM768.
|
|
1053
|
+
*
|
|
1054
|
+
* Performs both X25519 key exchange and ML-KEM-768 encapsulation.
|
|
1055
|
+
* The sender uses this with the recipient's public key to generate
|
|
1056
|
+
* a shared secret and encapsulated data.
|
|
1057
|
+
*
|
|
1058
|
+
* @param public_key: [String] 1216-byte recipient's public key
|
|
1059
|
+
* @return [Array<String, String>] [data, shared_secret]
|
|
1060
|
+
* - data: 1120 bytes (ML-KEM-768 ct + X25519 ephemeral pk)
|
|
1061
|
+
* - shared_secret: 64 bytes (ML-KEM-768 ss || X25519 ss)
|
|
1062
|
+
* @raise [RuntimeError] if encapsulation fails
|
|
1063
|
+
*/
|
|
1064
|
+
FIO_SFUNC VALUE iodine_crypto_x25519mlkem768_encapsulate(int argc,
|
|
1065
|
+
VALUE *argv,
|
|
1066
|
+
VALUE self) {
|
|
1067
|
+
fio_buf_info_s pk = FIO_BUF_INFO0;
|
|
1068
|
+
iodine_rb2c_arg(argc, argv, IODINE_ARG_BUF(pk, 0, "public_key", 1));
|
|
1069
|
+
|
|
1070
|
+
if (pk.len != 1216)
|
|
1071
|
+
rb_raise(rb_eArgError, "public_key must be 1216 bytes (got %zu)", pk.len);
|
|
1072
|
+
|
|
1073
|
+
uint8_t ct[1120], ss[64];
|
|
1074
|
+
int result = fio_x25519mlkem768_encaps(ct, ss, (const uint8_t *)pk.buf);
|
|
1075
|
+
if (result != 0)
|
|
1076
|
+
rb_raise(rb_eRuntimeError, "Encapsulation failed");
|
|
1077
|
+
|
|
1078
|
+
VALUE ciphertext = rb_str_new((const char *)ct, 1120);
|
|
1079
|
+
STORE.cache(ciphertext);
|
|
1080
|
+
VALUE shared_secret = rb_str_new((const char *)ss, 64);
|
|
1081
|
+
STORE.cache(shared_secret);
|
|
1082
|
+
/* Clear shared secret from stack */
|
|
1083
|
+
fio_memset(ss, 0, 64);
|
|
1084
|
+
return rb_ary_new_from_args(2, ciphertext, shared_secret);
|
|
1085
|
+
(void)self;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* Decapsulates a shared secret using X25519MLKEM768.
|
|
1090
|
+
*
|
|
1091
|
+
* Performs both X25519 shared secret derivation and ML-KEM-768 decapsulation.
|
|
1092
|
+
* The recipient uses this with their secret key and the sender's data
|
|
1093
|
+
* to recover the shared secret.
|
|
1094
|
+
*
|
|
1095
|
+
* @param data: [String] 1120-byte encapsulated data from encapsulate
|
|
1096
|
+
* @param secret_key: [String] 2432-byte recipient's secret key
|
|
1097
|
+
* @return [String] 64-byte shared secret (ML-KEM-768 ss || X25519 ss)
|
|
1098
|
+
* @raise [RuntimeError] if decapsulation fails (e.g., low-order point)
|
|
1099
|
+
*/
|
|
1100
|
+
FIO_SFUNC VALUE iodine_crypto_x25519mlkem768_decapsulate(int argc,
|
|
1101
|
+
VALUE *argv,
|
|
1102
|
+
VALUE self) {
|
|
1103
|
+
fio_buf_info_s ct = FIO_BUF_INFO0;
|
|
1104
|
+
fio_buf_info_s sk = FIO_BUF_INFO0;
|
|
1105
|
+
iodine_rb2c_arg(argc,
|
|
1106
|
+
argv,
|
|
1107
|
+
IODINE_ARG_BUF(ct, 0, "data", 1),
|
|
1108
|
+
IODINE_ARG_BUF(sk, 0, "secret_key", 1));
|
|
1109
|
+
|
|
1110
|
+
if (ct.len != 1120)
|
|
1111
|
+
rb_raise(rb_eArgError, "data must be 1120 bytes (got %zu)", ct.len);
|
|
1112
|
+
if (sk.len != 2432)
|
|
1113
|
+
rb_raise(rb_eArgError, "secret_key must be 2432 bytes (got %zu)", sk.len);
|
|
1114
|
+
|
|
1115
|
+
uint8_t ss[64];
|
|
1116
|
+
int result = fio_x25519mlkem768_decaps(ss,
|
|
1117
|
+
(const uint8_t *)ct.buf,
|
|
1118
|
+
(const uint8_t *)sk.buf);
|
|
1119
|
+
if (result != 0)
|
|
1120
|
+
rb_raise(rb_eRuntimeError, "Decapsulation failed (invalid key or data)");
|
|
1121
|
+
|
|
1122
|
+
VALUE shared_secret = rb_str_new((const char *)ss, 64);
|
|
1123
|
+
/* Clear shared secret from stack */
|
|
1124
|
+
fio_memset(ss, 0, 64);
|
|
1125
|
+
return shared_secret;
|
|
1126
|
+
(void)self;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/* *****************************************************************************
|
|
1130
|
+
Argon2 Password Hashing (RFC 9106)
|
|
1131
|
+
***************************************************************************** */
|
|
1132
|
+
|
|
1133
|
+
/**
|
|
1134
|
+
* Hashes a password using Argon2 (d, i, or id).
|
|
1135
|
+
*
|
|
1136
|
+
* @param password [String] The password to hash.
|
|
1137
|
+
* @param salt [String] A unique salt (minimum 8 bytes recommended).
|
|
1138
|
+
* @param t_cost [Integer] Time cost / iterations (default: 3).
|
|
1139
|
+
* @param m_cost [Integer] Memory cost in KiB (default: 65536 = 64 MiB).
|
|
1140
|
+
* @param parallelism [Integer] Parallelism lanes (default: 1).
|
|
1141
|
+
* @param outlen [Integer] Output length in bytes (default: 32).
|
|
1142
|
+
* @param type [Symbol] Argon2 variant: :d, :i, or :id (default: :id).
|
|
1143
|
+
* @param secret [String, nil] Optional secret key (Argon2 secret value K).
|
|
1144
|
+
* @param ad [String, nil] Optional associated data (Argon2 associated data X).
|
|
1145
|
+
* @return [String] Binary hash of the requested length.
|
|
1146
|
+
*/
|
|
1147
|
+
FIO_SFUNC VALUE iodine_crypto_argon2_hash(int argc, VALUE *argv, VALUE self) {
|
|
1148
|
+
fio_buf_info_s password = FIO_BUF_INFO0;
|
|
1149
|
+
fio_buf_info_s salt = FIO_BUF_INFO0;
|
|
1150
|
+
fio_buf_info_s secret = FIO_BUF_INFO0;
|
|
1151
|
+
fio_buf_info_s ad = FIO_BUF_INFO0;
|
|
1152
|
+
int64_t t_cost = 3;
|
|
1153
|
+
int64_t m_cost = 64 * 1024;
|
|
1154
|
+
int64_t parallelism = 1;
|
|
1155
|
+
int64_t outlen = 32;
|
|
1156
|
+
VALUE type_rb = Qnil;
|
|
1157
|
+
|
|
1158
|
+
iodine_rb2c_arg(argc,
|
|
1159
|
+
argv,
|
|
1160
|
+
IODINE_ARG_BUF(password, 0, "password", 1),
|
|
1161
|
+
IODINE_ARG_BUF(salt, 0, "salt", 1),
|
|
1162
|
+
IODINE_ARG_BUF(secret, 0, "secret", 0),
|
|
1163
|
+
IODINE_ARG_BUF(ad, 0, "ad", 0),
|
|
1164
|
+
IODINE_ARG_NUM(t_cost, 0, "t_cost", 0),
|
|
1165
|
+
IODINE_ARG_NUM(m_cost, 0, "m_cost", 0),
|
|
1166
|
+
IODINE_ARG_NUM(parallelism, 0, "parallelism", 0),
|
|
1167
|
+
IODINE_ARG_NUM(outlen, 0, "outlen", 0),
|
|
1168
|
+
IODINE_ARG_RB(type_rb, 0, "type", 0));
|
|
1169
|
+
|
|
1170
|
+
if (outlen < 4 || outlen > 0x0FFFFFFF)
|
|
1171
|
+
rb_raise(rb_eArgError, "outlen must be between 4 and 268435455");
|
|
1172
|
+
if (t_cost < 1)
|
|
1173
|
+
rb_raise(rb_eArgError, "t_cost must be >= 1");
|
|
1174
|
+
if (parallelism < 1)
|
|
1175
|
+
rb_raise(rb_eArgError, "parallelism must be >= 1");
|
|
1176
|
+
if (m_cost < 8 * parallelism)
|
|
1177
|
+
rb_raise(rb_eArgError, "m_cost must be >= 8 * parallelism");
|
|
1178
|
+
|
|
1179
|
+
fio_argon2_type_e type = FIO_ARGON2ID;
|
|
1180
|
+
if (type_rb != Qnil) {
|
|
1181
|
+
if (!RB_TYPE_P(type_rb, RUBY_T_SYMBOL))
|
|
1182
|
+
rb_raise(rb_eTypeError, "type must be a Symbol (:d, :i, or :id)");
|
|
1183
|
+
ID type_id = rb_sym2id(type_rb);
|
|
1184
|
+
if (type_id == rb_intern("d"))
|
|
1185
|
+
type = FIO_ARGON2D;
|
|
1186
|
+
else if (type_id == rb_intern("i"))
|
|
1187
|
+
type = FIO_ARGON2I;
|
|
1188
|
+
else if (type_id == rb_intern("id"))
|
|
1189
|
+
type = FIO_ARGON2ID;
|
|
1190
|
+
else
|
|
1191
|
+
rb_raise(rb_eArgError, "type must be :d, :i, or :id");
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
VALUE out = rb_str_buf_new((long)outlen);
|
|
1195
|
+
rb_str_set_len(out, (long)outlen);
|
|
1196
|
+
|
|
1197
|
+
int result = fio_argon2_hash(RSTRING_PTR(out),
|
|
1198
|
+
.password = password,
|
|
1199
|
+
.salt = salt,
|
|
1200
|
+
.secret = secret,
|
|
1201
|
+
.ad = ad,
|
|
1202
|
+
.t_cost = (uint32_t)t_cost,
|
|
1203
|
+
.m_cost = (uint32_t)m_cost,
|
|
1204
|
+
.parallelism = (uint32_t)parallelism,
|
|
1205
|
+
.outlen = (uint32_t)outlen,
|
|
1206
|
+
.type = type);
|
|
1207
|
+
if (result != 0)
|
|
1208
|
+
rb_raise(rb_eRuntimeError, "Argon2 hashing failed");
|
|
1209
|
+
|
|
1210
|
+
return out;
|
|
1211
|
+
(void)self;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/* *****************************************************************************
|
|
1215
|
+
Lyra2 Password Hashing
|
|
1216
|
+
***************************************************************************** */
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* Hashes a password using Lyra2.
|
|
1220
|
+
*
|
|
1221
|
+
* @param password [String] The password to hash.
|
|
1222
|
+
* @param salt [String] A unique salt.
|
|
1223
|
+
* @param t_cost [Integer] Time cost / rounds (default: 1).
|
|
1224
|
+
* @param m_cost [Integer] Memory cost / rows in matrix (default: 1000).
|
|
1225
|
+
* @param outlen [Integer] Output length in bytes (default: 32).
|
|
1226
|
+
* @param n_cols [Integer] Number of columns (default: 256).
|
|
1227
|
+
* @return [String] Binary hash of the requested length.
|
|
1228
|
+
*/
|
|
1229
|
+
FIO_SFUNC VALUE iodine_crypto_lyra2_hash(int argc, VALUE *argv, VALUE self) {
|
|
1230
|
+
fio_buf_info_s password = FIO_BUF_INFO0;
|
|
1231
|
+
fio_buf_info_s salt = FIO_BUF_INFO0;
|
|
1232
|
+
int64_t t_cost = 1;
|
|
1233
|
+
int64_t m_cost = 1000;
|
|
1234
|
+
int64_t outlen = 32;
|
|
1235
|
+
int64_t n_cols = 256;
|
|
1236
|
+
|
|
1237
|
+
iodine_rb2c_arg(argc,
|
|
1238
|
+
argv,
|
|
1239
|
+
IODINE_ARG_BUF(password, 0, "password", 1),
|
|
1240
|
+
IODINE_ARG_BUF(salt, 0, "salt", 1),
|
|
1241
|
+
IODINE_ARG_NUM(t_cost, 0, "t_cost", 0),
|
|
1242
|
+
IODINE_ARG_NUM(m_cost, 0, "m_cost", 0),
|
|
1243
|
+
IODINE_ARG_NUM(outlen, 0, "outlen", 0),
|
|
1244
|
+
IODINE_ARG_NUM(n_cols, 0, "n_cols", 0));
|
|
1245
|
+
|
|
1246
|
+
if (outlen < 1 || outlen > 0x0FFFFFFF)
|
|
1247
|
+
rb_raise(rb_eArgError, "outlen must be between 1 and 268435455");
|
|
1248
|
+
if (t_cost < 1)
|
|
1249
|
+
rb_raise(rb_eArgError, "t_cost must be >= 1");
|
|
1250
|
+
if (m_cost < 3)
|
|
1251
|
+
rb_raise(rb_eArgError, "m_cost must be >= 3");
|
|
1252
|
+
if (n_cols < 1)
|
|
1253
|
+
rb_raise(rb_eArgError, "n_cols must be >= 1");
|
|
1254
|
+
|
|
1255
|
+
VALUE out = rb_str_buf_new((long)outlen);
|
|
1256
|
+
rb_str_set_len(out, (long)outlen);
|
|
1257
|
+
|
|
1258
|
+
int result = fio_lyra2_hash(RSTRING_PTR(out),
|
|
1259
|
+
.password = password,
|
|
1260
|
+
.salt = salt,
|
|
1261
|
+
.t_cost = (uint64_t)t_cost,
|
|
1262
|
+
.m_cost = (uint64_t)m_cost,
|
|
1263
|
+
.outlen = (size_t)outlen,
|
|
1264
|
+
.n_cols = (size_t)n_cols);
|
|
1265
|
+
if (result != 0)
|
|
1266
|
+
rb_raise(rb_eRuntimeError, "Lyra2 hashing failed");
|
|
1267
|
+
|
|
1268
|
+
return out;
|
|
1269
|
+
(void)self;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
/* *****************************************************************************
|
|
1273
|
+
Module Initialization
|
|
1274
|
+
***************************************************************************** */
|
|
1275
|
+
|
|
1276
|
+
static void Init_Iodine_Crypto(void) {
|
|
1277
|
+
/* Iodine::Base::Crypto */
|
|
1278
|
+
iodine_rb_CRYPTO = rb_define_module_under(iodine_rb_IODINE_BASE, "Crypto");
|
|
1279
|
+
STORE.hold(iodine_rb_CRYPTO);
|
|
1280
|
+
|
|
1281
|
+
/* Iodine::Base::Crypto::ChaCha20Poly1305 */
|
|
1282
|
+
iodine_rb_CHACHA20POLY1305 =
|
|
1283
|
+
rb_define_module_under(iodine_rb_CRYPTO, "ChaCha20Poly1305");
|
|
1284
|
+
STORE.hold(iodine_rb_CHACHA20POLY1305);
|
|
1285
|
+
rb_define_module_function(iodine_rb_CHACHA20POLY1305,
|
|
1286
|
+
"encrypt",
|
|
1287
|
+
iodine_crypto_chacha_encrypt,
|
|
1288
|
+
-1);
|
|
1289
|
+
rb_define_module_function(iodine_rb_CHACHA20POLY1305,
|
|
1290
|
+
"decrypt",
|
|
1291
|
+
iodine_crypto_chacha_decrypt,
|
|
1292
|
+
-1);
|
|
1293
|
+
|
|
1294
|
+
/* Iodine::Base::Crypto::XChaCha20Poly1305 */
|
|
1295
|
+
iodine_rb_XCHACHA20POLY1305 =
|
|
1296
|
+
rb_define_module_under(iodine_rb_CRYPTO, "XChaCha20Poly1305");
|
|
1297
|
+
STORE.hold(iodine_rb_XCHACHA20POLY1305);
|
|
1298
|
+
rb_define_module_function(iodine_rb_XCHACHA20POLY1305,
|
|
1299
|
+
"encrypt",
|
|
1300
|
+
iodine_crypto_xchacha_encrypt,
|
|
1301
|
+
-1);
|
|
1302
|
+
rb_define_module_function(iodine_rb_XCHACHA20POLY1305,
|
|
1303
|
+
"decrypt",
|
|
1304
|
+
iodine_crypto_xchacha_decrypt,
|
|
1305
|
+
-1);
|
|
1306
|
+
|
|
1307
|
+
/* Iodine::Base::Crypto::AES128GCM */
|
|
1308
|
+
iodine_rb_AES128GCM = rb_define_module_under(iodine_rb_CRYPTO, "AES128GCM");
|
|
1309
|
+
STORE.hold(iodine_rb_AES128GCM);
|
|
1310
|
+
rb_define_module_function(iodine_rb_AES128GCM,
|
|
1311
|
+
"encrypt",
|
|
1312
|
+
iodine_crypto_aes128gcm_encrypt,
|
|
1313
|
+
-1);
|
|
1314
|
+
rb_define_module_function(iodine_rb_AES128GCM,
|
|
1315
|
+
"decrypt",
|
|
1316
|
+
iodine_crypto_aes128gcm_decrypt,
|
|
1317
|
+
-1);
|
|
1318
|
+
|
|
1319
|
+
/* Iodine::Base::Crypto::AES256GCM */
|
|
1320
|
+
iodine_rb_AES256GCM = rb_define_module_under(iodine_rb_CRYPTO, "AES256GCM");
|
|
1321
|
+
STORE.hold(iodine_rb_AES256GCM);
|
|
1322
|
+
rb_define_module_function(iodine_rb_AES256GCM,
|
|
1323
|
+
"encrypt",
|
|
1324
|
+
iodine_crypto_aes256gcm_encrypt,
|
|
1325
|
+
-1);
|
|
1326
|
+
rb_define_module_function(iodine_rb_AES256GCM,
|
|
1327
|
+
"decrypt",
|
|
1328
|
+
iodine_crypto_aes256gcm_decrypt,
|
|
1329
|
+
-1);
|
|
1330
|
+
|
|
1331
|
+
/* Iodine::Base::Crypto::Ed25519 */
|
|
1332
|
+
iodine_rb_ED25519 = rb_define_module_under(iodine_rb_CRYPTO, "Ed25519");
|
|
1333
|
+
STORE.hold(iodine_rb_ED25519);
|
|
1334
|
+
rb_define_module_function(iodine_rb_ED25519,
|
|
1335
|
+
"keypair",
|
|
1336
|
+
iodine_crypto_ed25519_keypair,
|
|
1337
|
+
0);
|
|
1338
|
+
rb_define_module_function(iodine_rb_ED25519,
|
|
1339
|
+
"public_key",
|
|
1340
|
+
iodine_crypto_ed25519_public_key,
|
|
1341
|
+
-1);
|
|
1342
|
+
rb_define_module_function(iodine_rb_ED25519,
|
|
1343
|
+
"sign",
|
|
1344
|
+
iodine_crypto_ed25519_sign,
|
|
1345
|
+
-1);
|
|
1346
|
+
rb_define_module_function(iodine_rb_ED25519,
|
|
1347
|
+
"verify",
|
|
1348
|
+
iodine_crypto_ed25519_verify,
|
|
1349
|
+
-1);
|
|
1350
|
+
rb_define_module_function(iodine_rb_ED25519,
|
|
1351
|
+
"to_x25519_secret",
|
|
1352
|
+
iodine_crypto_ed25519_to_x25519_secret,
|
|
1353
|
+
-1);
|
|
1354
|
+
rb_define_module_function(iodine_rb_ED25519,
|
|
1355
|
+
"to_x25519_public",
|
|
1356
|
+
iodine_crypto_ed25519_to_x25519_public,
|
|
1357
|
+
-1);
|
|
1358
|
+
|
|
1359
|
+
/* Iodine::Base::Crypto::X25519 */
|
|
1360
|
+
iodine_rb_X25519 = rb_define_module_under(iodine_rb_CRYPTO, "X25519");
|
|
1361
|
+
STORE.hold(iodine_rb_X25519);
|
|
1362
|
+
rb_define_module_function(iodine_rb_X25519,
|
|
1363
|
+
"keypair",
|
|
1364
|
+
iodine_crypto_x25519_keypair,
|
|
1365
|
+
0);
|
|
1366
|
+
rb_define_module_function(iodine_rb_X25519,
|
|
1367
|
+
"public_key",
|
|
1368
|
+
iodine_crypto_x25519_public_key,
|
|
1369
|
+
-1);
|
|
1370
|
+
rb_define_module_function(iodine_rb_X25519,
|
|
1371
|
+
"shared_secret",
|
|
1372
|
+
iodine_crypto_x25519_shared_secret,
|
|
1373
|
+
-1);
|
|
1374
|
+
rb_define_module_function(iodine_rb_X25519,
|
|
1375
|
+
"encrypt",
|
|
1376
|
+
iodine_crypto_x25519_encrypt,
|
|
1377
|
+
-1);
|
|
1378
|
+
rb_define_module_function(iodine_rb_X25519,
|
|
1379
|
+
"decrypt",
|
|
1380
|
+
iodine_crypto_x25519_decrypt,
|
|
1381
|
+
-1);
|
|
1382
|
+
rb_define_module_function(iodine_rb_X25519,
|
|
1383
|
+
"encrypt_aes128",
|
|
1384
|
+
iodine_crypto_x25519_encrypt_aes128,
|
|
1385
|
+
-1);
|
|
1386
|
+
rb_define_module_function(iodine_rb_X25519,
|
|
1387
|
+
"decrypt_aes128",
|
|
1388
|
+
iodine_crypto_x25519_decrypt_aes128,
|
|
1389
|
+
-1);
|
|
1390
|
+
rb_define_module_function(iodine_rb_X25519,
|
|
1391
|
+
"encrypt_aes256",
|
|
1392
|
+
iodine_crypto_x25519_encrypt_aes256,
|
|
1393
|
+
-1);
|
|
1394
|
+
rb_define_module_function(iodine_rb_X25519,
|
|
1395
|
+
"decrypt_aes256",
|
|
1396
|
+
iodine_crypto_x25519_decrypt_aes256,
|
|
1397
|
+
-1);
|
|
1398
|
+
|
|
1399
|
+
/* Iodine::Base::Crypto::HKDF */
|
|
1400
|
+
iodine_rb_HKDF = rb_define_module_under(iodine_rb_CRYPTO, "HKDF");
|
|
1401
|
+
STORE.hold(iodine_rb_HKDF);
|
|
1402
|
+
rb_define_module_function(iodine_rb_HKDF,
|
|
1403
|
+
"derive",
|
|
1404
|
+
iodine_crypto_hkdf_derive,
|
|
1405
|
+
-1);
|
|
1406
|
+
|
|
1407
|
+
/* Iodine::Base::Crypto::X25519MLKEM768 */
|
|
1408
|
+
iodine_rb_X25519MLKEM768 =
|
|
1409
|
+
rb_define_module_under(iodine_rb_CRYPTO, "X25519MLKEM768");
|
|
1410
|
+
STORE.hold(iodine_rb_X25519MLKEM768);
|
|
1411
|
+
rb_define_module_function(iodine_rb_X25519MLKEM768,
|
|
1412
|
+
"keypair",
|
|
1413
|
+
iodine_crypto_x25519mlkem768_keypair,
|
|
1414
|
+
0);
|
|
1415
|
+
rb_define_module_function(iodine_rb_X25519MLKEM768,
|
|
1416
|
+
"encapsulate",
|
|
1417
|
+
iodine_crypto_x25519mlkem768_encapsulate,
|
|
1418
|
+
-1);
|
|
1419
|
+
rb_define_module_function(iodine_rb_X25519MLKEM768,
|
|
1420
|
+
"decapsulate",
|
|
1421
|
+
iodine_crypto_x25519mlkem768_decapsulate,
|
|
1422
|
+
-1);
|
|
1423
|
+
|
|
1424
|
+
/* Iodine::Base::Crypto::Argon2 */
|
|
1425
|
+
iodine_rb_ARGON2 = rb_define_module_under(iodine_rb_CRYPTO, "Argon2");
|
|
1426
|
+
STORE.hold(iodine_rb_ARGON2);
|
|
1427
|
+
rb_define_module_function(iodine_rb_ARGON2,
|
|
1428
|
+
"hash",
|
|
1429
|
+
iodine_crypto_argon2_hash,
|
|
1430
|
+
-1);
|
|
1431
|
+
|
|
1432
|
+
/* Iodine::Base::Crypto::Lyra2 */
|
|
1433
|
+
iodine_rb_LYRA2 = rb_define_module_under(iodine_rb_CRYPTO, "Lyra2");
|
|
1434
|
+
STORE.hold(iodine_rb_LYRA2);
|
|
1435
|
+
rb_define_module_function(iodine_rb_LYRA2,
|
|
1436
|
+
"hash",
|
|
1437
|
+
iodine_crypto_lyra2_hash,
|
|
1438
|
+
-1);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
#endif /* H___IODINE_CRYPTO___H */
|