pq_crypto 0.5.3 → 0.6.1
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 +18 -0
- data/GET_STARTED.md +70 -9
- data/README.md +11 -6
- data/ext/pqcrypto/extconf.rb +2 -0
- data/ext/pqcrypto/pq_externalmu.c +23 -18
- data/ext/pqcrypto/pqcrypto_native_api.h +10 -0
- data/ext/pqcrypto/pqcrypto_ruby_secure.c +351 -48
- data/ext/pqcrypto/pqcrypto_secure.c +615 -84
- data/ext/pqcrypto/pqcrypto_secure.h +35 -10
- data/ext/pqcrypto/pqcrypto_version.h +1 -1
- data/lib/pq_crypto/hybrid_kem.rb +2 -1
- data/lib/pq_crypto/internal.rb +23 -0
- data/lib/pq_crypto/kem.rb +79 -44
- data/lib/pq_crypto/key.rb +90 -0
- data/lib/pq_crypto/pkcs8/der.rb +68 -0
- data/lib/pq_crypto/pkcs8/private_key_choice.rb +186 -0
- data/lib/pq_crypto/pkcs8.rb +61 -304
- data/lib/pq_crypto/serialization.rb +19 -29
- data/lib/pq_crypto/signature.rb +81 -51
- data/lib/pq_crypto/version.rb +1 -1
- data/lib/pq_crypto.rb +16 -4
- metadata +10 -3
data/lib/pq_crypto/pkcs8.rb
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "openssl"
|
|
4
|
-
|
|
5
3
|
module PQCrypto
|
|
6
4
|
module PKCS8
|
|
7
5
|
PEM_LABEL = "PRIVATE KEY"
|
|
8
6
|
PEM_BEGIN = "-----BEGIN #{PEM_LABEL}-----"
|
|
9
7
|
PEM_END = "-----END #{PEM_LABEL}-----"
|
|
8
|
+
ENCRYPTED_PEM_LABEL = "ENCRYPTED PRIVATE KEY"
|
|
9
|
+
ENCRYPTED_PEM_BEGIN = "-----BEGIN #{ENCRYPTED_PEM_LABEL}-----"
|
|
10
|
+
ENCRYPTED_PEM_END = "-----END #{ENCRYPTED_PEM_LABEL}-----"
|
|
10
11
|
ML_KEM_SEED_BYTES = 64
|
|
11
12
|
ML_DSA_SEED_BYTES = 32
|
|
13
|
+
ENCRYPTED_PKCS8_DEFAULT_ITERATIONS = 200_000
|
|
12
14
|
|
|
13
15
|
@allow_ml_dsa_seed_format = false
|
|
14
16
|
|
|
@@ -48,340 +50,95 @@ module PQCrypto
|
|
|
48
50
|
class << self
|
|
49
51
|
attr_accessor :allow_ml_dsa_seed_format
|
|
50
52
|
|
|
51
|
-
def encode_der(
|
|
52
|
-
entry = AlgorithmRegistry.fetch(algorithm_symbol)
|
|
53
|
-
validate_secret_key_algorithm!(algorithm_symbol, entry)
|
|
54
|
-
ensure_format_supported!(algorithm_symbol, format)
|
|
55
|
-
|
|
56
|
-
choice_der = case format
|
|
57
|
-
when :seed
|
|
58
|
-
encode_seed_choice(secret_material, algorithm_symbol)
|
|
59
|
-
when :expanded
|
|
60
|
-
encode_expanded_key_choice(secret_material, algorithm_symbol)
|
|
61
|
-
when :both
|
|
62
|
-
encode_both_choice(secret_material, algorithm_symbol)
|
|
63
|
-
else
|
|
64
|
-
raise SerializationError, "Unsupported PKCS#8 private key format: #{format.inspect}"
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
OpenSSL::ASN1::Sequence.new([
|
|
68
|
-
OpenSSL::ASN1::Integer.new(0),
|
|
69
|
-
OpenSSL::ASN1::Sequence.new([
|
|
70
|
-
OpenSSL::ASN1::ObjectId.new(AlgorithmRegistry.standard_oid(algorithm_symbol)),
|
|
71
|
-
]),
|
|
72
|
-
OpenSSL::ASN1::OctetString.new(choice_der),
|
|
73
|
-
]).to_der.b
|
|
74
|
-
rescue OpenSSL::ASN1::ASN1Error => e
|
|
75
|
-
raise SerializationError, e.message
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def encode_pem(algorithm_symbol, secret_material, format:)
|
|
79
|
-
der = encode_der(algorithm_symbol, secret_material, format: format)
|
|
80
|
-
body = encode_base64(der).scan(/.{1,64}/).join("\n")
|
|
81
|
-
"#{PEM_BEGIN}\n#{body}\n#{PEM_END}\n"
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def decode_der(der)
|
|
85
|
-
input = String(der).b
|
|
86
|
-
outer = decode_asn1(input)
|
|
87
|
-
raise SerializationError, "PKCS#8 DER contains trailing data" unless outer.to_der.b == input
|
|
88
|
-
raise SerializationError, "PKCS#8 must be an ASN.1 SEQUENCE" unless outer.is_a?(OpenSSL::ASN1::Sequence)
|
|
89
|
-
raise SerializationError, "PKCS#8 OneAsymmetricKey must contain exactly 3 elements" unless outer.value.size == 3
|
|
90
|
-
|
|
91
|
-
version, algorithm_identifier, private_key = outer.value
|
|
92
|
-
decode_version(version)
|
|
93
|
-
algorithm = decode_algorithm_identifier(algorithm_identifier)
|
|
53
|
+
def encode_der(algorithm, secret_material, format:, passphrase: nil, iterations: ENCRYPTED_PKCS8_DEFAULT_ITERATIONS)
|
|
94
54
|
entry = AlgorithmRegistry.fetch(algorithm)
|
|
95
|
-
validate_secret_key_algorithm!(algorithm, entry)
|
|
55
|
+
PrivateKeyChoice.validate_secret_key_algorithm!(algorithm, entry)
|
|
96
56
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
decode_private_key_choice(algorithm, String(private_key.value).b)
|
|
102
|
-
end
|
|
57
|
+
choice_der = PrivateKeyChoice.encode(algorithm, secret_material, format)
|
|
58
|
+
der = private_key_info_to_der(algorithm, choice_der)
|
|
59
|
+
return der if passphrase.nil?
|
|
103
60
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
decode_der(der)
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
private
|
|
110
|
-
|
|
111
|
-
def decode_asn1(der)
|
|
112
|
-
OpenSSL::ASN1.decode(der)
|
|
113
|
-
rescue OpenSSL::ASN1::ASN1Error => e
|
|
61
|
+
encrypt_der(der, passphrase: passphrase, iterations: iterations)
|
|
62
|
+
rescue ArgumentError => e
|
|
114
63
|
raise SerializationError, e.message
|
|
64
|
+
ensure
|
|
65
|
+
Internal.safe_wipe(choice_der) if defined?(choice_der)
|
|
66
|
+
Internal.safe_wipe(der) if passphrase && defined?(der)
|
|
115
67
|
end
|
|
116
68
|
|
|
117
|
-
def
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
version = value.value.respond_to?(:to_i) ? value.value.to_i : value.value
|
|
121
|
-
raise SerializationError, "PKCS#8 version must be 0" unless version == 0
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def decode_algorithm_identifier(value)
|
|
125
|
-
unless value.is_a?(OpenSSL::ASN1::Sequence)
|
|
126
|
-
raise SerializationError, "PKCS#8 algorithm must be an AlgorithmIdentifier SEQUENCE"
|
|
127
|
-
end
|
|
128
|
-
unless value.value.size == 1
|
|
129
|
-
raise SerializationError, "PKCS#8 AlgorithmIdentifier parameters must be absent"
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
oid = value.value.first
|
|
133
|
-
raise SerializationError, "PKCS#8 AlgorithmIdentifier must contain an OBJECT IDENTIFIER" unless oid.is_a?(OpenSSL::ASN1::ObjectId)
|
|
134
|
-
|
|
135
|
-
algorithm = AlgorithmRegistry.by_standard_oid(oid.oid)
|
|
136
|
-
raise SerializationError, "Unsupported PKCS#8 algorithm OID: #{oid.oid}" if algorithm.nil?
|
|
137
|
-
|
|
138
|
-
algorithm
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def decode_private_key_choice(algorithm, choice_der)
|
|
142
|
-
tag = choice_der.getbyte(0)
|
|
143
|
-
raise SerializationError, "PKCS#8 privateKey CHOICE is empty" if tag.nil?
|
|
144
|
-
|
|
145
|
-
case tag
|
|
146
|
-
when 0x80
|
|
147
|
-
ensure_format_supported!(algorithm, :seed)
|
|
148
|
-
decode_seed_choice(algorithm, choice_der)
|
|
149
|
-
when 0x04
|
|
150
|
-
ensure_format_supported!(algorithm, :expanded)
|
|
151
|
-
decode_expanded_key(algorithm, choice_der)
|
|
152
|
-
when 0x30
|
|
153
|
-
ensure_format_supported!(algorithm, :both)
|
|
154
|
-
decode_both_choice(algorithm, choice_der)
|
|
155
|
-
else
|
|
156
|
-
raise SerializationError,
|
|
157
|
-
"Unsupported PKCS#8 #{algorithm.inspect} private key CHOICE tag: 0x#{tag.to_s(16).rjust(2, '0')}"
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def decode_seed_choice(algorithm, choice_der)
|
|
162
|
-
seed = decode_tlv_value(choice_der, expected_tag: 0x80, label: "seed")
|
|
163
|
-
validate_seed_length!(algorithm, seed)
|
|
164
|
-
|
|
165
|
-
[algorithm, :seed, seed]
|
|
69
|
+
def encode_pem(algorithm, secret_material, format:, passphrase: nil, iterations: ENCRYPTED_PKCS8_DEFAULT_ITERATIONS)
|
|
70
|
+
der = encode_der(algorithm, secret_material, format: format, passphrase: passphrase, iterations: iterations)
|
|
71
|
+
pkcs8_native { PQCrypto.__send__(:native_pkcs8_der_to_pem, der, !passphrase.nil?) }
|
|
166
72
|
end
|
|
167
73
|
|
|
168
|
-
def
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
raise SerializationError, "PKCS#8 expandedKey contains trailing data"
|
|
172
|
-
end
|
|
173
|
-
unless expanded.is_a?(OpenSSL::ASN1::OctetString)
|
|
174
|
-
raise SerializationError, "PKCS#8 expandedKey must be an OCTET STRING"
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
bytes = String(expanded.value).b
|
|
178
|
-
validate_expanded_key_length!(algorithm, bytes)
|
|
179
|
-
|
|
180
|
-
[algorithm, :expanded, bytes]
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def decode_both_choice(algorithm, choice_der)
|
|
184
|
-
both = decode_asn1(choice_der)
|
|
185
|
-
raise SerializationError, "PKCS#8 both contains trailing data" unless both.to_der.b == choice_der
|
|
186
|
-
raise SerializationError, "PKCS#8 both must be a SEQUENCE" unless both.is_a?(OpenSSL::ASN1::Sequence)
|
|
187
|
-
raise SerializationError, "PKCS#8 both must contain exactly 2 elements" unless both.value.size == 2
|
|
188
|
-
|
|
189
|
-
seed, expanded = both.value
|
|
190
|
-
raise SerializationError, "PKCS#8 both seed must be an OCTET STRING" unless seed.is_a?(OpenSSL::ASN1::OctetString)
|
|
191
|
-
unless expanded.is_a?(OpenSSL::ASN1::OctetString)
|
|
192
|
-
raise SerializationError, "PKCS#8 both expandedKey must be an OCTET STRING"
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
seed_bytes = String(seed.value).b
|
|
196
|
-
expanded_bytes = String(expanded.value).b
|
|
197
|
-
validate_seed_length!(algorithm, seed_bytes)
|
|
198
|
-
validate_expanded_key_length!(algorithm, expanded_bytes)
|
|
199
|
-
verify_both_consistency!(algorithm, seed_bytes, expanded_bytes)
|
|
200
|
-
|
|
201
|
-
[algorithm, :both, [seed_bytes, expanded_bytes]]
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
def encode_seed_choice(secret_material, algorithm)
|
|
205
|
-
seed = String(secret_material).b
|
|
206
|
-
validate_seed_length!(algorithm, seed)
|
|
207
|
-
|
|
208
|
-
encode_tlv(0x80, seed)
|
|
209
|
-
end
|
|
74
|
+
def decode_der(der, passphrase: nil)
|
|
75
|
+
input = Internal.binary_string(der)
|
|
76
|
+
return decode_encrypted_der(input, passphrase: passphrase) if encrypted_der?(input)
|
|
210
77
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
78
|
+
oid, choice_der = private_key_info_from_der(input)
|
|
79
|
+
algorithm = AlgorithmRegistry.by_standard_oid(oid)
|
|
80
|
+
raise SerializationError, "Unsupported PKCS#8 algorithm OID: #{oid}" if algorithm.nil?
|
|
214
81
|
|
|
215
|
-
|
|
82
|
+
entry = AlgorithmRegistry.fetch(algorithm)
|
|
83
|
+
PrivateKeyChoice.validate_secret_key_algorithm!(algorithm, entry)
|
|
84
|
+
PrivateKeyChoice.decode(algorithm, Internal.binary_string(choice_der))
|
|
85
|
+
rescue ArgumentError => e
|
|
86
|
+
raise SerializationError, e.message
|
|
87
|
+
ensure
|
|
88
|
+
Internal.safe_wipe(choice_der) if defined?(choice_der)
|
|
216
89
|
end
|
|
217
90
|
|
|
218
|
-
def
|
|
219
|
-
|
|
220
|
-
|
|
91
|
+
def decode_pem(pem, passphrase: nil)
|
|
92
|
+
_encrypted, der = pkcs8_native { PQCrypto.__send__(:native_pkcs8_pem_to_der, String(pem)) }
|
|
93
|
+
begin
|
|
94
|
+
decode_der(der, passphrase: passphrase)
|
|
95
|
+
ensure
|
|
96
|
+
Internal.safe_wipe(der)
|
|
221
97
|
end
|
|
222
|
-
|
|
223
|
-
seed, expanded = secret_material
|
|
224
|
-
seed_bytes = String(seed).b
|
|
225
|
-
expanded_bytes = String(expanded).b
|
|
226
|
-
validate_seed_length!(algorithm, seed_bytes)
|
|
227
|
-
validate_expanded_key_length!(algorithm, expanded_bytes)
|
|
228
|
-
|
|
229
|
-
OpenSSL::ASN1::Sequence.new([
|
|
230
|
-
OpenSSL::ASN1::OctetString.new(seed_bytes),
|
|
231
|
-
OpenSSL::ASN1::OctetString.new(expanded_bytes),
|
|
232
|
-
]).to_der.b
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
def verify_both_consistency!(algorithm, seed, expanded)
|
|
236
|
-
native_method = {
|
|
237
|
-
ml_kem_512: :native_ml_kem_512_keypair_from_seed,
|
|
238
|
-
ml_kem_768: :native_ml_kem_keypair_from_seed,
|
|
239
|
-
ml_kem_1024: :native_ml_kem_1024_keypair_from_seed,
|
|
240
|
-
ml_dsa_44: :native_ml_dsa_44_keypair_from_seed,
|
|
241
|
-
ml_dsa_65: :native_ml_dsa_keypair_from_seed,
|
|
242
|
-
ml_dsa_87: :native_ml_dsa_87_keypair_from_seed,
|
|
243
|
-
}[algorithm]
|
|
244
|
-
return if native_method.nil?
|
|
245
|
-
|
|
246
|
-
_public_key, expected_expanded = PQCrypto.__send__(native_method, seed)
|
|
247
|
-
return if PQCrypto.__send__(:native_ct_equals, expected_expanded, expanded)
|
|
248
|
-
|
|
249
|
-
message = if ml_dsa_algorithm?(algorithm)
|
|
250
|
-
"seed/expandedKey inconsistency in ML-DSA PKCS#8 'both' encoding (RFC 9881 §6)"
|
|
251
|
-
else
|
|
252
|
-
"seed/expandedKey inconsistency in PKCS#8 'both' encoding (RFC 9935 §8)"
|
|
253
|
-
end
|
|
254
|
-
raise SerializationError, message
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
def validate_seed_length!(algorithm, seed)
|
|
258
|
-
expected = choice_profile(algorithm).fetch(:seed_bytes)
|
|
259
|
-
return if seed.bytesize == expected
|
|
260
|
-
|
|
261
|
-
raise SerializationError,
|
|
262
|
-
"Invalid #{algorithm.inspect} seed private key length: expected #{expected}, got #{seed.bytesize}"
|
|
263
98
|
end
|
|
264
99
|
|
|
265
|
-
|
|
266
|
-
expected = choice_profile(algorithm).fetch(:expanded_bytes)
|
|
267
|
-
return if expanded.bytesize == expected
|
|
268
|
-
|
|
269
|
-
raise SerializationError,
|
|
270
|
-
"Invalid #{algorithm.inspect} expanded private key length: expected #{expected}, got #{expanded.bytesize}"
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
def validate_secret_key_algorithm!(algorithm_symbol, entry)
|
|
274
|
-
return if PRIVATE_KEY_CHOICES.key?(algorithm_symbol) && %i[ml_kem ml_dsa].include?(entry.fetch(:family))
|
|
275
|
-
|
|
276
|
-
raise SerializationError, "PKCS#8 private key codec is not supported for #{algorithm_symbol.inspect}"
|
|
277
|
-
end
|
|
100
|
+
private
|
|
278
101
|
|
|
279
|
-
def
|
|
280
|
-
|
|
281
|
-
|
|
102
|
+
def private_key_info_to_der(algorithm, choice_der)
|
|
103
|
+
pkcs8_native do
|
|
104
|
+
PQCrypto.__send__(:native_pkcs8_private_key_info_to_der,
|
|
105
|
+
AlgorithmRegistry.standard_oid(algorithm), choice_der)
|
|
282
106
|
end
|
|
283
107
|
end
|
|
284
108
|
|
|
285
|
-
def
|
|
286
|
-
|
|
287
|
-
raise SerializationError,
|
|
288
|
-
"ML-DSA seed-format PKCS#8 is opt-in; set PQCrypto::PKCS8.allow_ml_dsa_seed_format = true to enable (see SECURITY.md for caveats)"
|
|
289
|
-
end
|
|
290
|
-
|
|
291
|
-
profile = choice_profile(algorithm)
|
|
292
|
-
return if profile.fetch(:supported_formats).include?(format)
|
|
293
|
-
|
|
294
|
-
raise SerializationError, "Unsupported PKCS#8 private key format for #{algorithm.inspect}: #{format.inspect}"
|
|
109
|
+
def private_key_info_from_der(der)
|
|
110
|
+
pkcs8_native { PQCrypto.__send__(:native_pkcs8_private_key_info_from_der, der) }
|
|
295
111
|
end
|
|
296
112
|
|
|
297
|
-
def
|
|
298
|
-
|
|
113
|
+
def encrypted_der?(der)
|
|
114
|
+
pkcs8_native { PQCrypto.__send__(:native_pkcs8_encrypted_der?, der) }
|
|
299
115
|
end
|
|
300
116
|
|
|
301
|
-
def
|
|
302
|
-
|
|
303
|
-
|
|
117
|
+
def encrypt_der(der, passphrase:, iterations:)
|
|
118
|
+
iterations = Integer(iterations)
|
|
119
|
+
raise SerializationError, "Encrypted PKCS#8 iterations must be positive" unless iterations.positive?
|
|
304
120
|
|
|
305
|
-
|
|
306
|
-
tag = der.getbyte(0)
|
|
307
|
-
unless tag == expected_tag
|
|
308
|
-
raise SerializationError, "PKCS#8 #{label} has unexpected tag: 0x#{tag.to_s(16).rjust(2, '0')}"
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
length, length_bytes = decode_der_length(der, 1)
|
|
312
|
-
value_offset = 1 + length_bytes
|
|
313
|
-
value_end = value_offset + length
|
|
314
|
-
raise SerializationError, "PKCS#8 #{label} length exceeds available data" if value_end > der.bytesize
|
|
315
|
-
raise SerializationError, "PKCS#8 #{label} contains trailing data" unless value_end == der.bytesize
|
|
316
|
-
|
|
317
|
-
der.byteslice(value_offset, length).b
|
|
121
|
+
pkcs8_native { PQCrypto.__send__(:native_pkcs8_encrypt_der, der, String(passphrase), iterations) }
|
|
318
122
|
end
|
|
319
123
|
|
|
320
|
-
def
|
|
321
|
-
raise SerializationError, "
|
|
322
|
-
return length.chr.b if length < 0x80
|
|
323
|
-
|
|
324
|
-
encoded = []
|
|
325
|
-
remaining = length
|
|
326
|
-
until remaining.zero?
|
|
327
|
-
encoded.unshift(remaining & 0xff)
|
|
328
|
-
remaining >>= 8
|
|
329
|
-
end
|
|
330
|
-
|
|
331
|
-
(0x80 | encoded.length).chr.b + encoded.pack("C*").b
|
|
332
|
-
end
|
|
333
|
-
|
|
334
|
-
def decode_der_length(der, offset)
|
|
335
|
-
first = der.getbyte(offset)
|
|
336
|
-
raise SerializationError, "PKCS#8 DER length is missing" if first.nil?
|
|
337
|
-
|
|
338
|
-
return [first, 1] if first < 0x80
|
|
339
|
-
|
|
340
|
-
length_octets = first & 0x7f
|
|
341
|
-
raise SerializationError, "PKCS#8 DER indefinite length is not allowed" if length_octets.zero?
|
|
342
|
-
raise SerializationError, "PKCS#8 DER length is too large" if length_octets > 4
|
|
343
|
-
if offset + 1 + length_octets > der.bytesize
|
|
344
|
-
raise SerializationError, "PKCS#8 DER length exceeds available data"
|
|
345
|
-
end
|
|
346
|
-
|
|
347
|
-
length = 0
|
|
348
|
-
length_octets.times do |i|
|
|
349
|
-
byte = der.getbyte(offset + 1 + i)
|
|
350
|
-
length = (length << 8) | byte
|
|
351
|
-
end
|
|
124
|
+
def decode_encrypted_der(der, passphrase:)
|
|
125
|
+
raise SerializationError, "Encrypted PKCS#8 requires passphrase" if passphrase.nil?
|
|
352
126
|
|
|
353
|
-
|
|
354
|
-
|
|
127
|
+
plain_der = pkcs8_native { PQCrypto.__send__(:native_pkcs8_decrypt_der, der, String(passphrase)) }
|
|
128
|
+
begin
|
|
129
|
+
decode_der(plain_der)
|
|
130
|
+
ensure
|
|
131
|
+
Internal.safe_wipe(plain_der)
|
|
355
132
|
end
|
|
356
|
-
|
|
357
|
-
[length, 1 + length_octets]
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
def encode_base64(bytes)
|
|
361
|
-
[String(bytes).b].pack("m0")
|
|
362
133
|
end
|
|
363
134
|
|
|
364
|
-
def
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
compact.unpack1("m0").b
|
|
371
|
-
rescue ArgumentError => e
|
|
135
|
+
def pkcs8_native
|
|
136
|
+
yield
|
|
137
|
+
rescue SerializationError
|
|
138
|
+
raise
|
|
139
|
+
rescue PQCrypto::Error => e
|
|
372
140
|
raise SerializationError, e.message
|
|
373
141
|
end
|
|
374
|
-
|
|
375
|
-
def der_from_pem(pem)
|
|
376
|
-
text = String(pem)
|
|
377
|
-
match = text.match(/\A#{Regexp.escape(PEM_BEGIN)}\r?\n(?<body>[A-Za-z0-9+\/=\r\n]+)\r?\n#{Regexp.escape(PEM_END)}[ \t\r\n]*\z/)
|
|
378
|
-
raise SerializationError, "Invalid PKCS#8 PEM: expected #{PEM_LABEL.inspect} label" unless match
|
|
379
|
-
|
|
380
|
-
body = match[:body]
|
|
381
|
-
raise SerializationError, "Invalid PKCS#8 PEM: embedded NUL in body" if body.include?("\0")
|
|
382
|
-
|
|
383
|
-
decode_base64(body)
|
|
384
|
-
end
|
|
385
142
|
end
|
|
386
143
|
end
|
|
387
144
|
end
|
|
@@ -21,63 +21,53 @@ module PQCrypto
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def public_key_to_pqc_container_der(algorithm, bytes)
|
|
24
|
-
|
|
25
|
-
rescue ArgumentError, PQCrypto::Error => e
|
|
26
|
-
raise SerializationError, e.message
|
|
24
|
+
dump(:native_public_key_to_pqc_container_der, algorithm, bytes)
|
|
27
25
|
end
|
|
28
26
|
|
|
29
27
|
def public_key_to_pqc_container_pem(algorithm, bytes)
|
|
30
|
-
|
|
31
|
-
rescue ArgumentError, PQCrypto::Error => e
|
|
32
|
-
raise SerializationError, e.message
|
|
28
|
+
dump(:native_public_key_to_pqc_container_pem, algorithm, bytes)
|
|
33
29
|
end
|
|
34
30
|
|
|
35
31
|
def secret_key_to_pqc_container_der(algorithm, bytes)
|
|
36
|
-
|
|
37
|
-
rescue ArgumentError, PQCrypto::Error => e
|
|
38
|
-
raise SerializationError, e.message
|
|
32
|
+
dump(:native_secret_key_to_pqc_container_der, algorithm, bytes)
|
|
39
33
|
end
|
|
40
34
|
|
|
41
35
|
def secret_key_to_pqc_container_pem(algorithm, bytes)
|
|
42
|
-
|
|
43
|
-
rescue ArgumentError, PQCrypto::Error => e
|
|
44
|
-
raise SerializationError, e.message
|
|
36
|
+
dump(:native_secret_key_to_pqc_container_pem, algorithm, bytes)
|
|
45
37
|
end
|
|
46
38
|
|
|
47
39
|
def public_key_from_pqc_container_der(expected_algorithm, der)
|
|
48
|
-
|
|
49
|
-
validate_algorithm_expectation!(expected_algorithm, algorithm)
|
|
50
|
-
[algorithm, bytes]
|
|
51
|
-
rescue ArgumentError, PQCrypto::Error => e
|
|
52
|
-
raise SerializationError, e.message
|
|
40
|
+
load(:native_public_key_from_pqc_container_der, expected_algorithm, der)
|
|
53
41
|
end
|
|
54
42
|
|
|
55
43
|
def public_key_from_pqc_container_pem(expected_algorithm, pem)
|
|
56
|
-
|
|
57
|
-
validate_algorithm_expectation!(expected_algorithm, algorithm)
|
|
58
|
-
[algorithm, bytes]
|
|
59
|
-
rescue ArgumentError, PQCrypto::Error => e
|
|
60
|
-
raise SerializationError, e.message
|
|
44
|
+
load(:native_public_key_from_pqc_container_pem, expected_algorithm, pem)
|
|
61
45
|
end
|
|
62
46
|
|
|
63
47
|
def secret_key_from_pqc_container_der(expected_algorithm, der)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
48
|
+
load(:native_secret_key_from_pqc_container_der, expected_algorithm, der)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def secret_key_from_pqc_container_pem(expected_algorithm, pem)
|
|
52
|
+
load(:native_secret_key_from_pqc_container_pem, expected_algorithm, pem)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def dump(native_method, algorithm, bytes)
|
|
58
|
+
PQCrypto.__send__(native_method, String(algorithm), Internal.binary_string(bytes))
|
|
67
59
|
rescue ArgumentError, PQCrypto::Error => e
|
|
68
60
|
raise SerializationError, e.message
|
|
69
61
|
end
|
|
70
62
|
|
|
71
|
-
def
|
|
72
|
-
algorithm, bytes = PQCrypto.__send__(
|
|
63
|
+
def load(native_method, expected_algorithm, source)
|
|
64
|
+
algorithm, bytes = PQCrypto.__send__(native_method, Internal.binary_string(source))
|
|
73
65
|
validate_algorithm_expectation!(expected_algorithm, algorithm)
|
|
74
66
|
[algorithm, bytes]
|
|
75
67
|
rescue ArgumentError, PQCrypto::Error => e
|
|
76
68
|
raise SerializationError, e.message
|
|
77
69
|
end
|
|
78
70
|
|
|
79
|
-
private
|
|
80
|
-
|
|
81
71
|
def validate_algorithm_expectation!(expected_algorithm, actual_algorithm)
|
|
82
72
|
return if expected_algorithm.nil? || expected_algorithm == actual_algorithm
|
|
83
73
|
|