pq_crypto 0.6.6 → 0.6.8
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 +108 -0
- data/README.md +11 -0
- data/SECURITY.md +46 -0
- data/ext/pqcrypto/extconf.rb +4 -0
- data/ext/pqcrypto/pqcrypto_native_api.h +6 -0
- data/ext/pqcrypto/pqcrypto_ruby_secure.c +122 -11
- data/ext/pqcrypto/pqcrypto_secure.c +553 -296
- data/ext/pqcrypto/pqcrypto_secure.h +14 -1
- data/ext/pqcrypto/pqcrypto_version.h +1 -1
- data/lib/pq_crypto/internal.rb +20 -0
- data/lib/pq_crypto/kem.rb +52 -15
- data/lib/pq_crypto/key.rb +14 -8
- data/lib/pq_crypto/pkcs8/der.rb +26 -11
- data/lib/pq_crypto/pkcs8/private_key_choice.rb +21 -25
- data/lib/pq_crypto/pkcs8.rb +13 -5
- data/lib/pq_crypto/signature.rb +61 -48
- data/lib/pq_crypto/spki.rb +39 -24
- data/lib/pq_crypto/version.rb +1 -1
- data/lib/pq_crypto.rb +9 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: db8e5b65d14cc5dcc4950a2da14b781efcf4c8dd17774cffc076e9d5591509ca
|
|
4
|
+
data.tar.gz: '09cf8c3e990a416ab0f8f77fe2a2a6067fdd8a3f4d00607a4162e4259d6044bd'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 81a8509efb9d280100975a8e4a9a0a6aab70061b49bbc48a396de490723c316abe63442cfb041c68dbeefd9383fba647b198318082723ffb9bace9d0ffd12c78
|
|
7
|
+
data.tar.gz: 1055665295f23df22a53880fbf02a2e8d0c0cb33f1b65a43b1fe2f1f040da829bcaf4895440093addc432f15fb9c21792f28bf41e2949c90b9c93bc2784e5e93
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,113 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.8] - 2026-10-28
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Refactored Ruby serialization and streaming paths.** Split SPKI and PKCS#8
|
|
8
|
+
decoding into focused validation steps, shared the seed/expanded-key codecs,
|
|
9
|
+
and consolidated ML-DSA streaming builder cleanup. SPKI algorithm matching
|
|
10
|
+
now uses one shared validator. Public APIs and encoded formats are unchanged.
|
|
11
|
+
|
|
12
|
+
## [0.6.7] - 2026-08-10
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **DER parsers could raise exceptions outside the `PQCrypto::Error` hierarchy.**
|
|
17
|
+
`OpenSSL::ASN1.decode` raises more than `OpenSSL::ASN1::ASN1Error`, and
|
|
18
|
+
re-encoding a successfully decoded object can fail as well. Three shapes
|
|
19
|
+
escaped the previous handling in `PQCrypto::SPKI`:
|
|
20
|
+
|
|
21
|
+
- `"\x0A\x01\xFF"` (ENUMERATED) raised `OpenSSL::OpenSSLError`
|
|
22
|
+
- `"\x17\x00"` / `"\x18\x00"` (empty UTCTIME / GENERALIZEDTIME) raised `TypeError`
|
|
23
|
+
- `"\x10\x00"` (universal SEQUENCE tag without the constructed bit) decoded
|
|
24
|
+
cleanly but raised `TypeError` from the `to_der` round trip used for the
|
|
25
|
+
trailing-data check
|
|
26
|
+
|
|
27
|
+
`TypeError` does not descend from `OpenSSL::OpenSSLError`, so widening the
|
|
28
|
+
rescue to the OpenSSL base class alone was not sufficient. Both rescue sites
|
|
29
|
+
now catch `OpenSSL::OpenSSLError, TypeError`, and `SPKI.decode_der` is guarded
|
|
30
|
+
end to end rather than only around the decode call.
|
|
31
|
+
|
|
32
|
+
Impact: code written as `rescue PQCrypto::Error` around key loading could be
|
|
33
|
+
bypassed by malformed input. No memory-safety consequence. Covered by a
|
|
34
|
+
systematic tag sweep over 1,280 DER shapes × three entry points
|
|
35
|
+
(`Key.from_der`, `SPKI.decode_der`, `PKCS8.decode_der`).
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- **FIPS 203 key validation for ML-KEM (boundary diagnostics).** mlkem-native
|
|
40
|
+
v2 exposes `check_pk` and `check_sk`, which this gem did not use. Both are
|
|
41
|
+
now wired in and run when a `PQCrypto::KEM::PublicKey` or `SecretKey` is
|
|
42
|
+
constructed:
|
|
43
|
+
|
|
44
|
+
- public keys get the Section 7.2 modulus check (coefficients in `[0,q-1]`)
|
|
45
|
+
- secret keys get the Section 7.3 hash check (`H(pk)` embedded in the sk)
|
|
46
|
+
|
|
47
|
+
Scope limits (intentional, matching FIPS 203):
|
|
48
|
+
|
|
49
|
+
- these checks are **diagnostics at import**, not a substitute for the
|
|
50
|
+
checks already performed inside encapsulation / decapsulation
|
|
51
|
+
- Section 7.3 does **not** detect a corrupted IND-CPA secret (`dk`) when
|
|
52
|
+
the embedded `H(pk)` is still intact — `#valid?` can still be true
|
|
53
|
+
- X-Wing public keys validate only the ML-KEM-768 half; the X25519 half is
|
|
54
|
+
unrestricted. X-Wing secret keys are 32-byte seeds and are not structure-
|
|
55
|
+
checked
|
|
56
|
+
|
|
57
|
+
`#valid?` is exposed on both key classes. Failures raise
|
|
58
|
+
`PQCrypto::InvalidKeyError`. Public-key internal buffers are frozen after
|
|
59
|
+
construction to block post-validation mutation; secret-key buffers stay
|
|
60
|
+
mutable so `#wipe!` can still zero them in place.
|
|
61
|
+
|
|
62
|
+
New C API: `pq_mlkem{,512,1024}_check_public_key` / `_check_secret_key`,
|
|
63
|
+
plus `PQ_ERROR_INVALID_PUBLIC_KEY` (-11) and `PQ_ERROR_INVALID_SECRET_KEY`
|
|
64
|
+
(-12). Upstream OOM (`-2`) is mapped to `PQ_ERROR_NOMEM` rather than
|
|
65
|
+
“invalid key”.
|
|
66
|
+
|
|
67
|
+
- **ML-DSA secret-key structure check on import.** Expanded ML-DSA secret
|
|
68
|
+
keys are validated via upstream `pk_from_sk` (coefficient norms, recomputed
|
|
69
|
+
`t0`, and `tr = H(pk)`). This matches the check mldsa-native documents for
|
|
70
|
+
importers and is enforced on `Signature::SecretKey` construction with
|
|
71
|
+
`#valid?` exposed. Public keys remain length-checked only (FIPS 204 has no
|
|
72
|
+
ML-KEM-style modulus check for the public key).
|
|
73
|
+
|
|
74
|
+
The check runs on import only. Keys produced by this process's own key
|
|
75
|
+
generation are valid by construction, and `pk_from_sk` re-derives the public
|
|
76
|
+
key, so validating them would roughly double the cost of every ML-DSA
|
|
77
|
+
keypair. Upstream also documents `pk_from_sk` as leaking secret-key validity
|
|
78
|
+
through its return value and timing.
|
|
79
|
+
|
|
80
|
+
New C API: `pq_mldsa{,44,87}_check_secret_key`.
|
|
81
|
+
|
|
82
|
+
- **`PQCRYPTO_KEYGEN_PCT=1` build option.** Enables the FIPS 140-3 pairwise
|
|
83
|
+
consistency test in both backends (`MLK_CONFIG_KEYGEN_PCT` /
|
|
84
|
+
`MLD_CONFIG_KEYGEN_PCT`): after generating a keypair it is exercised to catch
|
|
85
|
+
a faulty RNG or memory corruption at generation time. Upstream ships this
|
|
86
|
+
opt-in because it makes key generation noticeably more expensive, and it stays
|
|
87
|
+
opt-in here for the same reason. The selected setting is reported in the
|
|
88
|
+
build configuration banner.
|
|
89
|
+
|
|
90
|
+
- **`require_encrypted:` for PKCS#8 loading.** Supplying a passphrase states an
|
|
91
|
+
expectation that the key at rest is encrypted, but an unencrypted
|
|
92
|
+
PrivateKeyInfo was previously accepted and the passphrase left unused. An
|
|
93
|
+
encrypted key file replaced with a plaintext one therefore loaded without
|
|
94
|
+
complaint.
|
|
95
|
+
|
|
96
|
+
`require_encrypted: true` enforces that the input is an
|
|
97
|
+
EncryptedPrivateKeyInfo (DER content, not PEM label — relabeling a plaintext
|
|
98
|
+
key as `ENCRYPTED PRIVATE KEY` does not satisfy the policy). Available on
|
|
99
|
+
`PKCS8.decode_der` / `decode_pem`, `Key.from_der` / `from_pem`, and
|
|
100
|
+
`secret_key_from_pkcs8_der` / `_pem` on both `KEM` and `Signature`.
|
|
101
|
+
|
|
102
|
+
Limits (documented so the flag is not over-read):
|
|
103
|
+
|
|
104
|
+
- default is `false`; other loaders (`from_bytes`, `pqc_container_*`) are
|
|
105
|
+
unchanged
|
|
106
|
+
- the flag checks **format**, not passphrase strength (empty passphrase and
|
|
107
|
+
low PBKDF iterations still count as encrypted)
|
|
108
|
+
- the value must be a real boolean (`true`/`false`); strings like `"false"`
|
|
109
|
+
raise `ArgumentError` instead of being treated as truthy
|
|
110
|
+
|
|
3
111
|
## [0.6.6] - 2026-08-10
|
|
4
112
|
|
|
5
113
|
### Changed
|
data/README.md
CHANGED
|
@@ -101,6 +101,13 @@ can be tested explicitly with:
|
|
|
101
101
|
PQCRYPTO_NATIVE_ASM=1 bundle exec rake compile
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
The FIPS 140-3 pairwise consistency test is available but off by default,
|
|
105
|
+
because it makes key generation noticeably more expensive:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
PQCRYPTO_KEYGEN_PCT=1 bundle exec rake compile
|
|
109
|
+
```
|
|
110
|
+
|
|
104
111
|
## Security status
|
|
105
112
|
|
|
106
113
|
`pq_crypto` is experimental and not audited. Treat it as a low-level primitive
|
|
@@ -125,8 +132,12 @@ PQCrypto::Signature.secret_key_from_seed(:ml_dsa_65, seed_32_bytes)
|
|
|
125
132
|
|
|
126
133
|
PQCrypto::HybridKEM.generate(:ml_kem_768_x25519_xwing)
|
|
127
134
|
PQCrypto::Key.from_pem(pem, passphrase: passphrase)
|
|
135
|
+
PQCrypto::Key.from_pem(pem, passphrase: passphrase, require_encrypted: true)
|
|
136
|
+
keypair.public_key.valid? # ML-KEM structure check; ML-DSA public keys are length-only
|
|
137
|
+
keypair.secret_key.valid?
|
|
128
138
|
```
|
|
129
139
|
|
|
140
|
+
|
|
130
141
|
## More examples
|
|
131
142
|
|
|
132
143
|
Detailed usage examples live in [`GET_STARTED.md`](GET_STARTED.md):
|
data/SECURITY.md
CHANGED
|
@@ -127,6 +127,52 @@ into `mldsa-native` `signature_internal`; for an empty context this prefix is
|
|
|
127
127
|
Outside of test-only deterministic calls, production randomness delegates
|
|
128
128
|
directly to OpenSSL `RAND_bytes`.
|
|
129
129
|
|
|
130
|
+
## Key import validation (0.6.7+)
|
|
131
|
+
|
|
132
|
+
ML-KEM public/secret keys and ML-DSA secret keys are structure-checked when
|
|
133
|
+
the corresponding Ruby key objects are constructed:
|
|
134
|
+
|
|
135
|
+
| Object | Check | Source |
|
|
136
|
+
| --- | --- | --- |
|
|
137
|
+
| `KEM::PublicKey` | FIPS 203 §7.2 modulus check | `check_pk` |
|
|
138
|
+
| `KEM::SecretKey` | FIPS 203 §7.3 `H(pk)` check | `check_sk` |
|
|
139
|
+
| `Signature::SecretKey` | norms / `t0` / `tr` consistency | `pk_from_sk` |
|
|
140
|
+
| `Signature::PublicKey` | length only | — |
|
|
141
|
+
| X-Wing public key | ML-KEM-768 half only | `check_pk` |
|
|
142
|
+
| X-Wing secret key | none (32-byte seed) | — |
|
|
143
|
+
|
|
144
|
+
These are **import diagnostics**. Encapsulation, decapsulation, signing, and
|
|
145
|
+
verification still apply their own backend checks. A true `#valid?` result
|
|
146
|
+
does not prove full cryptographic integrity of every field (for example an
|
|
147
|
+
ML-KEM secret key with a corrupted IND-CPA `dk` but intact `H(pk)` still
|
|
148
|
+
passes §7.3).
|
|
149
|
+
|
|
150
|
+
The checks run on import. Keys produced by this process's own key generation
|
|
151
|
+
are valid by construction and are not re-checked, because the ML-DSA check
|
|
152
|
+
re-derives the public key and would roughly double the cost of every keypair.
|
|
153
|
+
|
|
154
|
+
The ML-DSA check (`pk_from_sk`) is documented upstream as leaking whether the
|
|
155
|
+
secret key is valid, through both its return value and its timing.
|
|
156
|
+
|
|
157
|
+
`require_encrypted:` applies only to the PKCS#8 branch. `PQCrypto::Key.from_der`
|
|
158
|
+
and `.from_pem` also accept SPKI public keys, and for those inputs the flag has
|
|
159
|
+
no effect and is silently ignored.
|
|
160
|
+
|
|
161
|
+
Public-key internal buffers are frozen after construction. Secret-key buffers
|
|
162
|
+
remain mutable so `#wipe!` can zero them.
|
|
163
|
+
|
|
164
|
+
### `require_encrypted:`
|
|
165
|
+
|
|
166
|
+
`require_encrypted: true` on PKCS#8 loaders rejects plaintext PrivateKeyInfo
|
|
167
|
+
and requires EncryptedPrivateKeyInfo (classified from DER, not PEM labels).
|
|
168
|
+
It does **not**:
|
|
169
|
+
|
|
170
|
+
- apply to `from_bytes` / `pqc_container_*` loaders
|
|
171
|
+
- enforce passphrase quality or PBKDF iteration count
|
|
172
|
+
- default on (default remains `false`)
|
|
173
|
+
|
|
174
|
+
The flag must be a boolean; non-boolean values raise `ArgumentError`.
|
|
175
|
+
|
|
130
176
|
## Memory wiping
|
|
131
177
|
|
|
132
178
|
`PQCrypto.secure_wipe` clears mutable Ruby strings in place. Ruby key objects
|
data/ext/pqcrypto/extconf.rb
CHANGED
|
@@ -84,6 +84,8 @@ def env_bool(name, default)
|
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
+
KEYGEN_PCT = env_bool("PQCRYPTO_KEYGEN_PCT", false)
|
|
88
|
+
|
|
87
89
|
NATIVE_ASM = env_bool("PQCRYPTO_NATIVE_ASM", native_asm_supported_by_default?)
|
|
88
90
|
NATIVE_ARITH = env_bool("PQCRYPTO_NATIVE_ARITH", NATIVE_ASM)
|
|
89
91
|
NATIVE_FIPS202 = env_bool("PQCRYPTO_NATIVE_FIPS202", NATIVE_ASM)
|
|
@@ -529,6 +531,7 @@ def native_flags(kind, level, shared:)
|
|
|
529
531
|
# Upstream has no guard against the stale define, so passing it would be a
|
|
530
532
|
# silently dead -D rather than a build error. Do not reintroduce it.
|
|
531
533
|
flags << (shared ? "-D#{prefix}_CONFIG_MULTILEVEL_WITH_SHARED" : "-D#{prefix}_CONFIG_MULTILEVEL_NO_SHARED")
|
|
534
|
+
flags << "-D#{prefix}_CONFIG_KEYGEN_PCT" if KEYGEN_PCT
|
|
532
535
|
flags << "-D#{prefix}_CONFIG_USE_NATIVE_BACKEND_ARITH" if NATIVE_ARITH
|
|
533
536
|
flags << "-D#{prefix}_CONFIG_USE_NATIVE_BACKEND_FIPS202" if NATIVE_FIPS202
|
|
534
537
|
flags.join(" ")
|
|
@@ -615,6 +618,7 @@ puts "Vendor ASM arch flags: #{VENDOR_ASM_ARCH_FLAGS.empty? ? '(none)' : VENDOR_
|
|
|
615
618
|
if x86_64_host? && (NATIVE_ARITH || NATIVE_FIPS202)
|
|
616
619
|
puts "x86_64 native backend: AVX2 build flags enabled"
|
|
617
620
|
end
|
|
621
|
+
puts "Keygen PCT (FIPS 140-3): #{KEYGEN_PCT ? 'enabled' : 'disabled'}"
|
|
618
622
|
puts "PQClean fallback: removed"
|
|
619
623
|
puts "Output: pqcrypto/pqcrypto_secure"
|
|
620
624
|
puts "===================================="
|
|
@@ -78,18 +78,24 @@ int pqcr_mlkem512_keypair_derand(uint8_t *pk, uint8_t *sk, const uint8_t *coins)
|
|
|
78
78
|
int pqcr_mlkem512_enc(uint8_t *ct, uint8_t *ss, const uint8_t *pk);
|
|
79
79
|
int pqcr_mlkem512_enc_derand(uint8_t *ct, uint8_t *ss, const uint8_t *pk, const uint8_t *coins);
|
|
80
80
|
int pqcr_mlkem512_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *sk);
|
|
81
|
+
int pqcr_mlkem512_check_pk(const uint8_t *pk);
|
|
82
|
+
int pqcr_mlkem512_check_sk(const uint8_t *sk);
|
|
81
83
|
|
|
82
84
|
int pqcr_mlkem768_keypair(uint8_t *pk, uint8_t *sk);
|
|
83
85
|
int pqcr_mlkem768_keypair_derand(uint8_t *pk, uint8_t *sk, const uint8_t *coins);
|
|
84
86
|
int pqcr_mlkem768_enc(uint8_t *ct, uint8_t *ss, const uint8_t *pk);
|
|
85
87
|
int pqcr_mlkem768_enc_derand(uint8_t *ct, uint8_t *ss, const uint8_t *pk, const uint8_t *coins);
|
|
86
88
|
int pqcr_mlkem768_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *sk);
|
|
89
|
+
int pqcr_mlkem768_check_pk(const uint8_t *pk);
|
|
90
|
+
int pqcr_mlkem768_check_sk(const uint8_t *sk);
|
|
87
91
|
|
|
88
92
|
int pqcr_mlkem1024_keypair(uint8_t *pk, uint8_t *sk);
|
|
89
93
|
int pqcr_mlkem1024_keypair_derand(uint8_t *pk, uint8_t *sk, const uint8_t *coins);
|
|
90
94
|
int pqcr_mlkem1024_enc(uint8_t *ct, uint8_t *ss, const uint8_t *pk);
|
|
91
95
|
int pqcr_mlkem1024_enc_derand(uint8_t *ct, uint8_t *ss, const uint8_t *pk, const uint8_t *coins);
|
|
92
96
|
int pqcr_mlkem1024_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *sk);
|
|
97
|
+
int pqcr_mlkem1024_check_pk(const uint8_t *pk);
|
|
98
|
+
int pqcr_mlkem1024_check_sk(const uint8_t *sk);
|
|
93
99
|
|
|
94
100
|
void pqcr_mlkem_shake256(uint8_t *output, size_t outlen, const uint8_t *input, size_t inlen);
|
|
95
101
|
void pqcr_mlkem_sha3_256(uint8_t *output, const uint8_t *input, size_t inlen);
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
#if defined(__clang__) || defined(__GNUC__)
|
|
9
9
|
#pragma GCC diagnostic pop
|
|
10
10
|
#endif
|
|
11
|
+
#include <stdbool.h>
|
|
12
|
+
#include <stdatomic.h>
|
|
11
13
|
#include <stdlib.h>
|
|
12
14
|
#include <string.h>
|
|
13
15
|
|
|
@@ -718,6 +720,12 @@ __attribute__((noreturn)) static void pq_raise_general_error(int err) {
|
|
|
718
720
|
case PQ_ERROR_OPENSSL:
|
|
719
721
|
rb_raise(ePQCryptoError, "OpenSSL error");
|
|
720
722
|
break;
|
|
723
|
+
case PQ_ERROR_INVALID_PUBLIC_KEY:
|
|
724
|
+
rb_raise(ePQCryptoError, "Invalid public key");
|
|
725
|
+
break;
|
|
726
|
+
case PQ_ERROR_INVALID_SECRET_KEY:
|
|
727
|
+
rb_raise(ePQCryptoError, "Invalid secret key");
|
|
728
|
+
break;
|
|
721
729
|
default:
|
|
722
730
|
rb_raise(ePQCryptoError, "Unknown error: %d", err);
|
|
723
731
|
break;
|
|
@@ -765,6 +773,28 @@ static VALUE pq_run_kem_keypair_from_seed(void *(*nogvl)(void *), VALUE seed, si
|
|
|
765
773
|
X(ml_kem_1024, MLKEM1024_PUBLICKEYBYTES, MLKEM1024_SECRETKEYBYTES, MLKEM1024_CIPHERTEXTBYTES, \
|
|
766
774
|
MLKEM1024_SHAREDSECRETBYTES)
|
|
767
775
|
|
|
776
|
+
static VALUE pq_run_key_check(int (*check)(const uint8_t *), VALUE key, size_t expected_len) {
|
|
777
|
+
uint8_t *buffer;
|
|
778
|
+
size_t copied_len = 0;
|
|
779
|
+
int result;
|
|
780
|
+
|
|
781
|
+
StringValue(key);
|
|
782
|
+
if ((size_t)RSTRING_LEN(key) != expected_len) {
|
|
783
|
+
return Qfalse;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
buffer = pq_copy_ruby_string(key, &copied_len);
|
|
787
|
+
result = check(buffer);
|
|
788
|
+
pq_secure_wipe(buffer, copied_len);
|
|
789
|
+
free(buffer);
|
|
790
|
+
|
|
791
|
+
if (result == PQ_ERROR_NOMEM) {
|
|
792
|
+
rb_raise(rb_eNoMemError, "Memory allocation failed");
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
return result == PQ_SUCCESS ? Qtrue : Qfalse;
|
|
796
|
+
}
|
|
797
|
+
|
|
768
798
|
#define PQ_DEFINE_RUBY_KEM(rb_name, pk_bytes, sk_bytes, ct_bytes, ss_bytes) \
|
|
769
799
|
static VALUE pqcrypto_##rb_name##_keypair(VALUE self) { \
|
|
770
800
|
(void)self; \
|
|
@@ -787,6 +817,41 @@ static VALUE pq_run_kem_keypair_from_seed(void *(*nogvl)(void *), VALUE seed, si
|
|
|
787
817
|
secret_key, sk_bytes, ss_bytes); \
|
|
788
818
|
}
|
|
789
819
|
|
|
820
|
+
#define PQ_DEFINE_RUBY_KEM_CHECK(rb_name, c_name) \
|
|
821
|
+
static VALUE pqcrypto_##rb_name##_check_public_key(VALUE self, VALUE key) { \
|
|
822
|
+
(void)self; \
|
|
823
|
+
return pq_run_key_check(pq_##c_name##_check_public_key, key, \
|
|
824
|
+
(size_t)c_name##_PUBLICKEYBYTES_VALUE); \
|
|
825
|
+
} \
|
|
826
|
+
static VALUE pqcrypto_##rb_name##_check_secret_key(VALUE self, VALUE key) { \
|
|
827
|
+
(void)self; \
|
|
828
|
+
return pq_run_key_check(pq_##c_name##_check_secret_key, key, \
|
|
829
|
+
(size_t)c_name##_SECRETKEYBYTES_VALUE); \
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
#define mlkem_PUBLICKEYBYTES_VALUE PQ_MLKEM_PUBLICKEYBYTES
|
|
833
|
+
#define mlkem_SECRETKEYBYTES_VALUE PQ_MLKEM_SECRETKEYBYTES
|
|
834
|
+
#define mlkem512_PUBLICKEYBYTES_VALUE MLKEM512_PUBLICKEYBYTES
|
|
835
|
+
#define mlkem512_SECRETKEYBYTES_VALUE MLKEM512_SECRETKEYBYTES
|
|
836
|
+
#define mlkem1024_PUBLICKEYBYTES_VALUE MLKEM1024_PUBLICKEYBYTES
|
|
837
|
+
#define mlkem1024_SECRETKEYBYTES_VALUE MLKEM1024_SECRETKEYBYTES
|
|
838
|
+
|
|
839
|
+
PQ_KEM_KEYPAIR_NOGVL_VARIANTS(PQ_DEFINE_RUBY_KEM_CHECK)
|
|
840
|
+
|
|
841
|
+
#undef PQ_DEFINE_RUBY_KEM_CHECK
|
|
842
|
+
|
|
843
|
+
#define PQ_DEFINE_RUBY_MLDSA_CHECK_SK(rb_name, c_name, sk_bytes) \
|
|
844
|
+
static VALUE pqcrypto_##rb_name##_check_secret_key(VALUE self, VALUE key) { \
|
|
845
|
+
(void)self; \
|
|
846
|
+
return pq_run_key_check(pq_##c_name##_check_secret_key, key, (size_t)(sk_bytes)); \
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
PQ_DEFINE_RUBY_MLDSA_CHECK_SK(ml_dsa, mldsa, MLDSA65_SECRETKEYBYTES)
|
|
850
|
+
PQ_DEFINE_RUBY_MLDSA_CHECK_SK(ml_dsa_44, mldsa44, MLDSA44_SECRETKEYBYTES)
|
|
851
|
+
PQ_DEFINE_RUBY_MLDSA_CHECK_SK(ml_dsa_87, mldsa87, MLDSA87_SECRETKEYBYTES)
|
|
852
|
+
|
|
853
|
+
#undef PQ_DEFINE_RUBY_MLDSA_CHECK_SK
|
|
854
|
+
|
|
790
855
|
PQ_RUBY_KEM_VARIANTS(PQ_DEFINE_RUBY_KEM)
|
|
791
856
|
|
|
792
857
|
#undef PQ_DEFINE_RUBY_KEM
|
|
@@ -1397,6 +1462,7 @@ static VALUE pqcrypto_version(VALUE self) {
|
|
|
1397
1462
|
|
|
1398
1463
|
typedef struct {
|
|
1399
1464
|
void *builder;
|
|
1465
|
+
atomic_bool busy;
|
|
1400
1466
|
} mu_builder_wrapper_t;
|
|
1401
1467
|
|
|
1402
1468
|
typedef struct {
|
|
@@ -1463,15 +1529,29 @@ static const rb_data_type_t mu_builder_data_type = {
|
|
|
1463
1529
|
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
1464
1530
|
};
|
|
1465
1531
|
|
|
1466
|
-
static mu_builder_wrapper_t *
|
|
1532
|
+
static mu_builder_wrapper_t *mu_builder_lock(VALUE obj) {
|
|
1467
1533
|
mu_builder_wrapper_t *wrapper;
|
|
1468
1534
|
TypedData_Get_Struct(obj, mu_builder_wrapper_t, &mu_builder_data_type, wrapper);
|
|
1469
|
-
if (wrapper == NULL
|
|
1470
|
-
rb_raise(ePQCryptoError, "mu builder
|
|
1535
|
+
if (wrapper == NULL) {
|
|
1536
|
+
rb_raise(ePQCryptoError, "mu builder is unavailable");
|
|
1537
|
+
}
|
|
1538
|
+
if (atomic_exchange_explicit(&wrapper->busy, true, memory_order_acquire)) {
|
|
1539
|
+
rb_raise(ePQCryptoError, "mu builder is busy");
|
|
1471
1540
|
}
|
|
1472
1541
|
return wrapper;
|
|
1473
1542
|
}
|
|
1474
1543
|
|
|
1544
|
+
static void mu_builder_unlock(mu_builder_wrapper_t *wrapper) {
|
|
1545
|
+
atomic_store_explicit(&wrapper->busy, false, memory_order_release);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
static void mu_builder_require_active(mu_builder_wrapper_t *wrapper) {
|
|
1549
|
+
if (wrapper->builder == NULL) {
|
|
1550
|
+
mu_builder_unlock(wrapper);
|
|
1551
|
+
rb_raise(ePQCryptoError, "mu builder used after release");
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1475
1555
|
static VALUE pqcrypto__native_mldsa_extract_tr(int argc, VALUE *argv, VALUE self) {
|
|
1476
1556
|
(void)self;
|
|
1477
1557
|
VALUE algorithm = ID2SYM(rb_intern("ml_dsa_65"));
|
|
@@ -1551,6 +1631,7 @@ static VALUE pqcrypto__native_mldsa_mu_builder_new(VALUE self, VALUE tr, VALUE c
|
|
|
1551
1631
|
VALUE obj =
|
|
1552
1632
|
TypedData_Make_Struct(rb_cObject, mu_builder_wrapper_t, &mu_builder_data_type, wrapper);
|
|
1553
1633
|
wrapper->builder = builder;
|
|
1634
|
+
atomic_init(&wrapper->busy, false);
|
|
1554
1635
|
return obj;
|
|
1555
1636
|
}
|
|
1556
1637
|
|
|
@@ -1562,24 +1643,31 @@ static void *pq_mu_absorb_nogvl(void *arg) {
|
|
|
1562
1643
|
|
|
1563
1644
|
static VALUE pqcrypto__native_mldsa_mu_builder_update(VALUE self, VALUE builder_obj, VALUE chunk) {
|
|
1564
1645
|
(void)self;
|
|
1565
|
-
mu_builder_wrapper_t *wrapper = mu_builder_unwrap(builder_obj);
|
|
1566
1646
|
StringValue(chunk);
|
|
1567
1647
|
|
|
1568
1648
|
size_t chunk_len = (size_t)RSTRING_LEN(chunk);
|
|
1649
|
+
mu_builder_wrapper_t *wrapper = mu_builder_lock(builder_obj);
|
|
1650
|
+
mu_builder_require_active(wrapper);
|
|
1569
1651
|
if (chunk_len == 0) {
|
|
1652
|
+
mu_builder_unlock(wrapper);
|
|
1570
1653
|
return Qnil;
|
|
1571
1654
|
}
|
|
1572
1655
|
|
|
1573
1656
|
if (chunk_len < PQ_MU_ABSORB_NOGVL_MIN_BYTES) {
|
|
1574
|
-
int rc =
|
|
1575
|
-
|
|
1657
|
+
int rc = pq_mu_builder_absorb(wrapper->builder,
|
|
1658
|
+
(const uint8_t *)RSTRING_PTR(chunk), chunk_len);
|
|
1659
|
+
mu_builder_unlock(wrapper);
|
|
1576
1660
|
if (rc != PQ_SUCCESS) {
|
|
1577
1661
|
pq_raise_general_error(rc);
|
|
1578
1662
|
}
|
|
1579
1663
|
return Qnil;
|
|
1580
1664
|
}
|
|
1581
1665
|
|
|
1582
|
-
uint8_t *copy =
|
|
1666
|
+
uint8_t *copy = malloc(chunk_len);
|
|
1667
|
+
if (copy == NULL) {
|
|
1668
|
+
mu_builder_unlock(wrapper);
|
|
1669
|
+
rb_raise(rb_eNoMemError, "Memory allocation failed");
|
|
1670
|
+
}
|
|
1583
1671
|
memcpy(copy, RSTRING_PTR(chunk), chunk_len);
|
|
1584
1672
|
|
|
1585
1673
|
mu_absorb_call_t call = {0};
|
|
@@ -1588,7 +1676,9 @@ static VALUE pqcrypto__native_mldsa_mu_builder_update(VALUE self, VALUE builder_
|
|
|
1588
1676
|
call.chunk_len = chunk_len;
|
|
1589
1677
|
|
|
1590
1678
|
rb_nogvl(pq_mu_absorb_nogvl, &call, NULL, NULL, PQ_RB_NOGVL_OFFLOAD_SAFE);
|
|
1679
|
+
RB_GC_GUARD(builder_obj);
|
|
1591
1680
|
free(copy);
|
|
1681
|
+
mu_builder_unlock(wrapper);
|
|
1592
1682
|
|
|
1593
1683
|
if (call.result != PQ_SUCCESS) {
|
|
1594
1684
|
pq_raise_general_error(call.result);
|
|
@@ -1604,7 +1694,8 @@ static void *pq_mu_finalize_nogvl(void *arg) {
|
|
|
1604
1694
|
|
|
1605
1695
|
static VALUE pqcrypto__native_mldsa_mu_builder_finalize(VALUE self, VALUE builder_obj) {
|
|
1606
1696
|
(void)self;
|
|
1607
|
-
mu_builder_wrapper_t *wrapper =
|
|
1697
|
+
mu_builder_wrapper_t *wrapper = mu_builder_lock(builder_obj);
|
|
1698
|
+
mu_builder_require_active(wrapper);
|
|
1608
1699
|
|
|
1609
1700
|
uint8_t mu[PQ_MLDSA_MUBYTES];
|
|
1610
1701
|
|
|
@@ -1613,8 +1704,10 @@ static VALUE pqcrypto__native_mldsa_mu_builder_finalize(VALUE self, VALUE builde
|
|
|
1613
1704
|
call.mu_out = mu;
|
|
1614
1705
|
|
|
1615
1706
|
rb_nogvl(pq_mu_finalize_nogvl, &call, NULL, NULL, PQ_RB_NOGVL_OFFLOAD_SAFE);
|
|
1707
|
+
RB_GC_GUARD(builder_obj);
|
|
1616
1708
|
|
|
1617
1709
|
wrapper->builder = NULL;
|
|
1710
|
+
mu_builder_unlock(wrapper);
|
|
1618
1711
|
|
|
1619
1712
|
if (call.result != PQ_SUCCESS) {
|
|
1620
1713
|
pq_secure_wipe(mu, sizeof(mu));
|
|
@@ -1628,12 +1721,12 @@ static VALUE pqcrypto__native_mldsa_mu_builder_finalize(VALUE self, VALUE builde
|
|
|
1628
1721
|
|
|
1629
1722
|
static VALUE pqcrypto__native_mldsa_mu_builder_release(VALUE self, VALUE builder_obj) {
|
|
1630
1723
|
(void)self;
|
|
1631
|
-
mu_builder_wrapper_t *wrapper;
|
|
1632
|
-
|
|
1633
|
-
if (wrapper != NULL && wrapper->builder != NULL) {
|
|
1724
|
+
mu_builder_wrapper_t *wrapper = mu_builder_lock(builder_obj);
|
|
1725
|
+
if (wrapper->builder != NULL) {
|
|
1634
1726
|
pq_mu_builder_release(wrapper->builder);
|
|
1635
1727
|
wrapper->builder = NULL;
|
|
1636
1728
|
}
|
|
1729
|
+
mu_builder_unlock(wrapper);
|
|
1637
1730
|
return Qnil;
|
|
1638
1731
|
}
|
|
1639
1732
|
|
|
@@ -1999,6 +2092,24 @@ void Init_pqcrypto_secure(void) {
|
|
|
1999
2092
|
pqcrypto_ml_kem_1024_encapsulate, 1);
|
|
2000
2093
|
rb_define_module_function(mPQCrypto, "ml_kem_1024_decapsulate",
|
|
2001
2094
|
pqcrypto_ml_kem_1024_decapsulate, 2);
|
|
2095
|
+
rb_define_module_function(mPQCrypto, "ml_kem_check_public_key",
|
|
2096
|
+
pqcrypto_ml_kem_check_public_key, 1);
|
|
2097
|
+
rb_define_module_function(mPQCrypto, "ml_kem_check_secret_key",
|
|
2098
|
+
pqcrypto_ml_kem_check_secret_key, 1);
|
|
2099
|
+
rb_define_module_function(mPQCrypto, "ml_kem_512_check_public_key",
|
|
2100
|
+
pqcrypto_ml_kem_512_check_public_key, 1);
|
|
2101
|
+
rb_define_module_function(mPQCrypto, "ml_kem_512_check_secret_key",
|
|
2102
|
+
pqcrypto_ml_kem_512_check_secret_key, 1);
|
|
2103
|
+
rb_define_module_function(mPQCrypto, "ml_kem_1024_check_public_key",
|
|
2104
|
+
pqcrypto_ml_kem_1024_check_public_key, 1);
|
|
2105
|
+
rb_define_module_function(mPQCrypto, "ml_kem_1024_check_secret_key",
|
|
2106
|
+
pqcrypto_ml_kem_1024_check_secret_key, 1);
|
|
2107
|
+
rb_define_module_function(mPQCrypto, "ml_dsa_check_secret_key",
|
|
2108
|
+
pqcrypto_ml_dsa_check_secret_key, 1);
|
|
2109
|
+
rb_define_module_function(mPQCrypto, "ml_dsa_44_check_secret_key",
|
|
2110
|
+
pqcrypto_ml_dsa_44_check_secret_key, 1);
|
|
2111
|
+
rb_define_module_function(mPQCrypto, "ml_dsa_87_check_secret_key",
|
|
2112
|
+
pqcrypto_ml_dsa_87_check_secret_key, 1);
|
|
2002
2113
|
rb_define_module_function(mPQCrypto, "hybrid_kem_keypair", pqcrypto_hybrid_kem_keypair, 0);
|
|
2003
2114
|
rb_define_module_function(mPQCrypto, "hybrid_kem_encapsulate", pqcrypto_hybrid_kem_encapsulate,
|
|
2004
2115
|
1);
|