pq_crypto 0.3.0 → 0.3.2
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/CHANGELOG.md +40 -15
- data/GET_STARTED.md +26 -0
- data/README.md +62 -7
- data/SECURITY.md +33 -21
- data/ext/pqcrypto/extconf.rb +57 -4
- data/ext/pqcrypto/pq_externalmu.c +297 -0
- data/ext/pqcrypto/pqcrypto_ruby_secure.c +309 -9
- data/ext/pqcrypto/pqcrypto_secure.c +102 -42
- data/ext/pqcrypto/pqcrypto_secure.h +26 -2
- data/ext/pqcrypto/vendor/pqclean/common/keccak4x/Makefile +8 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/Makefile +19 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/Makefile +19 -0
- data/lib/pq_crypto/kem.rb +7 -3
- data/lib/pq_crypto/serialization.rb +1 -1
- data/lib/pq_crypto/signature.rb +115 -3
- data/lib/pq_crypto/version.rb +1 -1
- data/lib/pq_crypto.rb +12 -0
- metadata +8 -4
|
@@ -99,6 +99,32 @@ cleanup:
|
|
|
99
99
|
return ret;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
static int x25519_public_from_private(uint8_t *pk, const uint8_t *sk) {
|
|
103
|
+
EVP_PKEY *pkey = NULL;
|
|
104
|
+
size_t pklen = X25519_PUBLICKEYBYTES;
|
|
105
|
+
int ret = PQ_ERROR_KEYPAIR;
|
|
106
|
+
|
|
107
|
+
if (!pk || !sk) {
|
|
108
|
+
return PQ_ERROR_BUFFER;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, sk, X25519_SECRETKEYBYTES);
|
|
112
|
+
if (!pkey)
|
|
113
|
+
goto cleanup;
|
|
114
|
+
|
|
115
|
+
if (EVP_PKEY_get_raw_public_key(pkey, pk, &pklen) <= 0)
|
|
116
|
+
goto cleanup;
|
|
117
|
+
if (pklen != X25519_PUBLICKEYBYTES)
|
|
118
|
+
goto cleanup;
|
|
119
|
+
|
|
120
|
+
ret = PQ_SUCCESS;
|
|
121
|
+
|
|
122
|
+
cleanup:
|
|
123
|
+
if (pkey)
|
|
124
|
+
EVP_PKEY_free(pkey);
|
|
125
|
+
return ret;
|
|
126
|
+
}
|
|
127
|
+
|
|
102
128
|
static int x25519_shared_secret(uint8_t *shared, const uint8_t *their_pk, const uint8_t *my_sk) {
|
|
103
129
|
EVP_PKEY_CTX *ctx = NULL;
|
|
104
130
|
EVP_PKEY *pkey = NULL;
|
|
@@ -159,8 +185,6 @@ static int xwing_combiner(uint8_t shared_secret[HYBRID_SHAREDSECRETBYTES],
|
|
|
159
185
|
|
|
160
186
|
if (EVP_DigestInit_ex(ctx, EVP_sha3_256(), NULL) != 1)
|
|
161
187
|
goto cleanup;
|
|
162
|
-
if (EVP_DigestUpdate(ctx, XWING_LABEL, sizeof(XWING_LABEL)) != 1)
|
|
163
|
-
goto cleanup;
|
|
164
188
|
if (EVP_DigestUpdate(ctx, ss_M, MLKEM_SHAREDSECRETBYTES) != 1)
|
|
165
189
|
goto cleanup;
|
|
166
190
|
if (EVP_DigestUpdate(ctx, ss_X, X25519_SHAREDSECRETBYTES) != 1)
|
|
@@ -169,6 +193,8 @@ static int xwing_combiner(uint8_t shared_secret[HYBRID_SHAREDSECRETBYTES],
|
|
|
169
193
|
goto cleanup;
|
|
170
194
|
if (EVP_DigestUpdate(ctx, pk_X, X25519_PUBLICKEYBYTES) != 1)
|
|
171
195
|
goto cleanup;
|
|
196
|
+
if (EVP_DigestUpdate(ctx, XWING_LABEL, sizeof(XWING_LABEL)) != 1)
|
|
197
|
+
goto cleanup;
|
|
172
198
|
if (EVP_DigestFinal_ex(ctx, shared_secret, &out_len) != 1)
|
|
173
199
|
goto cleanup;
|
|
174
200
|
if (out_len != HYBRID_SHAREDSECRETBYTES)
|
|
@@ -181,6 +207,55 @@ cleanup:
|
|
|
181
207
|
return ret;
|
|
182
208
|
}
|
|
183
209
|
|
|
210
|
+
static int xwing_expand_secret_key(hybrid_expanded_secret_key_t *expanded_key,
|
|
211
|
+
const uint8_t seed[HYBRID_SECRETKEYBYTES]) {
|
|
212
|
+
EVP_MD_CTX *ctx = NULL;
|
|
213
|
+
uint8_t expanded[XWING_EXPANDEDBYTES];
|
|
214
|
+
int ret = PQ_ERROR_OPENSSL;
|
|
215
|
+
|
|
216
|
+
if (!expanded_key || !seed) {
|
|
217
|
+
return PQ_ERROR_BUFFER;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
memset(expanded_key, 0, sizeof(*expanded_key));
|
|
221
|
+
memset(expanded, 0, sizeof(expanded));
|
|
222
|
+
|
|
223
|
+
ctx = EVP_MD_CTX_new();
|
|
224
|
+
if (!ctx)
|
|
225
|
+
goto cleanup;
|
|
226
|
+
|
|
227
|
+
if (EVP_DigestInit_ex(ctx, EVP_shake256(), NULL) != 1)
|
|
228
|
+
goto cleanup;
|
|
229
|
+
if (EVP_DigestUpdate(ctx, seed, HYBRID_SECRETKEYBYTES) != 1)
|
|
230
|
+
goto cleanup;
|
|
231
|
+
if (EVP_DigestFinalXOF(ctx, expanded, sizeof(expanded)) != 1)
|
|
232
|
+
goto cleanup;
|
|
233
|
+
|
|
234
|
+
ret = PQCLEAN_MLKEM768_CLEAN_crypto_kem_keypair_derand(expanded_key->mlkem_pk,
|
|
235
|
+
expanded_key->mlkem_sk, expanded);
|
|
236
|
+
if (ret != 0) {
|
|
237
|
+
ret = PQ_ERROR_KEYPAIR;
|
|
238
|
+
goto cleanup;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
memcpy(expanded_key->x25519_sk, expanded + 64, X25519_SECRETKEYBYTES);
|
|
242
|
+
ret = x25519_public_from_private(expanded_key->x25519_pk, expanded_key->x25519_sk);
|
|
243
|
+
if (ret != PQ_SUCCESS) {
|
|
244
|
+
goto cleanup;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
ret = PQ_SUCCESS;
|
|
248
|
+
|
|
249
|
+
cleanup:
|
|
250
|
+
if (ctx)
|
|
251
|
+
EVP_MD_CTX_free(ctx);
|
|
252
|
+
pq_secure_wipe(expanded, sizeof(expanded));
|
|
253
|
+
if (ret != PQ_SUCCESS && expanded_key) {
|
|
254
|
+
pq_secure_wipe(expanded_key, sizeof(*expanded_key));
|
|
255
|
+
}
|
|
256
|
+
return ret;
|
|
257
|
+
}
|
|
258
|
+
|
|
184
259
|
int pq_mlkem_keypair(uint8_t *pk, uint8_t *sk) {
|
|
185
260
|
return PQCLEAN_MLKEM768_CLEAN_crypto_kem_keypair(pk, sk) == 0 ? PQ_SUCCESS : PQ_ERROR_KEYPAIR;
|
|
186
261
|
}
|
|
@@ -278,35 +353,36 @@ int pq_testing_mldsa_sign_from_seed(uint8_t *signature, size_t *signature_len,
|
|
|
278
353
|
|
|
279
354
|
int pq_hybrid_kem_keypair(uint8_t *public_key, uint8_t *secret_key) {
|
|
280
355
|
hybrid_public_key_t pk;
|
|
281
|
-
|
|
282
|
-
|
|
356
|
+
hybrid_expanded_secret_key_t expanded;
|
|
357
|
+
uint8_t seed[HYBRID_SECRETKEYBYTES];
|
|
358
|
+
int ret = PQ_SUCCESS;
|
|
283
359
|
|
|
284
360
|
if (!public_key || !secret_key) {
|
|
285
361
|
return PQ_ERROR_BUFFER;
|
|
286
362
|
}
|
|
287
363
|
|
|
288
364
|
memset(&pk, 0, sizeof(pk));
|
|
289
|
-
memset(&
|
|
365
|
+
memset(&expanded, 0, sizeof(expanded));
|
|
366
|
+
memset(seed, 0, sizeof(seed));
|
|
290
367
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
: PQ_ERROR_KEYPAIR;
|
|
294
|
-
if (ret != PQ_SUCCESS) {
|
|
368
|
+
if (RAND_bytes(seed, sizeof(seed)) != 1) {
|
|
369
|
+
ret = PQ_ERROR_RANDOM;
|
|
295
370
|
goto cleanup;
|
|
296
371
|
}
|
|
297
372
|
|
|
298
|
-
ret =
|
|
373
|
+
ret = xwing_expand_secret_key(&expanded, seed);
|
|
299
374
|
if (ret != PQ_SUCCESS) {
|
|
300
375
|
goto cleanup;
|
|
301
376
|
}
|
|
302
377
|
|
|
378
|
+
memcpy(pk.mlkem_pk, expanded.mlkem_pk, MLKEM_PUBLICKEYBYTES);
|
|
379
|
+
memcpy(pk.x25519_pk, expanded.x25519_pk, X25519_PUBLICKEYBYTES);
|
|
303
380
|
memcpy(public_key, &pk, HYBRID_PUBLICKEYBYTES);
|
|
304
|
-
memcpy(secret_key,
|
|
381
|
+
memcpy(secret_key, seed, HYBRID_SECRETKEYBYTES);
|
|
305
382
|
|
|
306
383
|
cleanup:
|
|
307
|
-
pq_secure_wipe(
|
|
308
|
-
|
|
309
|
-
pq_secure_wipe(&pk, sizeof(pk));
|
|
384
|
+
pq_secure_wipe(seed, sizeof(seed));
|
|
385
|
+
pq_secure_wipe(&expanded, sizeof(expanded));
|
|
310
386
|
return ret;
|
|
311
387
|
}
|
|
312
388
|
|
|
@@ -357,20 +433,15 @@ cleanup:
|
|
|
357
433
|
pq_secure_wipe(mlkem_ss, sizeof(mlkem_ss));
|
|
358
434
|
pq_secure_wipe(x25519_ss, sizeof(x25519_ss));
|
|
359
435
|
pq_secure_wipe(x25519_ephemeral_sk, sizeof(x25519_ephemeral_sk));
|
|
360
|
-
pq_secure_wipe(&pk, sizeof(pk));
|
|
361
|
-
pq_secure_wipe(&ct, sizeof(ct));
|
|
362
436
|
return ret;
|
|
363
437
|
}
|
|
364
438
|
|
|
365
439
|
int pq_hybrid_kem_decapsulate(uint8_t *shared_secret, const uint8_t *ciphertext,
|
|
366
440
|
const uint8_t *secret_key) {
|
|
367
441
|
hybrid_ciphertext_t ct;
|
|
368
|
-
|
|
369
|
-
uint8_t recipient_x25519_pk[X25519_PUBLICKEYBYTES];
|
|
442
|
+
hybrid_expanded_secret_key_t expanded;
|
|
370
443
|
uint8_t mlkem_ss[MLKEM_SHAREDSECRETBYTES];
|
|
371
444
|
uint8_t x25519_ss[X25519_SHAREDSECRETBYTES];
|
|
372
|
-
EVP_PKEY *pkey = NULL;
|
|
373
|
-
size_t pklen = X25519_PUBLICKEYBYTES;
|
|
374
445
|
int ret = PQ_SUCCESS;
|
|
375
446
|
|
|
376
447
|
if (!shared_secret || !ciphertext || !secret_key) {
|
|
@@ -378,44 +449,34 @@ int pq_hybrid_kem_decapsulate(uint8_t *shared_secret, const uint8_t *ciphertext,
|
|
|
378
449
|
}
|
|
379
450
|
|
|
380
451
|
memcpy(&ct, ciphertext, HYBRID_CIPHERTEXTBYTES);
|
|
381
|
-
|
|
382
|
-
memset(recipient_x25519_pk, 0, sizeof(recipient_x25519_pk));
|
|
452
|
+
memset(&expanded, 0, sizeof(expanded));
|
|
383
453
|
memset(mlkem_ss, 0, sizeof(mlkem_ss));
|
|
384
454
|
memset(x25519_ss, 0, sizeof(x25519_ss));
|
|
385
455
|
|
|
386
|
-
|
|
387
|
-
ret = PQ_ERROR_DECAPSULATE;
|
|
388
|
-
goto cleanup;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
ret = x25519_shared_secret(x25519_ss, ct.x25519_ephemeral, sk.x25519_sk);
|
|
456
|
+
ret = xwing_expand_secret_key(&expanded, secret_key);
|
|
392
457
|
if (ret != PQ_SUCCESS) {
|
|
393
458
|
ret = PQ_ERROR_DECAPSULATE;
|
|
394
459
|
goto cleanup;
|
|
395
460
|
}
|
|
396
461
|
|
|
397
|
-
|
|
398
|
-
if (!pkey) {
|
|
462
|
+
if (PQCLEAN_MLKEM768_CLEAN_crypto_kem_dec(mlkem_ss, ct.mlkem_ct, expanded.mlkem_sk) != 0) {
|
|
399
463
|
ret = PQ_ERROR_DECAPSULATE;
|
|
400
464
|
goto cleanup;
|
|
401
465
|
}
|
|
402
|
-
|
|
403
|
-
|
|
466
|
+
|
|
467
|
+
ret = x25519_shared_secret(x25519_ss, ct.x25519_ephemeral, expanded.x25519_sk);
|
|
468
|
+
if (ret != PQ_SUCCESS) {
|
|
404
469
|
ret = PQ_ERROR_DECAPSULATE;
|
|
405
470
|
goto cleanup;
|
|
406
471
|
}
|
|
407
472
|
|
|
408
|
-
ret =
|
|
409
|
-
|
|
473
|
+
ret =
|
|
474
|
+
xwing_combiner(shared_secret, mlkem_ss, x25519_ss, ct.x25519_ephemeral, expanded.x25519_pk);
|
|
410
475
|
|
|
411
476
|
cleanup:
|
|
412
|
-
if (pkey)
|
|
413
|
-
EVP_PKEY_free(pkey);
|
|
414
|
-
pq_secure_wipe(recipient_x25519_pk, sizeof(recipient_x25519_pk));
|
|
415
477
|
pq_secure_wipe(mlkem_ss, sizeof(mlkem_ss));
|
|
416
478
|
pq_secure_wipe(x25519_ss, sizeof(x25519_ss));
|
|
417
|
-
pq_secure_wipe(&
|
|
418
|
-
pq_secure_wipe(&sk, sizeof(sk));
|
|
479
|
+
pq_secure_wipe(&expanded, sizeof(expanded));
|
|
419
480
|
return ret;
|
|
420
481
|
}
|
|
421
482
|
|
|
@@ -429,8 +490,7 @@ cleanup:
|
|
|
429
490
|
|
|
430
491
|
static const char PQC_OID_ML_KEM_768[] = "2.25.186599352125448088867056807454444238446";
|
|
431
492
|
|
|
432
|
-
static const char PQC_OID_ML_KEM_768_X25519_XWING[] =
|
|
433
|
-
"2.25.318532651283923671095712569430174917109";
|
|
493
|
+
static const char PQC_OID_ML_KEM_768_X25519_XWING[] = "1.3.6.1.4.1.62253.25722";
|
|
434
494
|
static const char PQC_OID_ML_DSA_65[] = "2.25.305232938483772195555080795650659207792";
|
|
435
495
|
static const char PQC_PUBLIC_KEY_PEM_LABEL[] = "PQC PUBLIC KEY CONTAINER";
|
|
436
496
|
static const char PQC_PRIVATE_KEY_PEM_LABEL[] = "PQC PRIVATE KEY CONTAINER";
|
|
@@ -899,5 +959,5 @@ int pq_secret_key_from_pqc_container_pem(char **algorithm_out, uint8_t **key_out
|
|
|
899
959
|
}
|
|
900
960
|
|
|
901
961
|
const char *pq_version(void) {
|
|
902
|
-
return "0.3.
|
|
962
|
+
return "0.3.2";
|
|
903
963
|
}
|
|
@@ -23,9 +23,11 @@
|
|
|
23
23
|
#define X25519_PUBLICKEYBYTES 32
|
|
24
24
|
#define X25519_SECRETKEYBYTES 32
|
|
25
25
|
#define X25519_SHAREDSECRETBYTES 32
|
|
26
|
+
#define XWING_SEEDBYTES 32
|
|
27
|
+
#define XWING_EXPANDEDBYTES 96
|
|
26
28
|
|
|
27
29
|
#define HYBRID_PUBLICKEYBYTES (MLKEM_PUBLICKEYBYTES + X25519_PUBLICKEYBYTES)
|
|
28
|
-
#define HYBRID_SECRETKEYBYTES
|
|
30
|
+
#define HYBRID_SECRETKEYBYTES XWING_SEEDBYTES
|
|
29
31
|
#define HYBRID_CIPHERTEXTBYTES (MLKEM_CIPHERTEXTBYTES + X25519_PUBLICKEYBYTES)
|
|
30
32
|
#define HYBRID_SHAREDSECRETBYTES 32
|
|
31
33
|
|
|
@@ -48,10 +50,16 @@ typedef struct {
|
|
|
48
50
|
uint8_t x25519_pk[X25519_PUBLICKEYBYTES];
|
|
49
51
|
} hybrid_public_key_t;
|
|
50
52
|
|
|
53
|
+
typedef struct {
|
|
54
|
+
uint8_t seed[XWING_SEEDBYTES];
|
|
55
|
+
} hybrid_secret_key_t;
|
|
56
|
+
|
|
51
57
|
typedef struct {
|
|
52
58
|
uint8_t mlkem_sk[MLKEM_SECRETKEYBYTES];
|
|
53
59
|
uint8_t x25519_sk[X25519_SECRETKEYBYTES];
|
|
54
|
-
|
|
60
|
+
uint8_t mlkem_pk[MLKEM_PUBLICKEYBYTES];
|
|
61
|
+
uint8_t x25519_pk[X25519_PUBLICKEYBYTES];
|
|
62
|
+
} hybrid_expanded_secret_key_t;
|
|
55
63
|
|
|
56
64
|
typedef struct {
|
|
57
65
|
uint8_t mlkem_ct[MLKEM_CIPHERTEXTBYTES];
|
|
@@ -136,6 +144,22 @@ const char *pq_version(void);
|
|
|
136
144
|
#define PQ_MLDSA_PUBLICKEYBYTES MLDSA_PUBLICKEYBYTES
|
|
137
145
|
#define PQ_MLDSA_SECRETKEYBYTES MLDSA_SECRETKEYBYTES
|
|
138
146
|
#define PQ_MLDSA_BYTES MLDSA_BYTES
|
|
147
|
+
#define PQ_MLDSA_MUBYTES 64
|
|
148
|
+
#define PQ_MLDSA_TRBYTES 64
|
|
149
|
+
|
|
150
|
+
int pq_mldsa_extract_tr_from_secret_key(uint8_t *tr_out, const uint8_t *secret_key);
|
|
151
|
+
int pq_mldsa_compute_tr_from_public_key(uint8_t *tr_out, const uint8_t *public_key);
|
|
152
|
+
|
|
153
|
+
int pq_sign_mu(uint8_t *signature, size_t *signature_len,
|
|
154
|
+
const uint8_t *mu, const uint8_t *secret_key);
|
|
155
|
+
int pq_verify_mu(const uint8_t *signature, size_t signature_len,
|
|
156
|
+
const uint8_t *mu, const uint8_t *public_key);
|
|
157
|
+
void *pq_mu_builder_new(void);
|
|
158
|
+
int pq_mu_builder_init(void *state, const uint8_t *tr,
|
|
159
|
+
const uint8_t *ctx, size_t ctxlen);
|
|
160
|
+
int pq_mu_builder_absorb(void *state, const uint8_t *chunk, size_t chunk_len);
|
|
161
|
+
int pq_mu_builder_finalize(void *state, uint8_t *mu_out);
|
|
162
|
+
void pq_mu_builder_release(void *state);
|
|
139
163
|
|
|
140
164
|
int pq_hybrid_kem_keypair(uint8_t *public_key, uint8_t *secret_key);
|
|
141
165
|
int pq_hybrid_kem_encapsulate(uint8_t *ciphertext, uint8_t *shared_secret,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# This Makefile can be used with GNU Make or BSD Make
|
|
2
|
+
|
|
3
|
+
LIB=libml-kem-768_clean.a
|
|
4
|
+
HEADERS=api.h cbd.h indcpa.h kem.h ntt.h params.h poly.h polyvec.h reduce.h symmetric.h verify.h
|
|
5
|
+
OBJECTS=cbd.o indcpa.o kem.o ntt.o poly.o polyvec.o reduce.o symmetric-shake.o verify.o
|
|
6
|
+
|
|
7
|
+
CFLAGS=-O3 -Wall -Wextra -Wpedantic -Werror -Wmissing-prototypes -Wredundant-decls -std=c99 -I../../../common $(EXTRAFLAGS)
|
|
8
|
+
|
|
9
|
+
all: $(LIB)
|
|
10
|
+
|
|
11
|
+
%.o: %.c $(HEADERS)
|
|
12
|
+
$(CC) $(CFLAGS) -c -o $@ $<
|
|
13
|
+
|
|
14
|
+
$(LIB): $(OBJECTS)
|
|
15
|
+
$(AR) -r $@ $(OBJECTS)
|
|
16
|
+
|
|
17
|
+
clean:
|
|
18
|
+
$(RM) $(OBJECTS)
|
|
19
|
+
$(RM) $(LIB)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# This Makefile can be used with GNU Make or BSD Make
|
|
2
|
+
|
|
3
|
+
LIB=libml-dsa-65_clean.a
|
|
4
|
+
HEADERS=api.h ntt.h packing.h params.h poly.h polyvec.h reduce.h rounding.h sign.h symmetric.h
|
|
5
|
+
OBJECTS=ntt.o packing.o poly.o polyvec.o reduce.o rounding.o sign.o symmetric-shake.o
|
|
6
|
+
|
|
7
|
+
CFLAGS=-O3 -Wall -Wextra -Wpedantic -Werror -Wmissing-prototypes -Wredundant-decls -std=c99 -I../../../common $(EXTRAFLAGS)
|
|
8
|
+
|
|
9
|
+
all: $(LIB)
|
|
10
|
+
|
|
11
|
+
%.o: %.c $(HEADERS)
|
|
12
|
+
$(CC) $(CFLAGS) -c -o $@ $<
|
|
13
|
+
|
|
14
|
+
$(LIB): $(OBJECTS)
|
|
15
|
+
$(AR) -r $@ $(OBJECTS)
|
|
16
|
+
|
|
17
|
+
clean:
|
|
18
|
+
$(RM) $(OBJECTS)
|
|
19
|
+
$(RM) $(LIB)
|
data/lib/pq_crypto/kem.rb
CHANGED
|
@@ -184,11 +184,11 @@ module PQCrypto
|
|
|
184
184
|
alias eql? ==
|
|
185
185
|
|
|
186
186
|
def hash
|
|
187
|
-
|
|
187
|
+
object_id.hash
|
|
188
188
|
end
|
|
189
189
|
|
|
190
|
-
def
|
|
191
|
-
|
|
190
|
+
def inspect
|
|
191
|
+
"#<#{self.class}:0x#{object_id.to_s(16)} algorithm=#{algorithm.inspect}>"
|
|
192
192
|
end
|
|
193
193
|
|
|
194
194
|
private
|
|
@@ -206,6 +206,10 @@ module PQCrypto
|
|
|
206
206
|
@ciphertext = String(ciphertext).b
|
|
207
207
|
@shared_secret = String(shared_secret).b
|
|
208
208
|
end
|
|
209
|
+
|
|
210
|
+
def inspect
|
|
211
|
+
"#<#{self.class}:0x#{object_id.to_s(16)} ciphertext_bytes=#{@ciphertext.bytesize} shared_secret_bytes=#{@shared_secret.bytesize}>"
|
|
212
|
+
end
|
|
209
213
|
end
|
|
210
214
|
end
|
|
211
215
|
end
|
data/lib/pq_crypto/signature.rb
CHANGED
|
@@ -74,6 +74,95 @@ module PQCrypto
|
|
|
74
74
|
|
|
75
75
|
raise UnsupportedAlgorithmError, "Unsupported signature algorithm: #{algorithm.inspect}"
|
|
76
76
|
end
|
|
77
|
+
|
|
78
|
+
def _streaming_sign(secret_key, io, chunk_size, context)
|
|
79
|
+
validate_chunk_size!(chunk_size)
|
|
80
|
+
validate_context!(context)
|
|
81
|
+
validate_io!(io)
|
|
82
|
+
|
|
83
|
+
sk_bytes = secret_key.__send__(:bytes_for_native)
|
|
84
|
+
begin
|
|
85
|
+
tr = PQCrypto.__send__(:_native_mldsa_extract_tr, sk_bytes)
|
|
86
|
+
rescue ArgumentError => e
|
|
87
|
+
raise InvalidKeyError, e.message
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
builder = PQCrypto.__send__(:_native_mldsa_mu_builder_new, tr, context.b)
|
|
91
|
+
builder_consumed = false
|
|
92
|
+
mu = nil
|
|
93
|
+
begin
|
|
94
|
+
_drain_io_into_builder(io, builder, chunk_size)
|
|
95
|
+
mu = PQCrypto.__send__(:_native_mldsa_mu_builder_finalize, builder)
|
|
96
|
+
builder_consumed = true
|
|
97
|
+
PQCrypto.__send__(:_native_mldsa_sign_mu, mu, sk_bytes)
|
|
98
|
+
ensure
|
|
99
|
+
PQCrypto.__send__(:_native_mldsa_mu_builder_release, builder) unless builder_consumed
|
|
100
|
+
PQCrypto.secure_wipe(tr) if tr && !tr.frozen?
|
|
101
|
+
PQCrypto.secure_wipe(mu) if mu && !mu.frozen?
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def _streaming_verify(public_key, io, signature, chunk_size, context)
|
|
106
|
+
validate_chunk_size!(chunk_size)
|
|
107
|
+
validate_context!(context)
|
|
108
|
+
validate_io!(io)
|
|
109
|
+
|
|
110
|
+
pk_bytes = public_key.__send__(:bytes_for_native)
|
|
111
|
+
begin
|
|
112
|
+
tr = PQCrypto.__send__(:_native_mldsa_compute_tr, pk_bytes)
|
|
113
|
+
rescue ArgumentError => e
|
|
114
|
+
raise InvalidKeyError, e.message
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
builder = PQCrypto.__send__(:_native_mldsa_mu_builder_new, tr, context.b)
|
|
118
|
+
builder_consumed = false
|
|
119
|
+
mu = nil
|
|
120
|
+
sig_bytes = String(signature).b
|
|
121
|
+
begin
|
|
122
|
+
_drain_io_into_builder(io, builder, chunk_size)
|
|
123
|
+
mu = PQCrypto.__send__(:_native_mldsa_mu_builder_finalize, builder)
|
|
124
|
+
builder_consumed = true
|
|
125
|
+
PQCrypto.__send__(:_native_mldsa_verify_mu, mu, sig_bytes, pk_bytes)
|
|
126
|
+
ensure
|
|
127
|
+
PQCrypto.__send__(:_native_mldsa_mu_builder_release, builder) unless builder_consumed
|
|
128
|
+
|
|
129
|
+
PQCrypto.secure_wipe(tr) if tr && !tr.frozen?
|
|
130
|
+
PQCrypto.secure_wipe(mu) if mu && !mu.frozen?
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def _drain_io_into_builder(io, builder, chunk_size)
|
|
135
|
+
buffer = String.new(capacity: chunk_size).b
|
|
136
|
+
loop do
|
|
137
|
+
result = io.read(chunk_size, buffer)
|
|
138
|
+
break if result.nil?
|
|
139
|
+
|
|
140
|
+
chunk = result.equal?(buffer) ? buffer : result
|
|
141
|
+
chunk_bytes = chunk.encoding == Encoding::BINARY ? chunk : chunk.b
|
|
142
|
+
break if chunk_bytes.bytesize.zero?
|
|
143
|
+
|
|
144
|
+
PQCrypto.__send__(:_native_mldsa_mu_builder_update, builder, chunk_bytes)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def validate_io!(io)
|
|
149
|
+
unless io.respond_to?(:read)
|
|
150
|
+
raise ArgumentError, "io must respond to #read"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def validate_chunk_size!(chunk_size)
|
|
155
|
+
unless chunk_size.is_a?(Integer) && chunk_size > 0
|
|
156
|
+
raise ArgumentError, "chunk_size must be a positive Integer"
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def validate_context!(context)
|
|
161
|
+
ctx = String(context).b
|
|
162
|
+
if ctx.bytesize > 255
|
|
163
|
+
raise ArgumentError, "context must be at most 255 bytes (FIPS 204)"
|
|
164
|
+
end
|
|
165
|
+
end
|
|
77
166
|
end
|
|
78
167
|
|
|
79
168
|
class Keypair
|
|
@@ -125,6 +214,17 @@ module PQCrypto
|
|
|
125
214
|
true
|
|
126
215
|
end
|
|
127
216
|
|
|
217
|
+
def verify_io(io, signature, chunk_size: 1 << 20, context: "".b)
|
|
218
|
+
Signature.send(:_streaming_verify, self, io, signature, chunk_size, context)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def verify_io!(io, signature, chunk_size: 1 << 20, context: "".b)
|
|
222
|
+
unless verify_io(io, signature, chunk_size: chunk_size, context: context)
|
|
223
|
+
raise PQCrypto::VerificationError, "Verification failed"
|
|
224
|
+
end
|
|
225
|
+
true
|
|
226
|
+
end
|
|
227
|
+
|
|
128
228
|
def ==(other)
|
|
129
229
|
return false unless other.is_a?(PublicKey) && other.algorithm == algorithm
|
|
130
230
|
PQCrypto.__send__(:native_ct_equals, other.to_bytes, @bytes)
|
|
@@ -142,6 +242,10 @@ module PQCrypto
|
|
|
142
242
|
|
|
143
243
|
private
|
|
144
244
|
|
|
245
|
+
def bytes_for_native
|
|
246
|
+
@bytes
|
|
247
|
+
end
|
|
248
|
+
|
|
145
249
|
def validate_length!
|
|
146
250
|
expected = Signature.details(@algorithm).fetch(:public_key_bytes)
|
|
147
251
|
raise InvalidKeyError, "Invalid signature public key length" unless @bytes.bytesize == expected
|
|
@@ -175,6 +279,10 @@ module PQCrypto
|
|
|
175
279
|
raise InvalidKeyError, e.message
|
|
176
280
|
end
|
|
177
281
|
|
|
282
|
+
def sign_io(io, chunk_size: 1 << 20, context: "".b)
|
|
283
|
+
Signature.send(:_streaming_sign, self, io, chunk_size, context)
|
|
284
|
+
end
|
|
285
|
+
|
|
178
286
|
def wipe!
|
|
179
287
|
PQCrypto.secure_wipe(@bytes)
|
|
180
288
|
self
|
|
@@ -188,15 +296,19 @@ module PQCrypto
|
|
|
188
296
|
alias eql? ==
|
|
189
297
|
|
|
190
298
|
def hash
|
|
191
|
-
|
|
299
|
+
object_id.hash
|
|
192
300
|
end
|
|
193
301
|
|
|
194
|
-
def
|
|
195
|
-
|
|
302
|
+
def inspect
|
|
303
|
+
"#<#{self.class}:0x#{object_id.to_s(16)} algorithm=#{algorithm.inspect}>"
|
|
196
304
|
end
|
|
197
305
|
|
|
198
306
|
private
|
|
199
307
|
|
|
308
|
+
def bytes_for_native
|
|
309
|
+
@bytes
|
|
310
|
+
end
|
|
311
|
+
|
|
200
312
|
def validate_length!
|
|
201
313
|
expected = Signature.details(@algorithm).fetch(:secret_key_bytes)
|
|
202
314
|
raise InvalidKeyError, "Invalid signature secret key length" unless @bytes.bytesize == expected
|
data/lib/pq_crypto/version.rb
CHANGED
data/lib/pq_crypto.rb
CHANGED
|
@@ -69,6 +69,17 @@ module PQCrypto
|
|
|
69
69
|
__test_sign_from_seed
|
|
70
70
|
].freeze
|
|
71
71
|
|
|
72
|
+
EXTERNAL_MU_METHODS = %i[
|
|
73
|
+
_native_mldsa_extract_tr
|
|
74
|
+
_native_mldsa_compute_tr
|
|
75
|
+
_native_mldsa_mu_builder_new
|
|
76
|
+
_native_mldsa_mu_builder_update
|
|
77
|
+
_native_mldsa_mu_builder_finalize
|
|
78
|
+
_native_mldsa_mu_builder_release
|
|
79
|
+
_native_mldsa_sign_mu
|
|
80
|
+
_native_mldsa_verify_mu
|
|
81
|
+
].freeze
|
|
82
|
+
|
|
72
83
|
class << PQCrypto
|
|
73
84
|
NativeBindings::NATIVE_METHODS.each do |name|
|
|
74
85
|
alias_name = :"native_#{name.to_s.sub(/\A__/, '')}"
|
|
@@ -78,6 +89,7 @@ module PQCrypto
|
|
|
78
89
|
|
|
79
90
|
private(*NativeBindings::NATIVE_METHODS)
|
|
80
91
|
private(*NativeBindings::NATIVE_METHODS.map { |n| :"native_#{n.to_s.sub(/\A__/, '')}" })
|
|
92
|
+
private(*NativeBindings::EXTERNAL_MU_METHODS)
|
|
81
93
|
end
|
|
82
94
|
end
|
|
83
95
|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pq_crypto
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Roman Haydarov
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: rake
|
|
@@ -69,6 +69,7 @@ files:
|
|
|
69
69
|
- ext/pqcrypto/extconf.rb
|
|
70
70
|
- ext/pqcrypto/mldsa_api.h
|
|
71
71
|
- ext/pqcrypto/mlkem_api.h
|
|
72
|
+
- ext/pqcrypto/pq_externalmu.c
|
|
72
73
|
- ext/pqcrypto/pq_randombytes.c
|
|
73
74
|
- ext/pqcrypto/pqcrypto_ruby_secure.c
|
|
74
75
|
- ext/pqcrypto/pqcrypto_secure.c
|
|
@@ -86,6 +87,7 @@ files:
|
|
|
86
87
|
- ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-times4-SIMD256.c
|
|
87
88
|
- ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-times4-SnP.h
|
|
88
89
|
- ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-unrolling.macros
|
|
90
|
+
- ext/pqcrypto/vendor/pqclean/common/keccak4x/Makefile
|
|
89
91
|
- ext/pqcrypto/vendor/pqclean/common/keccak4x/Makefile.Microsoft_nmake
|
|
90
92
|
- ext/pqcrypto/vendor/pqclean/common/keccak4x/SIMD256-config.h
|
|
91
93
|
- ext/pqcrypto/vendor/pqclean/common/keccak4x/align.h
|
|
@@ -99,6 +101,7 @@ files:
|
|
|
99
101
|
- ext/pqcrypto/vendor/pqclean/common/sp800-185.c
|
|
100
102
|
- ext/pqcrypto/vendor/pqclean/common/sp800-185.h
|
|
101
103
|
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/LICENSE
|
|
104
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/Makefile
|
|
102
105
|
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/Makefile.Microsoft_nmake
|
|
103
106
|
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/api.h
|
|
104
107
|
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/cbd.c
|
|
@@ -121,6 +124,7 @@ files:
|
|
|
121
124
|
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/verify.c
|
|
122
125
|
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/verify.h
|
|
123
126
|
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/LICENSE
|
|
127
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/Makefile
|
|
124
128
|
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/Makefile.Microsoft_nmake
|
|
125
129
|
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/api.h
|
|
126
130
|
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/ntt.c
|
|
@@ -163,14 +167,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
163
167
|
requirements:
|
|
164
168
|
- - ">="
|
|
165
169
|
- !ruby/object:Gem::Version
|
|
166
|
-
version: 3.4.0
|
|
170
|
+
version: 3.4.0
|
|
167
171
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
168
172
|
requirements:
|
|
169
173
|
- - ">="
|
|
170
174
|
- !ruby/object:Gem::Version
|
|
171
175
|
version: '0'
|
|
172
176
|
requirements: []
|
|
173
|
-
rubygems_version: 3.6.
|
|
177
|
+
rubygems_version: 3.6.7
|
|
174
178
|
specification_version: 4
|
|
175
179
|
summary: Primitive-first post-quantum cryptography for Ruby
|
|
176
180
|
test_files: []
|