Module: PQCrypto::PKCS8

Defined in:
lib/pq_crypto/pkcs8.rb

Constant Summary collapse

PEM_LABEL =
"PRIVATE KEY"
PEM_BEGIN =
"-----BEGIN #{PEM_LABEL}-----"
PEM_END =
"-----END #{PEM_LABEL}-----"
ML_KEM_SEED_BYTES =
64
ML_DSA_SEED_BYTES =
32
PRIVATE_KEY_CHOICES =
{
  ml_kem_512: {
    seed_bytes: ML_KEM_SEED_BYTES,
    expanded_bytes: PQCrypto::ML_KEM_512_SECRET_KEY_BYTES,
    supported_formats: %i[seed expanded both],
  }.freeze,
  ml_kem_768: {
    seed_bytes: ML_KEM_SEED_BYTES,
    expanded_bytes: PQCrypto::ML_KEM_SECRET_KEY_BYTES,
    supported_formats: %i[seed expanded both],
  }.freeze,
  ml_kem_1024: {
    seed_bytes: ML_KEM_SEED_BYTES,
    expanded_bytes: PQCrypto::ML_KEM_1024_SECRET_KEY_BYTES,
    supported_formats: %i[seed expanded both],
  }.freeze,
  ml_dsa_44: {
    seed_bytes: ML_DSA_SEED_BYTES,
    expanded_bytes: PQCrypto::SIGN_44_SECRET_KEY_BYTES,
    supported_formats: %i[seed expanded both],
  }.freeze,
  ml_dsa_65: {
    seed_bytes: ML_DSA_SEED_BYTES,
    expanded_bytes: PQCrypto::SIGN_SECRET_KEY_BYTES,
    supported_formats: %i[seed expanded both],
  }.freeze,
  ml_dsa_87: {
    seed_bytes: ML_DSA_SEED_BYTES,
    expanded_bytes: PQCrypto::SIGN_87_SECRET_KEY_BYTES,
    supported_formats: %i[seed expanded both],
  }.freeze,
}.freeze

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.allow_ml_dsa_seed_formatObject

Returns the value of attribute allow_ml_dsa_seed_format.



49
50
51
# File 'lib/pq_crypto/pkcs8.rb', line 49

def allow_ml_dsa_seed_format
  @allow_ml_dsa_seed_format
end

Class Method Details

.decode_der(der) ⇒ Object

Raises:



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/pq_crypto/pkcs8.rb', line 84

def decode_der(der)
  input = String(der).b
  outer = decode_asn1(input)
  raise SerializationError, "PKCS#8 DER contains trailing data" unless outer.to_der.b == input
  raise SerializationError, "PKCS#8 must be an ASN.1 SEQUENCE" unless outer.is_a?(OpenSSL::ASN1::Sequence)
  raise SerializationError, "PKCS#8 OneAsymmetricKey must contain exactly 3 elements" unless outer.value.size == 3

  version, algorithm_identifier, private_key = outer.value
  decode_version(version)
  algorithm = decode_algorithm_identifier(algorithm_identifier)
  entry = AlgorithmRegistry.fetch(algorithm)
  validate_secret_key_algorithm!(algorithm, entry)

  unless private_key.is_a?(OpenSSL::ASN1::OctetString)
    raise SerializationError, "PKCS#8 privateKey must be an OCTET STRING"
  end

  decode_private_key_choice(algorithm, String(private_key.value).b)
end

.decode_pem(pem) ⇒ Object



104
105
106
107
# File 'lib/pq_crypto/pkcs8.rb', line 104

def decode_pem(pem)
  der = der_from_pem(pem)
  decode_der(der)
end

.encode_der(algorithm_symbol, secret_material, format:) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/pq_crypto/pkcs8.rb', line 51

def encode_der(algorithm_symbol, secret_material, format:)
  entry = AlgorithmRegistry.fetch(algorithm_symbol)
  validate_secret_key_algorithm!(algorithm_symbol, entry)
  ensure_format_supported!(algorithm_symbol, format)

  choice_der = case format
               when :seed
                 encode_seed_choice(secret_material, algorithm_symbol)
               when :expanded
                 encode_expanded_key_choice(secret_material, algorithm_symbol)
               when :both
                 encode_both_choice(secret_material, algorithm_symbol)
               else
                 raise SerializationError, "Unsupported PKCS#8 private key format: #{format.inspect}"
               end

  OpenSSL::ASN1::Sequence.new([
    OpenSSL::ASN1::Integer.new(0),
    OpenSSL::ASN1::Sequence.new([
      OpenSSL::ASN1::ObjectId.new(AlgorithmRegistry.standard_oid(algorithm_symbol)),
    ]),
    OpenSSL::ASN1::OctetString.new(choice_der),
  ]).to_der.b
rescue OpenSSL::ASN1::ASN1Error => e
  raise SerializationError, e.message
end

.encode_pem(algorithm_symbol, secret_material, format:) ⇒ Object



78
79
80
81
82
# File 'lib/pq_crypto/pkcs8.rb', line 78

def encode_pem(algorithm_symbol, secret_material, format:)
  der = encode_der(algorithm_symbol, secret_material, format: format)
  body = encode_base64(der).scan(/.{1,64}/).join("\n")
  "#{PEM_BEGIN}\n#{body}\n#{PEM_END}\n"
end