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.
@@ -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(algorithm_symbol, secret_material, format:)
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
- unless private_key.is_a?(OpenSSL::ASN1::OctetString)
98
- raise SerializationError, "PKCS#8 privateKey must be an OCTET STRING"
99
- end
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
- def decode_pem(pem)
105
- der = der_from_pem(pem)
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 decode_version(value)
118
- raise SerializationError, "PKCS#8 version must be an INTEGER" unless value.is_a?(OpenSSL::ASN1::Integer)
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 decode_expanded_key(algorithm, choice_der)
169
- expanded = decode_asn1(choice_der)
170
- unless expanded.to_der.b == choice_der
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
- def encode_expanded_key_choice(secret_material, algorithm)
212
- bytes = String(secret_material).b
213
- validate_expanded_key_length!(algorithm, bytes)
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
- OpenSSL::ASN1::OctetString.new(bytes).to_der.b
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 encode_both_choice(secret_material, algorithm)
219
- unless secret_material.is_a?(Array) && secret_material.size == 2
220
- raise SerializationError, "PKCS#8 both format requires [seed, expandedKey]"
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
- def validate_expanded_key_length!(algorithm, expanded)
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 choice_profile(algorithm)
280
- PRIVATE_KEY_CHOICES.fetch(algorithm) do
281
- raise SerializationError, "PKCS#8 private key codec is not supported for #{algorithm.inspect}"
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 ensure_format_supported!(algorithm, format)
286
- if ml_dsa_algorithm?(algorithm) && %i[seed both].include?(format) && !allow_ml_dsa_seed_format
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 ml_dsa_algorithm?(algorithm)
298
- %i[ml_dsa_44 ml_dsa_65 ml_dsa_87].include?(algorithm)
113
+ def encrypted_der?(der)
114
+ pkcs8_native { PQCrypto.__send__(:native_pkcs8_encrypted_der?, der) }
299
115
  end
300
116
 
301
- def encode_tlv(tag, value)
302
- tag.chr.b + encode_der_length(value.bytesize) + value
303
- end
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
- def decode_tlv_value(der, expected_tag:, label:)
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 encode_der_length(length)
321
- raise SerializationError, "Invalid DER length" if length.negative?
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
- if length < 0x80 || (length_octets > 1 && der.getbyte(offset + 1).zero?)
354
- raise SerializationError, "PKCS#8 DER length is not minimally encoded"
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 decode_base64(body)
365
- compact = body.gsub(/[\r\n]/, "")
366
- unless compact.match?(/\A(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?\z/)
367
- raise SerializationError, "Invalid PKCS#8 PEM: invalid base64"
368
- end
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
- PQCrypto.__send__(:native_public_key_to_pqc_container_der, String(algorithm), String(bytes).b)
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
- PQCrypto.__send__(:native_public_key_to_pqc_container_pem, String(algorithm), String(bytes).b)
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
- PQCrypto.__send__(:native_secret_key_to_pqc_container_der, String(algorithm), String(bytes).b)
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
- PQCrypto.__send__(:native_secret_key_to_pqc_container_pem, String(algorithm), String(bytes).b)
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
- algorithm, bytes = PQCrypto.__send__(:native_public_key_from_pqc_container_der, String(der).b)
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
- algorithm, bytes = PQCrypto.__send__(:native_public_key_from_pqc_container_pem, String(pem).b)
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
- algorithm, bytes = PQCrypto.__send__(:native_secret_key_from_pqc_container_der, String(der).b)
65
- validate_algorithm_expectation!(expected_algorithm, algorithm)
66
- [algorithm, bytes]
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 secret_key_from_pqc_container_pem(expected_algorithm, pem)
72
- algorithm, bytes = PQCrypto.__send__(:native_secret_key_from_pqc_container_pem, String(pem).b)
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