pq_crypto 0.3.2 → 0.4.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/.github/workflows/ci.yml +56 -0
- data/CHANGELOG.md +37 -0
- data/GET_STARTED.md +361 -40
- data/README.md +58 -241
- data/SECURITY.md +101 -82
- data/ext/pqcrypto/extconf.rb +40 -7
- data/ext/pqcrypto/mldsa_api.h +71 -1
- data/ext/pqcrypto/mlkem_api.h +24 -0
- data/ext/pqcrypto/pq_externalmu.c +14 -1
- data/ext/pqcrypto/pqcrypto_ruby_secure.c +484 -81
- data/ext/pqcrypto/pqcrypto_secure.c +179 -72
- data/ext/pqcrypto/pqcrypto_secure.h +87 -7
- data/ext/pqcrypto/pqcrypto_version.h +7 -0
- data/ext/pqcrypto/vendor/.vendored +1 -1
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/LICENSE +5 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/Makefile +19 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/Makefile.Microsoft_nmake +23 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/api.h +18 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/cbd.c +83 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/cbd.h +11 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/indcpa.c +327 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/indcpa.h +22 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/kem.c +164 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/kem.h +23 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/ntt.c +146 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/ntt.h +14 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/params.h +36 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/poly.c +311 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/poly.h +37 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/polyvec.c +198 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/polyvec.h +26 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/reduce.c +41 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/reduce.h +13 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/symmetric-shake.c +71 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/symmetric.h +30 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/verify.c +67 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-1024/clean/verify.h +13 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/LICENSE +5 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/Makefile +19 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/Makefile.Microsoft_nmake +23 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/api.h +18 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/cbd.c +108 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/cbd.h +11 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/indcpa.c +327 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/indcpa.h +22 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/kem.c +164 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/kem.h +23 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/ntt.c +146 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/ntt.h +14 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/params.h +36 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/poly.c +299 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/poly.h +37 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/polyvec.c +188 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/polyvec.h +26 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/reduce.c +41 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/reduce.h +13 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/symmetric-shake.c +71 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/symmetric.h +30 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/verify.c +67 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-512/clean/verify.h +13 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/LICENSE +5 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/Makefile +19 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/Makefile.Microsoft_nmake +23 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/api.h +50 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/ntt.c +98 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/ntt.h +10 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/packing.c +261 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/packing.h +31 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/params.h +44 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/poly.c +848 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/poly.h +52 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/polyvec.c +415 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/polyvec.h +65 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/reduce.c +69 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/reduce.h +17 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/rounding.c +98 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/rounding.h +14 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/sign.c +407 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/sign.h +47 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/symmetric-shake.c +26 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-44/clean/symmetric.h +34 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/LICENSE +5 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/Makefile +19 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/Makefile.Microsoft_nmake +23 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/api.h +50 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/ntt.c +98 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/ntt.h +10 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/packing.c +261 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/packing.h +31 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/params.h +44 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/poly.c +823 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/poly.h +52 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/polyvec.c +415 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/polyvec.h +65 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/reduce.c +69 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/reduce.h +17 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/rounding.c +92 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/rounding.h +14 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/sign.c +407 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/sign.h +47 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/symmetric-shake.c +26 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-87/clean/symmetric.h +34 -0
- data/lib/pq_crypto/algorithm_registry.rb +200 -0
- data/lib/pq_crypto/hybrid_kem.rb +1 -12
- data/lib/pq_crypto/kem.rb +104 -13
- data/lib/pq_crypto/pkcs8.rb +387 -0
- data/lib/pq_crypto/serialization.rb +1 -14
- data/lib/pq_crypto/signature.rb +123 -17
- data/lib/pq_crypto/spki.rb +131 -0
- data/lib/pq_crypto/version.rb +1 -1
- data/lib/pq_crypto.rb +78 -19
- data/script/vendor_libs.rb +4 -0
- metadata +95 -3
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PQCrypto
|
|
4
|
+
module AlgorithmRegistry
|
|
5
|
+
class << self
|
|
6
|
+
def entries
|
|
7
|
+
@entries ||= begin
|
|
8
|
+
{
|
|
9
|
+
ml_kem_512: {
|
|
10
|
+
family: :ml_kem,
|
|
11
|
+
legacy_oid: nil,
|
|
12
|
+
standard_oid: "2.16.840.1.101.3.4.4.1",
|
|
13
|
+
public_key_bytes: PQCrypto::ML_KEM_512_PUBLIC_KEY_BYTES,
|
|
14
|
+
secret_key_bytes: PQCrypto::ML_KEM_512_SECRET_KEY_BYTES,
|
|
15
|
+
ciphertext_bytes: PQCrypto::ML_KEM_512_CIPHERTEXT_BYTES,
|
|
16
|
+
shared_secret_bytes: PQCrypto::ML_KEM_512_SHARED_SECRET_BYTES,
|
|
17
|
+
signature_bytes: nil,
|
|
18
|
+
description: "Pure ML-KEM-512 primitive (FIPS 203).",
|
|
19
|
+
}.freeze,
|
|
20
|
+
ml_kem_768: {
|
|
21
|
+
family: :ml_kem,
|
|
22
|
+
legacy_oid: "2.25.186599352125448088867056807454444238446",
|
|
23
|
+
standard_oid: "2.16.840.1.101.3.4.4.2",
|
|
24
|
+
public_key_bytes: PQCrypto::ML_KEM_PUBLIC_KEY_BYTES,
|
|
25
|
+
secret_key_bytes: PQCrypto::ML_KEM_SECRET_KEY_BYTES,
|
|
26
|
+
ciphertext_bytes: PQCrypto::ML_KEM_CIPHERTEXT_BYTES,
|
|
27
|
+
shared_secret_bytes: PQCrypto::ML_KEM_SHARED_SECRET_BYTES,
|
|
28
|
+
signature_bytes: nil,
|
|
29
|
+
description: "Pure ML-KEM-768 primitive (FIPS 203).",
|
|
30
|
+
}.freeze,
|
|
31
|
+
ml_kem_1024: {
|
|
32
|
+
family: :ml_kem,
|
|
33
|
+
legacy_oid: nil,
|
|
34
|
+
standard_oid: "2.16.840.1.101.3.4.4.3",
|
|
35
|
+
public_key_bytes: PQCrypto::ML_KEM_1024_PUBLIC_KEY_BYTES,
|
|
36
|
+
secret_key_bytes: PQCrypto::ML_KEM_1024_SECRET_KEY_BYTES,
|
|
37
|
+
ciphertext_bytes: PQCrypto::ML_KEM_1024_CIPHERTEXT_BYTES,
|
|
38
|
+
shared_secret_bytes: PQCrypto::ML_KEM_1024_SHARED_SECRET_BYTES,
|
|
39
|
+
signature_bytes: nil,
|
|
40
|
+
description: "Pure ML-KEM-1024 primitive (FIPS 203).",
|
|
41
|
+
}.freeze,
|
|
42
|
+
ml_kem_768_x25519_xwing: {
|
|
43
|
+
family: :ml_kem_hybrid,
|
|
44
|
+
legacy_oid: "1.3.6.1.4.1.62253.25722",
|
|
45
|
+
standard_oid: nil,
|
|
46
|
+
public_key_bytes: PQCrypto::HYBRID_KEM_PUBLIC_KEY_BYTES,
|
|
47
|
+
secret_key_bytes: PQCrypto::HYBRID_KEM_SECRET_KEY_BYTES,
|
|
48
|
+
ciphertext_bytes: PQCrypto::HYBRID_KEM_CIPHERTEXT_BYTES,
|
|
49
|
+
shared_secret_bytes: PQCrypto::HYBRID_KEM_SHARED_SECRET_BYTES,
|
|
50
|
+
signature_bytes: nil,
|
|
51
|
+
description: "Hybrid KEM: ML-KEM-768 + X25519 combined via X-Wing SHA3-256 combiner (draft-connolly-cfrg-xwing-kem).",
|
|
52
|
+
}.freeze,
|
|
53
|
+
ml_dsa_44: {
|
|
54
|
+
family: :ml_dsa,
|
|
55
|
+
legacy_oid: nil,
|
|
56
|
+
standard_oid: "2.16.840.1.101.3.4.3.17",
|
|
57
|
+
public_key_bytes: PQCrypto::SIGN_44_PUBLIC_KEY_BYTES,
|
|
58
|
+
secret_key_bytes: PQCrypto::SIGN_44_SECRET_KEY_BYTES,
|
|
59
|
+
ciphertext_bytes: nil,
|
|
60
|
+
shared_secret_bytes: nil,
|
|
61
|
+
signature_bytes: PQCrypto::SIGN_44_BYTES,
|
|
62
|
+
description: "ML-DSA-44 signature primitive (FIPS 204).",
|
|
63
|
+
}.freeze,
|
|
64
|
+
ml_dsa_65: {
|
|
65
|
+
family: :ml_dsa,
|
|
66
|
+
legacy_oid: "2.25.305232938483772195555080795650659207792",
|
|
67
|
+
standard_oid: "2.16.840.1.101.3.4.3.18",
|
|
68
|
+
public_key_bytes: PQCrypto::SIGN_PUBLIC_KEY_BYTES,
|
|
69
|
+
secret_key_bytes: PQCrypto::SIGN_SECRET_KEY_BYTES,
|
|
70
|
+
ciphertext_bytes: nil,
|
|
71
|
+
shared_secret_bytes: nil,
|
|
72
|
+
signature_bytes: PQCrypto::SIGN_BYTES,
|
|
73
|
+
description: "ML-DSA-65 signature primitive (FIPS 204).",
|
|
74
|
+
}.freeze,
|
|
75
|
+
ml_dsa_87: {
|
|
76
|
+
family: :ml_dsa,
|
|
77
|
+
legacy_oid: nil,
|
|
78
|
+
standard_oid: "2.16.840.1.101.3.4.3.19",
|
|
79
|
+
public_key_bytes: PQCrypto::SIGN_87_PUBLIC_KEY_BYTES,
|
|
80
|
+
secret_key_bytes: PQCrypto::SIGN_87_SECRET_KEY_BYTES,
|
|
81
|
+
ciphertext_bytes: nil,
|
|
82
|
+
shared_secret_bytes: nil,
|
|
83
|
+
signature_bytes: PQCrypto::SIGN_87_BYTES,
|
|
84
|
+
description: "ML-DSA-87 signature primitive (FIPS 204).",
|
|
85
|
+
}.freeze,
|
|
86
|
+
}.freeze
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def fetch(symbol)
|
|
91
|
+
entries.fetch(symbol) do
|
|
92
|
+
raise UnsupportedAlgorithmError, "Unsupported algorithm: #{symbol.inspect}"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def legacy_oid(symbol)
|
|
97
|
+
fetch(symbol).fetch(:legacy_oid)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def standard_oid(symbol)
|
|
101
|
+
oid = fetch(symbol).fetch(:standard_oid)
|
|
102
|
+
raise SerializationError, "No standard OID registered for #{symbol.inspect}" if oid.nil?
|
|
103
|
+
|
|
104
|
+
oid
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def by_legacy_oid(oid)
|
|
108
|
+
legacy_oid_index.fetch(oid, nil)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def by_standard_oid(oid)
|
|
112
|
+
standard_oid_index.fetch(oid, nil)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def supported_kems
|
|
116
|
+
supported_by_family(:ml_kem)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def supported_hybrid_kems
|
|
120
|
+
supported_by_family(:ml_kem_hybrid)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def supported_signatures
|
|
124
|
+
supported_by_family(:ml_dsa)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def legacy_metadata_view
|
|
128
|
+
@legacy_metadata_view ||= entries.each_with_object({}) do |(algorithm, entry), view|
|
|
129
|
+
oid = entry.fetch(:legacy_oid)
|
|
130
|
+
next if oid.nil?
|
|
131
|
+
|
|
132
|
+
view[algorithm] = {
|
|
133
|
+
family: entry.fetch(:family),
|
|
134
|
+
oid: oid,
|
|
135
|
+
}.freeze
|
|
136
|
+
end.freeze
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def details_for_family(family)
|
|
140
|
+
@details_for_family ||= {}
|
|
141
|
+
@details_for_family[family] ||= begin
|
|
142
|
+
entries.each_with_object({}) do |(algorithm, entry), details|
|
|
143
|
+
next unless entry.fetch(:family) == family
|
|
144
|
+
|
|
145
|
+
details[algorithm] = details_entry(algorithm, entry)
|
|
146
|
+
end.freeze
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private
|
|
151
|
+
|
|
152
|
+
def supported_by_family(family)
|
|
153
|
+
supported_by_family_cache[family] ||= entries.each_with_object([]) do |(algorithm, entry), supported|
|
|
154
|
+
supported << algorithm if entry.fetch(:family) == family
|
|
155
|
+
end.freeze
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def supported_by_family_cache
|
|
159
|
+
@supported_by_family_cache ||= {}
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def legacy_oid_index
|
|
163
|
+
@legacy_oid_index ||= entries.each_with_object({}) do |(algorithm, entry), index|
|
|
164
|
+
oid = entry.fetch(:legacy_oid)
|
|
165
|
+
index[oid] = algorithm unless oid.nil?
|
|
166
|
+
end.freeze
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def standard_oid_index
|
|
170
|
+
@standard_oid_index ||= entries.each_with_object({}) do |(algorithm, entry), index|
|
|
171
|
+
oid = entry.fetch(:standard_oid)
|
|
172
|
+
index[oid] = algorithm unless oid.nil?
|
|
173
|
+
end.freeze
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def details_entry(algorithm, entry)
|
|
177
|
+
# :oid intentionally remains an alias for the legacy pqc_container_* OID.
|
|
178
|
+
# Use .standard_oid when an RFC 9935/9881 OID is required.
|
|
179
|
+
detail = {
|
|
180
|
+
name: algorithm,
|
|
181
|
+
family: entry.fetch(:family),
|
|
182
|
+
oid: entry.fetch(:legacy_oid),
|
|
183
|
+
public_key_bytes: entry.fetch(:public_key_bytes),
|
|
184
|
+
secret_key_bytes: entry.fetch(:secret_key_bytes),
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if entry.fetch(:ciphertext_bytes)
|
|
188
|
+
detail[:ciphertext_bytes] = entry.fetch(:ciphertext_bytes)
|
|
189
|
+
detail[:shared_secret_bytes] = entry.fetch(:shared_secret_bytes)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
detail[:signature_bytes] = entry.fetch(:signature_bytes) if entry.fetch(:signature_bytes)
|
|
193
|
+
detail[:description] = entry.fetch(:description)
|
|
194
|
+
|
|
195
|
+
detail.freeze
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
data/lib/pq_crypto/hybrid_kem.rb
CHANGED
|
@@ -4,18 +4,7 @@ module PQCrypto
|
|
|
4
4
|
module HybridKEM
|
|
5
5
|
CANONICAL_ALGORITHM = :ml_kem_768_x25519_xwing
|
|
6
6
|
|
|
7
|
-
DETAILS =
|
|
8
|
-
CANONICAL_ALGORITHM => {
|
|
9
|
-
name: CANONICAL_ALGORITHM,
|
|
10
|
-
family: Serialization.algorithm_to_family(CANONICAL_ALGORITHM),
|
|
11
|
-
oid: Serialization.algorithm_to_oid(CANONICAL_ALGORITHM),
|
|
12
|
-
public_key_bytes: HYBRID_KEM_PUBLIC_KEY_BYTES,
|
|
13
|
-
secret_key_bytes: HYBRID_KEM_SECRET_KEY_BYTES,
|
|
14
|
-
ciphertext_bytes: HYBRID_KEM_CIPHERTEXT_BYTES,
|
|
15
|
-
shared_secret_bytes: HYBRID_KEM_SHARED_SECRET_BYTES,
|
|
16
|
-
description: "Hybrid KEM: ML-KEM-768 + X25519 combined via X-Wing SHA3-256 combiner (draft-connolly-cfrg-xwing-kem).",
|
|
17
|
-
}.freeze,
|
|
18
|
-
}.freeze
|
|
7
|
+
DETAILS = AlgorithmRegistry.details_for_family(:ml_kem_hybrid).freeze
|
|
19
8
|
|
|
20
9
|
class << self
|
|
21
10
|
def generate(algorithm = CANONICAL_ALGORITHM)
|
data/lib/pq_crypto/kem.rb
CHANGED
|
@@ -6,23 +6,33 @@ module PQCrypto
|
|
|
6
6
|
module KEM
|
|
7
7
|
CANONICAL_ALGORITHM = :ml_kem_768
|
|
8
8
|
|
|
9
|
-
DETAILS =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
DETAILS = AlgorithmRegistry.details_for_family(:ml_kem).freeze
|
|
10
|
+
|
|
11
|
+
NATIVE_DISPATCH = {
|
|
12
|
+
ml_kem_512: {
|
|
13
|
+
keypair: :native_ml_kem_512_keypair,
|
|
14
|
+
keypair_from_seed: :native_ml_kem_512_keypair_from_seed,
|
|
15
|
+
encapsulate: :native_ml_kem_512_encapsulate,
|
|
16
|
+
decapsulate: :native_ml_kem_512_decapsulate,
|
|
17
|
+
}.freeze,
|
|
18
|
+
ml_kem_768: {
|
|
19
|
+
keypair: :native_ml_kem_keypair,
|
|
20
|
+
keypair_from_seed: :native_ml_kem_keypair_from_seed,
|
|
21
|
+
encapsulate: :native_ml_kem_encapsulate,
|
|
22
|
+
decapsulate: :native_ml_kem_decapsulate,
|
|
23
|
+
}.freeze,
|
|
24
|
+
ml_kem_1024: {
|
|
25
|
+
keypair: :native_ml_kem_1024_keypair,
|
|
26
|
+
keypair_from_seed: :native_ml_kem_1024_keypair_from_seed,
|
|
27
|
+
encapsulate: :native_ml_kem_1024_encapsulate,
|
|
28
|
+
decapsulate: :native_ml_kem_1024_decapsulate,
|
|
19
29
|
}.freeze,
|
|
20
30
|
}.freeze
|
|
21
31
|
|
|
22
32
|
class << self
|
|
23
33
|
def generate(algorithm = CANONICAL_ALGORITHM)
|
|
24
34
|
algorithm = resolve_algorithm!(algorithm)
|
|
25
|
-
public_key, secret_key = PQCrypto.__send__(:
|
|
35
|
+
public_key, secret_key = PQCrypto.__send__(native_method_for(algorithm, :keypair))
|
|
26
36
|
Keypair.new(PublicKey.new(algorithm, public_key), SecretKey.new(algorithm, secret_key))
|
|
27
37
|
end
|
|
28
38
|
|
|
@@ -54,6 +64,26 @@ module PQCrypto
|
|
|
54
64
|
SecretKey.new(resolve_algorithm!(resolved_algorithm), bytes)
|
|
55
65
|
end
|
|
56
66
|
|
|
67
|
+
def secret_key_from_pkcs8_der(der)
|
|
68
|
+
secret_key_from_decoded_pkcs8(*PKCS8.decode_der(der))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def secret_key_from_pkcs8_pem(pem)
|
|
72
|
+
secret_key_from_decoded_pkcs8(*PKCS8.decode_pem(pem))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def public_key_from_spki_der(der, algorithm: nil)
|
|
76
|
+
resolved_algorithm, bytes = SPKI.decode_der(der)
|
|
77
|
+
validate_algorithm_match!(algorithm, resolved_algorithm) if algorithm
|
|
78
|
+
PublicKey.new(resolve_algorithm!(resolved_algorithm), bytes)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def public_key_from_spki_pem(pem, algorithm: nil)
|
|
82
|
+
resolved_algorithm, bytes = SPKI.decode_pem(pem)
|
|
83
|
+
validate_algorithm_match!(algorithm, resolved_algorithm) if algorithm
|
|
84
|
+
PublicKey.new(resolve_algorithm!(resolved_algorithm), bytes)
|
|
85
|
+
end
|
|
86
|
+
|
|
57
87
|
def details(algorithm)
|
|
58
88
|
DETAILS.fetch(resolve_algorithm!(algorithm)).dup
|
|
59
89
|
end
|
|
@@ -69,6 +99,37 @@ module PQCrypto
|
|
|
69
99
|
|
|
70
100
|
raise UnsupportedAlgorithmError, "Unsupported KEM algorithm: #{algorithm.inspect}"
|
|
71
101
|
end
|
|
102
|
+
|
|
103
|
+
def secret_key_from_decoded_pkcs8(algorithm, format, material)
|
|
104
|
+
secret_material = case format
|
|
105
|
+
when :seed
|
|
106
|
+
_public_key, expanded = PQCrypto.__send__(native_method_for(algorithm, :keypair_from_seed), material)
|
|
107
|
+
expanded
|
|
108
|
+
when :both
|
|
109
|
+
_seed, expanded = material
|
|
110
|
+
expanded
|
|
111
|
+
when :expanded
|
|
112
|
+
material
|
|
113
|
+
else
|
|
114
|
+
raise SerializationError, "Unsupported PKCS#8 private key format: #{format.inspect}"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
SecretKey.new(resolve_algorithm!(algorithm), secret_material)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def native_method_for(algorithm, operation)
|
|
121
|
+
NATIVE_DISPATCH.fetch(resolve_algorithm!(algorithm)).fetch(operation)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def validate_algorithm_match!(expected_algorithm, actual_algorithm)
|
|
125
|
+
expected = resolve_algorithm!(expected_algorithm)
|
|
126
|
+
return if expected == actual_algorithm
|
|
127
|
+
|
|
128
|
+
raise SerializationError,
|
|
129
|
+
"Expected #{expected.inspect}, got #{actual_algorithm.inspect} (SPKI key algorithm mismatch)"
|
|
130
|
+
rescue UnsupportedAlgorithmError => e
|
|
131
|
+
raise SerializationError, e.message
|
|
132
|
+
end
|
|
72
133
|
end
|
|
73
134
|
|
|
74
135
|
class Keypair
|
|
@@ -109,8 +170,16 @@ module PQCrypto
|
|
|
109
170
|
Serialization.public_key_to_pqc_container_pem(@algorithm, @bytes)
|
|
110
171
|
end
|
|
111
172
|
|
|
173
|
+
def to_spki_der
|
|
174
|
+
SPKI.encode_der(@algorithm, @bytes)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def to_spki_pem
|
|
178
|
+
SPKI.encode_pem(@algorithm, @bytes)
|
|
179
|
+
end
|
|
180
|
+
|
|
112
181
|
def encapsulate
|
|
113
|
-
ciphertext, shared_secret = PQCrypto.__send__(:
|
|
182
|
+
ciphertext, shared_secret = PQCrypto.__send__(KEM.send(:native_method_for, @algorithm, :encapsulate), @bytes)
|
|
114
183
|
EncapsulationResult.new(ciphertext, shared_secret)
|
|
115
184
|
rescue ArgumentError => e
|
|
116
185
|
raise InvalidKeyError, e.message
|
|
@@ -165,8 +234,30 @@ module PQCrypto
|
|
|
165
234
|
Serialization.secret_key_to_pqc_container_pem(@algorithm, @bytes)
|
|
166
235
|
end
|
|
167
236
|
|
|
237
|
+
def to_pkcs8_der(format: :expanded)
|
|
238
|
+
case format
|
|
239
|
+
when :expanded
|
|
240
|
+
PKCS8.encode_der(@algorithm, @bytes, format: :expanded)
|
|
241
|
+
when :seed, :both
|
|
242
|
+
raise SerializationError, "PKCS#8 #{format.inspect} export from KEM::SecretKey requires original seed material"
|
|
243
|
+
else
|
|
244
|
+
raise SerializationError, "Unsupported PKCS#8 private key format: #{format.inspect}"
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def to_pkcs8_pem(format: :expanded)
|
|
249
|
+
case format
|
|
250
|
+
when :expanded
|
|
251
|
+
PKCS8.encode_pem(@algorithm, @bytes, format: :expanded)
|
|
252
|
+
when :seed, :both
|
|
253
|
+
raise SerializationError, "PKCS#8 #{format.inspect} export from KEM::SecretKey requires original seed material"
|
|
254
|
+
else
|
|
255
|
+
raise SerializationError, "Unsupported PKCS#8 private key format: #{format.inspect}"
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
168
259
|
def decapsulate(ciphertext)
|
|
169
|
-
PQCrypto.__send__(:
|
|
260
|
+
PQCrypto.__send__(KEM.send(:native_method_for, @algorithm, :decapsulate), String(ciphertext).b, @bytes)
|
|
170
261
|
rescue ArgumentError => e
|
|
171
262
|
raise InvalidCiphertextError, e.message
|
|
172
263
|
end
|