Module: PQCrypto::PKCS8::PrivateKeyChoice
- Defined in:
- lib/pq_crypto/pkcs8/private_key_choice.rb
Constant Summary collapse
- SEED_TAG =
0x80- EXPANDED_TAG =
0x04- BOTH_TAG =
0x30- KEYPAIR_FROM_SEED_METHODS =
{ ml_kem_512: :native_ml_kem_512_keypair_from_seed, ml_kem_768: :native_ml_kem_keypair_from_seed, ml_kem_1024: :native_ml_kem_1024_keypair_from_seed, ml_dsa_44: :native_ml_dsa_44_keypair_from_seed, ml_dsa_65: :native_ml_dsa_keypair_from_seed, ml_dsa_87: :native_ml_dsa_87_keypair_from_seed, }.freeze
Class Method Summary collapse
- .consistency_error_message(algorithm) ⇒ Object
- .decode(algorithm, choice_der) ⇒ Object
- .decode_both(algorithm, choice_der) ⇒ Object
- .decode_expanded(algorithm, choice_der) ⇒ Object
- .decode_seed(algorithm, choice_der) ⇒ Object
- .encode(algorithm, secret_material, format) ⇒ Object
- .encode_both(algorithm, secret_material) ⇒ Object
- .encode_expanded(algorithm, secret_material) ⇒ Object
- .encode_seed(algorithm, secret_material) ⇒ Object
- .ensure_format_supported!(algorithm, format) ⇒ Object
- .expanded_bytes(algorithm) ⇒ Object
- .ml_dsa_algorithm?(algorithm) ⇒ Boolean
- .profile(algorithm) ⇒ Object
- .seed_bytes(algorithm) ⇒ Object
- .validate_expanded_length!(algorithm, expanded) ⇒ Object
- .validate_secret_key_algorithm!(algorithm, entry) ⇒ Object
- .validate_seed_length!(algorithm, seed) ⇒ Object
- .verify_both_consistency!(algorithm, seed, expanded) ⇒ Object
Class Method Details
.consistency_error_message(algorithm) ⇒ Object
179 180 181 182 183 |
# File 'lib/pq_crypto/pkcs8/private_key_choice.rb', line 179 def (algorithm) return "seed/expandedKey inconsistency in ML-DSA PKCS#8 'both' encoding (RFC 9881 §6)" if ml_dsa_algorithm?(algorithm) "seed/expandedKey inconsistency in PKCS#8 'both' encoding (RFC 9935 §8)" end |
.decode(algorithm, choice_der) ⇒ Object
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/pq_crypto/pkcs8/private_key_choice.rb', line 36 def decode(algorithm, choice_der) tag = choice_der.getbyte(0) raise SerializationError, "PKCS#8 privateKey CHOICE is empty" if tag.nil? case tag when SEED_TAG ensure_format_supported!(algorithm, :seed) decode_seed(algorithm, choice_der) when EXPANDED_TAG ensure_format_supported!(algorithm, :expanded) (algorithm, choice_der) when BOTH_TAG ensure_format_supported!(algorithm, :both) decode_both(algorithm, choice_der) else raise SerializationError, "Unsupported PKCS#8 #{algorithm.inspect} private key CHOICE tag: 0x#{tag.to_s(16).rjust(2, '0')}" end end |
.decode_both(algorithm, choice_der) ⇒ Object
123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/pq_crypto/pkcs8/private_key_choice.rb', line 123 def decode_both(algorithm, choice_der) _tag, body, next_offset = DER.decode_tlv(choice_der, 0, expected_tag: BOTH_TAG, label: "both") raise SerializationError, "PKCS#8 both contains trailing data" unless next_offset == choice_der.bytesize _seed_tag, seed_bytes, offset = DER.decode_tlv(body, 0, expected_tag: EXPANDED_TAG, label: "both seed") , , offset = DER.decode_tlv(body, offset, expected_tag: EXPANDED_TAG, label: "both expandedKey") raise SerializationError, "PKCS#8 both must contain exactly 2 elements" unless offset == body.bytesize validate_seed_length!(algorithm, seed_bytes) (algorithm, ) verify_both_consistency!(algorithm, seed_bytes, ) [algorithm, :both, [seed_bytes, ]] end |
.decode_expanded(algorithm, choice_der) ⇒ Object
115 116 117 118 119 120 121 |
# File 'lib/pq_crypto/pkcs8/private_key_choice.rb', line 115 def (algorithm, choice_der) _tag, bytes, next_offset = DER.decode_tlv(choice_der, 0, expected_tag: EXPANDED_TAG, label: "expandedKey") raise SerializationError, "PKCS#8 expandedKey contains trailing data" unless next_offset == choice_der.bytesize (algorithm, bytes) [algorithm, :expanded, bytes] end |
.decode_seed(algorithm, choice_der) ⇒ Object
107 108 109 110 111 112 113 |
# File 'lib/pq_crypto/pkcs8/private_key_choice.rb', line 107 def decode_seed(algorithm, choice_der) _tag, seed, next_offset = DER.decode_tlv(choice_der, 0, expected_tag: SEED_TAG, label: "seed") raise SerializationError, "PKCS#8 seed contains trailing data" unless next_offset == choice_der.bytesize validate_seed_length!(algorithm, seed) [algorithm, :seed, seed] end |
.encode(algorithm, secret_material, format) ⇒ Object
21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/pq_crypto/pkcs8/private_key_choice.rb', line 21 def encode(algorithm, secret_material, format) ensure_format_supported!(algorithm, format) case format when :seed encode_seed(algorithm, secret_material) when :expanded (algorithm, secret_material) when :both encode_both(algorithm, secret_material) else raise SerializationError, "Unsupported PKCS#8 private key format: #{format.inspect}" end end |
.encode_both(algorithm, secret_material) ⇒ Object
152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/pq_crypto/pkcs8/private_key_choice.rb', line 152 def encode_both(algorithm, secret_material) unless secret_material.is_a?(Array) && secret_material.size == 2 raise SerializationError, "PKCS#8 both format requires [seed, expandedKey]" end seed, = secret_material seed_bytes = Internal.binary_string(seed) = Internal.binary_string() validate_seed_length!(algorithm, seed_bytes) (algorithm, ) body = DER.encode_tlv(EXPANDED_TAG, seed_bytes) + DER.encode_tlv(EXPANDED_TAG, ) DER.encode_tlv(BOTH_TAG, body) end |
.encode_expanded(algorithm, secret_material) ⇒ Object
145 146 147 148 149 150 |
# File 'lib/pq_crypto/pkcs8/private_key_choice.rb', line 145 def (algorithm, secret_material) bytes = Internal.binary_string(secret_material) (algorithm, bytes) DER.encode_tlv(EXPANDED_TAG, bytes) end |
.encode_seed(algorithm, secret_material) ⇒ Object
138 139 140 141 142 143 |
# File 'lib/pq_crypto/pkcs8/private_key_choice.rb', line 138 def encode_seed(algorithm, secret_material) seed = Internal.binary_string(secret_material) validate_seed_length!(algorithm, seed) DER.encode_tlv(SEED_TAG, seed) end |
.ensure_format_supported!(algorithm, format) ⇒ Object
62 63 64 65 66 67 68 69 70 71 |
# File 'lib/pq_crypto/pkcs8/private_key_choice.rb', line 62 def ensure_format_supported!(algorithm, format) if ml_dsa_algorithm?(algorithm) && %i[seed both].include?(format) && !PKCS8.allow_ml_dsa_seed_format raise SerializationError, "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)" end return if profile(algorithm).fetch(:supported_formats).include?(format) raise SerializationError, "Unsupported PKCS#8 private key format for #{algorithm.inspect}: #{format.inspect}" end |
.expanded_bytes(algorithm) ⇒ Object
77 78 79 |
# File 'lib/pq_crypto/pkcs8/private_key_choice.rb', line 77 def (algorithm) profile(algorithm).fetch(:expanded_bytes) end |
.ml_dsa_algorithm?(algorithm) ⇒ Boolean
81 82 83 |
# File 'lib/pq_crypto/pkcs8/private_key_choice.rb', line 81 def ml_dsa_algorithm?(algorithm) %i[ml_dsa_44 ml_dsa_65 ml_dsa_87].include?(algorithm) end |
.profile(algorithm) ⇒ Object
85 86 87 88 89 |
# File 'lib/pq_crypto/pkcs8/private_key_choice.rb', line 85 def profile(algorithm) PKCS8::PRIVATE_KEY_CHOICES.fetch(algorithm) do raise SerializationError, "PKCS#8 private key codec is not supported for #{algorithm.inspect}" end end |
.seed_bytes(algorithm) ⇒ Object
73 74 75 |
# File 'lib/pq_crypto/pkcs8/private_key_choice.rb', line 73 def seed_bytes(algorithm) profile(algorithm).fetch(:seed_bytes) end |
.validate_expanded_length!(algorithm, expanded) ⇒ Object
99 100 101 102 103 104 105 |
# File 'lib/pq_crypto/pkcs8/private_key_choice.rb', line 99 def (algorithm, ) expected = (algorithm) return if .bytesize == expected raise SerializationError, "Invalid #{algorithm.inspect} expanded private key length: expected #{expected}, got #{.bytesize}" end |
.validate_secret_key_algorithm!(algorithm, entry) ⇒ Object
56 57 58 59 60 |
# File 'lib/pq_crypto/pkcs8/private_key_choice.rb', line 56 def validate_secret_key_algorithm!(algorithm, entry) return if PKCS8::PRIVATE_KEY_CHOICES.key?(algorithm) && %i[ml_kem ml_dsa].include?(entry.fetch(:family)) raise SerializationError, "PKCS#8 private key codec is not supported for #{algorithm.inspect}" end |
.validate_seed_length!(algorithm, seed) ⇒ Object
91 92 93 94 95 96 97 |
# File 'lib/pq_crypto/pkcs8/private_key_choice.rb', line 91 def validate_seed_length!(algorithm, seed) expected = seed_bytes(algorithm) return if seed.bytesize == expected raise SerializationError, "Invalid #{algorithm.inspect} seed private key length: expected #{expected}, got #{seed.bytesize}" end |
.verify_both_consistency!(algorithm, seed, expanded) ⇒ Object
167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/pq_crypto/pkcs8/private_key_choice.rb', line 167 def verify_both_consistency!(algorithm, seed, ) native_method = KEYPAIR_FROM_SEED_METHODS.fetch(algorithm, nil) return if native_method.nil? _public_key, = PQCrypto.__send__(native_method, seed) return if Internal.constant_time_equal?(, ) raise SerializationError, (algorithm) ensure Internal.safe_wipe() if defined?() end |