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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8cd88ab6ebe3042111e60711895a9563a1510057a321ac9dab510496634656b8
|
|
4
|
+
data.tar.gz: 684f469f8be7912780e00d368989418499aae00bb530d7fc32f8b6c6d5576593
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 54c1f3b0b8a2f7141d3ec4c8649c003793a3469f212fed94b0dc7ef0e2b0ff3ddba510383a0b62813d9ee98700bf266547be1900693c9ad465fb36deec91ab7b
|
|
7
|
+
data.tar.gz: 41c0bdea2d91bbd2a2e8884ee2b2a328c4c06d410fba8d92c9c1a9470b9d5597d0b8f5233b0ba87225535a80c9055033518ef1dd82e90101a9f5ffcd606b81a6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.2] — 2026-04-25
|
|
4
|
+
|
|
5
|
+
### Added — streaming ML-DSA for large inputs
|
|
6
|
+
|
|
7
|
+
- Added `PQCrypto::Signature::SecretKey#sign_io(io, chunk_size: 1 << 20, context: "".b)`.
|
|
8
|
+
- Added `PQCrypto::Signature::PublicKey#verify_io(io, signature, chunk_size: 1 << 20, context: "".b)` and `verify_io!`.
|
|
9
|
+
- Implemented streaming pure ML-DSA through an internal FIPS 204 ExternalMu path. Existing one-shot `sign` / `verify` semantics are unchanged; no public `sign_mu` / `verify_mu` API is exposed.
|
|
10
|
+
|
|
11
|
+
### Notes
|
|
12
|
+
|
|
13
|
+
- Streaming is primarily for large IO inputs and lower peak memory pressure. It is not a HashML-DSA/prehash speed mode; CPU cost is still dominated by SHAKE/Keccak.
|
|
14
|
+
- Default empty-context streaming signatures interoperate with the existing one-shot `verify(message, signature)` API. Non-empty `context:` must be supplied again during `verify_io`.
|
|
15
|
+
|
|
16
|
+
## [0.3.1] — 2026-04-24
|
|
17
|
+
|
|
18
|
+
### Fixed — X-Wing draft-10 compatibility
|
|
19
|
+
|
|
20
|
+
- Changed `:ml_kem_768_x25519_xwing` secret keys to the draft-10 32-byte
|
|
21
|
+
X-Wing decapsulation seed and derive ML-KEM/X25519 private material with
|
|
22
|
+
SHAKE256 during key generation and decapsulation.
|
|
23
|
+
- Corrected the X-Wing combiner transcript to
|
|
24
|
+
`ss = SHA3-256( ss_M || ss_X || ct_X || pk_X || XWingLabel )`.
|
|
25
|
+
- Updated the hybrid serialization OID to the X-Wing draft OID
|
|
26
|
+
`1.3.6.1.4.1.62253.25722`.
|
|
27
|
+
- Redacted key `inspect` output, removed public secret-key fingerprints,
|
|
28
|
+
improved native extension load diagnostics, switched the extension build
|
|
29
|
+
flag to C11, and aligned docs with the implementation.
|
|
30
|
+
|
|
3
31
|
## [0.3.0] — 2026-04-24
|
|
4
32
|
|
|
5
33
|
**Breaking release.** Hybrid KEM keys, ciphertexts, and `pqc_container_*`
|
|
@@ -9,17 +37,14 @@ and ML-DSA-65 material is unaffected.
|
|
|
9
37
|
### Changed — hybrid KEM (breaking)
|
|
10
38
|
|
|
11
39
|
- Replaced the 0.2.0 ad-hoc `HKDF-SHA256`-with-double-transcript combiner
|
|
12
|
-
with
|
|
13
|
-
|
|
14
|
-
`ss = SHA3-256( XWingLabel || ss_M || ss_X || ct_X || pk_X )`, where
|
|
15
|
-
`XWingLabel` is the 6-byte ASCII string `\.//^\`.
|
|
40
|
+
with a SHA3-256 X-Wing-inspired combiner. This was later corrected in
|
|
41
|
+
`0.3.1` to match draft-10 transcript order and 32-byte secret keys.
|
|
16
42
|
- Renamed the hybrid algorithm symbol
|
|
17
43
|
`:ml_kem_768_x25519_hkdf_sha256` → `:ml_kem_768_x25519_xwing`.
|
|
18
44
|
- Retired the 0.2.0 project-local hybrid OID
|
|
19
|
-
(`2.25.260242945110721168101139140490528778800`).
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
hybrid container now fails fast with `SerializationError`.
|
|
45
|
+
(`2.25.260242945110721168101139140490528778800`). 0.3.0 used
|
|
46
|
+
`2.25.318532651283923671095712569430174917109`; this was later replaced
|
|
47
|
+
in `0.3.1` by the X-Wing draft OID.
|
|
23
48
|
|
|
24
49
|
### Changed — native code hygiene
|
|
25
50
|
|
|
@@ -39,9 +64,8 @@ and ML-DSA-65 material is unaffected.
|
|
|
39
64
|
`hybrid_public_key_t`, `hybrid_secret_key_t`, and
|
|
40
65
|
`hybrid_ciphertext_t` so any future change that introduces padding
|
|
41
66
|
fails at compile time rather than silently shifting byte offsets.
|
|
42
|
-
- Migrated PEM codec
|
|
43
|
-
|
|
44
|
-
invalid base64 characters rather than treating them as zeros.
|
|
67
|
+
- Migrated PEM codec to OpenSSL `BIO_f_base64` with stricter PEM
|
|
68
|
+
header/footer framing and trailing-garbage checks.
|
|
45
69
|
- Deleted the entire internal HKDF and SHA-256 helper paths that 0.2.0
|
|
46
70
|
used for its combiner; the X-Wing combiner is a single SHA3-256
|
|
47
71
|
invocation through `EVP_DigestUpdate`.
|
|
@@ -61,9 +85,9 @@ and ML-DSA-65 material is unaffected.
|
|
|
61
85
|
`CRYPTO_memcmp` through a new `PQCrypto.ct_equals` native helper, so
|
|
62
86
|
key equality checks no longer leak timing information about a
|
|
63
87
|
prefix-match.
|
|
64
|
-
- `
|
|
65
|
-
|
|
66
|
-
|
|
88
|
+
- `PublicKey#hash` and `SecretKey#hash` now hash a SHA-256 fingerprint
|
|
89
|
+
of the bytes instead of the raw bytes. The public secret-key fingerprint
|
|
90
|
+
method is removed in `0.3.1` to reduce accidental logging risk.
|
|
67
91
|
- Native entrypoints and their `native_*` aliases are installed once via
|
|
68
92
|
the new `PQCrypto::NativeBindings` module instead of the ad-hoc
|
|
69
93
|
`unless method_defined?` guards on the singleton.
|
|
@@ -72,7 +96,8 @@ and ML-DSA-65 material is unaffected.
|
|
|
72
96
|
|
|
73
97
|
### Changed — packaging
|
|
74
98
|
|
|
75
|
-
- `required_ruby_version` from `">= 3.4.0.a"` to
|
|
99
|
+
- Intended to change `required_ruby_version` from `">= 3.4.0.a"` to
|
|
100
|
+
`">= 3.4"`; the gemspec is aligned in `0.3.1`.
|
|
76
101
|
- Version bumped to `0.3.0`.
|
|
77
102
|
- `VerificationError` class is still defined (and still raised by
|
|
78
103
|
`verify!`) for backward compatibility, but the native `verify`
|
data/GET_STARTED.md
CHANGED
|
@@ -35,6 +35,29 @@ sig.public_key.verify("message", signature)
|
|
|
35
35
|
sig.public_key.verify!("message", signature)
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
For large files, use streaming ML-DSA:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
signature = File.open("document.bin", "rb") do |io|
|
|
42
|
+
sig.secret_key.sign_io(io, chunk_size: 1 << 20)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
ok = File.open("document.bin", "rb") do |io|
|
|
46
|
+
sig.public_key.verify_io(io, signature, chunk_size: 1 << 20)
|
|
47
|
+
end
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
With an optional context:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
ctx = "document-v1".b
|
|
54
|
+
signature = File.open("document.bin", "rb") { |io| sig.secret_key.sign_io(io, context: ctx) }
|
|
55
|
+
ok = File.open("document.bin", "rb") { |io| sig.public_key.verify_io(io, signature, context: ctx) }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`sign_io` / `verify_io` are pure ML-DSA streaming helpers, not prehash
|
|
59
|
+
shortcuts. `verify_io!` raises on mismatch.
|
|
60
|
+
|
|
38
61
|
## 6. Hybrid KEM (X-Wing)
|
|
39
62
|
|
|
40
63
|
```ruby
|
|
@@ -43,6 +66,9 @@ result = hybrid.public_key.encapsulate
|
|
|
43
66
|
shared_secret = hybrid.secret_key.decapsulate(result.ciphertext)
|
|
44
67
|
```
|
|
45
68
|
|
|
69
|
+
The raw X-Wing secret key exported by this API is the draft-10 32-byte
|
|
70
|
+
decapsulation seed, not the expanded ML-KEM/X25519 private material.
|
|
71
|
+
|
|
46
72
|
The hybrid mode follows `draft-connolly-cfrg-xwing-kem`. See
|
|
47
73
|
`SECURITY.md` for audit status.
|
|
48
74
|
|
data/README.md
CHANGED
|
@@ -13,13 +13,14 @@ It exposes three public building blocks:
|
|
|
13
13
|
The gem is backed by vendored `PQClean` sources for `ML-KEM-768` /
|
|
14
14
|
`ML-DSA-65` and by OpenSSL for `X25519` and `SHA3-256`. Every piece of
|
|
15
15
|
conventional-crypto functionality goes through standard library calls
|
|
16
|
-
(`EVP_*`, `RAND_bytes`, `CRYPTO_memcmp`,
|
|
17
|
-
|
|
16
|
+
(`EVP_*`, `RAND_bytes`, `CRYPTO_memcmp`, `BIO_f_base64`) — nothing
|
|
17
|
+
roll-your-own where a library primitive exists.
|
|
18
18
|
|
|
19
19
|
## Status
|
|
20
20
|
|
|
21
21
|
- primitive-first API only
|
|
22
22
|
- no protocol/session helpers in the public surface
|
|
23
|
+
- streaming ML-DSA signing/verification is available for large IO inputs
|
|
23
24
|
- serialization uses pq_crypto-specific `pqc_container_*` wrappers
|
|
24
25
|
- not audited
|
|
25
26
|
- not yet positioned as production-ready
|
|
@@ -42,7 +43,21 @@ bundle exec rake compile
|
|
|
42
43
|
|
|
43
44
|
- Ruby 3.4.x
|
|
44
45
|
- a C toolchain with C11 support (for `_Static_assert` / `_Thread_local`)
|
|
45
|
-
- OpenSSL **3.0 or later** with SHA3-256 available (default provider)
|
|
46
|
+
- OpenSSL **3.0 or later** with SHA3-256 and SHAKE256 available (default provider)
|
|
47
|
+
|
|
48
|
+
### Build-time Keccak backend
|
|
49
|
+
|
|
50
|
+
The default build uses PQClean's scalar `common/fips202.c` backend:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
PQCRYPTO_KECCAK_BACKEND=clean bundle exec rake compile
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`PQCRYPTO_KECCAK_BACKEND=xkcp` is reserved for a separately vendored,
|
|
57
|
+
reviewed, `fips202.h`-compatible XKCP adapter. If requested without that
|
|
58
|
+
adapter, the build aborts instead of silently falling back to `clean`.
|
|
59
|
+
This avoids mixing OpenSSL EVP SHAKE state with PQClean SHAKE state and
|
|
60
|
+
keeps output-byte compatibility explicit.
|
|
46
61
|
|
|
47
62
|
## Async / Fiber scheduler support
|
|
48
63
|
|
|
@@ -100,6 +115,8 @@ shared_secret = keypair.secret_key.decapsulate(result.ciphertext)
|
|
|
100
115
|
|
|
101
116
|
### ML-DSA-65
|
|
102
117
|
|
|
118
|
+
One-shot signing keeps the existing API:
|
|
119
|
+
|
|
103
120
|
```ruby
|
|
104
121
|
keypair = PQCrypto::Signature.generate(:ml_dsa_65)
|
|
105
122
|
signature = keypair.secret_key.sign("hello")
|
|
@@ -108,6 +125,36 @@ keypair.public_key.verify("hello", signature) # => true / false
|
|
|
108
125
|
keypair.public_key.verify!("hello", signature) # raises on mismatch
|
|
109
126
|
```
|
|
110
127
|
|
|
128
|
+
For large inputs, use streaming IO so the message does not need to be
|
|
129
|
+
materialized as one Ruby string:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
signature = File.open("document.bin", "rb") do |io|
|
|
133
|
+
keypair.secret_key.sign_io(io, chunk_size: 1 << 20)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
ok = File.open("document.bin", "rb") do |io|
|
|
137
|
+
keypair.public_key.verify_io(io, signature, chunk_size: 1 << 20)
|
|
138
|
+
end
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`sign_io` / `verify_io` use pure ML-DSA with an internal FIPS 204
|
|
142
|
+
ExternalMu flow. They are not HashML-DSA/prehash shortcuts and do not
|
|
143
|
+
expose public `sign_mu` / `verify_mu` APIs. With the default empty
|
|
144
|
+
context, streaming signatures verify with `verify(message, signature)`
|
|
145
|
+
and one-shot signatures verify with `verify_io(io, signature)`.
|
|
146
|
+
|
|
147
|
+
Optional context is supported and must match on verify:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
ctx = "invoice-v1".b
|
|
151
|
+
signature = File.open("document.bin", "rb") { |io| keypair.secret_key.sign_io(io, context: ctx) }
|
|
152
|
+
ok = File.open("document.bin", "rb") { |io| keypair.public_key.verify_io(io, signature, context: ctx) }
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`chunk_size` must be positive. `context` is limited to 255 bytes by
|
|
156
|
+
FIPS 204. `verify_io!` raises `PQCrypto::VerificationError` on mismatch.
|
|
157
|
+
|
|
111
158
|
Note: `verify` returns a plain boolean for normal outcomes. `verify!`
|
|
112
159
|
raises `PQCrypto::VerificationError` when the signature does not
|
|
113
160
|
match.
|
|
@@ -120,14 +167,16 @@ result = keypair.public_key.encapsulate
|
|
|
120
167
|
shared_secret = keypair.secret_key.decapsulate(result.ciphertext)
|
|
121
168
|
```
|
|
122
169
|
|
|
123
|
-
The
|
|
170
|
+
The implementation follows draft-10 key expansion: the X-Wing secret
|
|
171
|
+
decapsulation key is a 32-byte seed expanded with SHAKE256 into ML-KEM
|
|
172
|
+
and X25519 private material. The combiner is exactly:
|
|
124
173
|
|
|
125
174
|
```
|
|
126
|
-
ss = SHA3-256(
|
|
175
|
+
ss = SHA3-256( ss_M || ss_X || ct_X || pk_X || "\.//^\" )
|
|
127
176
|
```
|
|
128
177
|
|
|
129
|
-
as specified by `draft-connolly-cfrg-xwing-kem`. See `SECURITY.md`
|
|
130
|
-
audit status and interoperability caveats.
|
|
178
|
+
as specified by `draft-connolly-cfrg-xwing-kem-10`. See `SECURITY.md`
|
|
179
|
+
for audit status and interoperability caveats.
|
|
131
180
|
|
|
132
181
|
## Serialization
|
|
133
182
|
|
|
@@ -172,6 +221,12 @@ key.wipe! # scrub the key's internal copy
|
|
|
172
221
|
`CRYPTO_memcmp` through a `PQCrypto.ct_equals` helper so comparisons
|
|
173
222
|
do not leak timing information about a prefix match.
|
|
174
223
|
|
|
224
|
+
Secret key `inspect` output is intentionally redacted and secret key
|
|
225
|
+
objects do not expose a public `fingerprint` method. `wipe!` remains
|
|
226
|
+
best-effort only: it clears the current Ruby string buffer owned by the
|
|
227
|
+
key object, not every possible copy made by Ruby, OpenSSL, serialization,
|
|
228
|
+
logging, or the garbage collector.
|
|
229
|
+
|
|
175
230
|
## Introspection
|
|
176
231
|
|
|
177
232
|
```ruby
|
data/SECURITY.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Scope of the public API
|
|
4
4
|
|
|
5
|
-
`pq_crypto`
|
|
5
|
+
`pq_crypto` exposes a primitive-first public surface:
|
|
6
6
|
|
|
7
7
|
- `PQCrypto::KEM` (`ML-KEM-768`)
|
|
8
8
|
- `PQCrypto::Signature` (`ML-DSA-65`)
|
|
@@ -29,23 +29,25 @@ gem.
|
|
|
29
29
|
### HybridKEM
|
|
30
30
|
|
|
31
31
|
`PQCrypto::HybridKEM` implements the **X-Wing** construction from
|
|
32
|
-
[`draft-connolly-cfrg-xwing-kem`](https://datatracker.ietf.org/doc/draft-connolly-cfrg-xwing-kem/)
|
|
32
|
+
[`draft-connolly-cfrg-xwing-kem-10`](https://datatracker.ietf.org/doc/draft-connolly-cfrg-xwing-kem/).
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
The X-Wing secret decapsulation key is a 32-byte seed. It is expanded
|
|
35
|
+
with SHAKE256 into the ML-KEM-768 and X25519 private material used
|
|
36
|
+
internally for decapsulation. The public key and ciphertext are the
|
|
37
|
+
fixed-length concatenations specified by the draft.
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
ss = SHA3-256( ss_M || ss_X || ct_X || pk_X || XWingLabel )
|
|
40
|
+
|
|
41
|
+
where `XWingLabel = "\.//^\"` (6 ASCII bytes).
|
|
39
42
|
|
|
40
43
|
X-Wing as specified has a proof of classical IND-CCA security under
|
|
41
44
|
the strong Diffie-Hellman assumption for X25519 (in the ROM), and
|
|
42
45
|
post-quantum IND-CCA security in the standard model assuming ML-KEM-768
|
|
43
46
|
is IND-CCA secure and SHA3-256 behaves as a PRF.
|
|
44
47
|
|
|
45
|
-
This gem is
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
verified against the reference implementation before relying on it.
|
|
48
|
+
This gem is intended to match the X-Wing draft as of version 10. External
|
|
49
|
+
interoperability should still be verified against the reference
|
|
50
|
+
implementation before relying on it.
|
|
49
51
|
|
|
50
52
|
### Deterministic test hooks
|
|
51
53
|
|
|
@@ -68,16 +70,16 @@ They are:
|
|
|
68
70
|
- not real PKCS#8
|
|
69
71
|
- not advertised as interoperable with OpenSSL, Go, Java, or PKI tooling
|
|
70
72
|
|
|
71
|
-
The
|
|
72
|
-
OIDs under `2.25.*`.
|
|
73
|
-
|
|
73
|
+
The `pqc_container_*` envelope itself is project-specific. ML-KEM and
|
|
74
|
+
ML-DSA currently use project-local UUID-derived OIDs under `2.25.*`.
|
|
75
|
+
Hybrid X-Wing uses the draft X-Wing OID `1.3.6.1.4.1.62253.25722`.
|
|
74
76
|
|
|
75
77
|
The hybrid OID used by 0.2.0
|
|
76
|
-
(`2.25.260242945110721168101139140490528778800`) is retired
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
(`2.25.260242945110721168101139140490528778800`) is retired. The
|
|
79
|
+
intermediate 0.3.0 project-local hybrid OID
|
|
80
|
+
(`2.25.318532651283923671095712569430174917109`) is also retired in
|
|
81
|
+
favor of the draft X-Wing OID. Older hybrid containers are rejected at
|
|
82
|
+
decode time.
|
|
81
83
|
|
|
82
84
|
## Memory wiping
|
|
83
85
|
|
|
@@ -98,9 +100,19 @@ OpenSSL is used for:
|
|
|
98
100
|
- `SHA3-256` (X-Wing combiner, via `EVP_sha3_256`)
|
|
99
101
|
- `RAND_bytes` (production entropy source for `randombytes()`)
|
|
100
102
|
- `CRYPTO_memcmp` (constant-time comparison used by `PQCrypto.ct_equals`)
|
|
101
|
-
- Base64 encode/decode for PEM via
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
- Base64 encode/decode for PEM via OpenSSL `BIO_f_base64`, with strict
|
|
104
|
+
header/footer framing and trailing-garbage checks.
|
|
105
|
+
|
|
106
|
+
## Secret key display and wiping
|
|
107
|
+
|
|
108
|
+
Secret key objects redact `inspect` output and intentionally do not expose
|
|
109
|
+
a public `fingerprint` method. This avoids accidental logging of raw secret
|
|
110
|
+
bytes or stable secret-derived identifiers.
|
|
111
|
+
|
|
112
|
+
`wipe!` is best-effort only. It wipes the current Ruby string buffer held
|
|
113
|
+
by the key object; it cannot guarantee erasure of copies made by Ruby,
|
|
114
|
+
OpenSSL, native wrapper buffers, serialization, logging, crash dumps, or
|
|
115
|
+
the garbage collector.
|
|
104
116
|
|
|
105
117
|
## Threading
|
|
106
118
|
|
data/ext/pqcrypto/extconf.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
require "mkmf"
|
|
5
5
|
|
|
6
|
-
$CFLAGS << " -std=
|
|
6
|
+
$CFLAGS << " -std=c11 -Wall -Wextra -O2"
|
|
7
7
|
$CFLAGS << " -fstack-protector-strong -D_FORTIFY_SOURCE=2"
|
|
8
8
|
VENDOR_ONLY_CFLAGS = "-Wno-unused-parameter -Wno-unused-function -Wno-strict-prototypes -Wno-pedantic -Wno-c23-extensions -Wno-undef"
|
|
9
9
|
|
|
@@ -11,10 +11,14 @@ $LDFLAGS << " -Wl,-no_warn_duplicate_libraries" if RbConfig::CONFIG["host_os"] =
|
|
|
11
11
|
|
|
12
12
|
USE_SYSTEM = arg_config("--use-system-libraries") || ENV["PQCRYPTO_USE_SYSTEM_LIBRARIES"]
|
|
13
13
|
|
|
14
|
+
KECCAK_BACKEND = (ENV["PQCRYPTO_KECCAK_BACKEND"] || "clean").strip.downcase
|
|
15
|
+
SUPPORTED_KECCAK_BACKENDS = %w[clean xkcp].freeze
|
|
16
|
+
|
|
14
17
|
SANITIZE = ENV["PQCRYPTO_SANITIZE"]
|
|
15
18
|
|
|
16
19
|
if SANITIZE && !SANITIZE.strip.empty?
|
|
17
20
|
sanitize = SANITIZE.strip
|
|
21
|
+
$CFLAGS.gsub!(/\s-D_FORTIFY_SOURCE=\d+/, "")
|
|
18
22
|
$CFLAGS << " -O1 -g -fno-omit-frame-pointer -fsanitize=#{sanitize}"
|
|
19
23
|
$LDFLAGS << " -fsanitize=#{sanitize}"
|
|
20
24
|
end
|
|
@@ -72,9 +76,53 @@ def configure_openssl!
|
|
|
72
76
|
SRC
|
|
73
77
|
abort "OpenSSL SHA3-256 is required (X-Wing combiner)" unless try_compile(sha3_check)
|
|
74
78
|
|
|
79
|
+
shake_check = <<~SRC
|
|
80
|
+
#include <openssl/evp.h>
|
|
81
|
+
int main(void) {
|
|
82
|
+
const EVP_MD *md = EVP_shake256();
|
|
83
|
+
return md == NULL ? 1 : 0;
|
|
84
|
+
}
|
|
85
|
+
SRC
|
|
86
|
+
abort "OpenSSL SHAKE256 is required (X-Wing key expansion)" unless try_compile(shake_check)
|
|
87
|
+
|
|
75
88
|
$CFLAGS << " -DHAVE_OPENSSL_EVP_H -DHAVE_OPENSSL_RAND_H"
|
|
76
89
|
end
|
|
77
90
|
|
|
91
|
+
def configure_keccak_backend(vendor_dir, common_dir)
|
|
92
|
+
abort "Unsupported PQCRYPTO_KECCAK_BACKEND=#{KECCAK_BACKEND.inspect}. Supported: #{SUPPORTED_KECCAK_BACKENDS.join(", ")}" unless SUPPORTED_KECCAK_BACKENDS.include?(KECCAK_BACKEND)
|
|
93
|
+
|
|
94
|
+
case KECCAK_BACKEND
|
|
95
|
+
when "clean"
|
|
96
|
+
{
|
|
97
|
+
name: "clean",
|
|
98
|
+
include_dirs: [],
|
|
99
|
+
source_group: ["pqclean_common", [File.join(common_dir, "fips202.c")]]
|
|
100
|
+
}
|
|
101
|
+
when "xkcp"
|
|
102
|
+
# The optimized backend must provide the same fips202.h-compatible API as
|
|
103
|
+
# PQClean's common/fips202.c. Do not substitute OpenSSL EVP SHAKE here: the
|
|
104
|
+
# PQClean SHAKE state layout is part of the ML-KEM/ML-DSA call graph.
|
|
105
|
+
xkcp_dir = File.join(vendor_dir, "xkcp")
|
|
106
|
+
adapter_source = File.join(xkcp_dir, "pqclean_fips202_xkcp.c")
|
|
107
|
+
|
|
108
|
+
abort <<~MSG unless File.exist?(adapter_source)
|
|
109
|
+
PQCRYPTO_KECCAK_BACKEND=xkcp was requested, but no reviewed XKCP adapter was found.
|
|
110
|
+
|
|
111
|
+
Expected:
|
|
112
|
+
#{adapter_source}
|
|
113
|
+
|
|
114
|
+
Refusing to fall back silently to the clean backend. Vendor a fips202.h-compatible
|
|
115
|
+
XKCP adapter first, then run the full SHAKE-dependent KAT/regression test matrix.
|
|
116
|
+
MSG
|
|
117
|
+
|
|
118
|
+
{
|
|
119
|
+
name: "xkcp",
|
|
120
|
+
include_dirs: [xkcp_dir],
|
|
121
|
+
source_group: ["xkcp_keccak", [adapter_source]]
|
|
122
|
+
}
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
78
126
|
def configure_pqclean(vendor_dir)
|
|
79
127
|
return nil unless vendor_dir
|
|
80
128
|
|
|
@@ -85,17 +133,20 @@ def configure_pqclean(vendor_dir)
|
|
|
85
133
|
mldsa_dir = File.join(pqclean_dir, "crypto_sign", "ml-dsa-65", "clean")
|
|
86
134
|
common_dir = File.join(pqclean_dir, "common")
|
|
87
135
|
|
|
88
|
-
|
|
136
|
+
keccak_config = configure_keccak_backend(vendor_dir, common_dir)
|
|
137
|
+
|
|
138
|
+
include_dirs = [mlkem_dir, mldsa_dir, common_dir, *keccak_config[:include_dirs]]
|
|
89
139
|
return nil unless include_dirs.all? { |dir| Dir.exist?(dir) }
|
|
90
140
|
|
|
91
141
|
mlkem_sources = Dir.glob(File.join(mlkem_dir, "*.c")).sort
|
|
92
142
|
mldsa_sources = Dir.glob(File.join(mldsa_dir, "*.c")).sort
|
|
93
|
-
common_sources = %w[
|
|
143
|
+
common_sources = %w[sha2.c sp800-185.c].map { |name| File.join(common_dir, name) }
|
|
94
144
|
|
|
95
145
|
source_groups = [
|
|
96
146
|
["pqclean_mlkem", mlkem_sources],
|
|
97
147
|
["pqclean_mldsa", mldsa_sources],
|
|
98
|
-
["pqclean_common", common_sources]
|
|
148
|
+
["pqclean_common", common_sources],
|
|
149
|
+
keccak_config[:source_group]
|
|
99
150
|
]
|
|
100
151
|
|
|
101
152
|
return nil unless source_groups.all? { |_, sources| sources.all? { |path| File.exist?(path) } }
|
|
@@ -105,6 +156,7 @@ def configure_pqclean(vendor_dir)
|
|
|
105
156
|
|
|
106
157
|
{
|
|
107
158
|
include_dirs: include_dirs,
|
|
159
|
+
keccak_backend: keccak_config[:name],
|
|
108
160
|
source_groups: source_groups
|
|
109
161
|
}
|
|
110
162
|
end
|
|
@@ -155,6 +207,7 @@ pqclean_config = configure_pqclean(vendor_dir)
|
|
|
155
207
|
puts "OpenSSL: system"
|
|
156
208
|
abort "PQClean vendored sources are required. Run: bundle exec rake vendor" unless pqclean_config
|
|
157
209
|
puts "PQClean: vendored (randombytes overridden by pq_randombytes.c)"
|
|
210
|
+
puts "Keccak backend: #{pqclean_config[:keccak_backend]}"
|
|
158
211
|
puts "Output: pqcrypto/pqcrypto_secure"
|
|
159
212
|
puts "===================================="
|
|
160
213
|
|