Module: PqcAsn1::DER
- Defined in:
- lib/pqc_asn1.rb,
lib/pqc_asn1.rb,
lib/pqc_asn1/key_sizes.rb
Overview
Unified result returned by DER.parse_spki and DER.parse_pkcs8.
An immutable value object carrying all parsed fields plus a format
field (:spki or :pkcs8) that identifies which structure was parsed.
For SPKI: key is the public key bytes (frozen ASCII-8BIT String).
For PKCS#8: key is a SecureBuffer; its backing memory is securely
zeroed when the object is garbage-collected.
Supports attribute access (+result.oid+) and deconstruct_keys
for pattern matching.
Defined Under Namespace
Classes: CompositeKeyInfo, Cursor, EncryptedKeyInfo, KeyInfo
Constant Summary collapse
- REGISTERED_KEY_SIZES =
Key sizes for algorithms registered at runtime via OID.register. Unlike KEY_SIZES (frozen at load time from oids.yml), this hash is mutable so OID.register can extend it without rebuilding KEY_SIZES. Intentionally a mutable constant (standard Ruby pattern); mutated only by OID.register under the GVL.
{}
- KEY_SIZES =
Known key sizes (in bytes) for NIST PQC algorithms. Source: FIPS 203 (ML-KEM), FIPS 204 (ML-DSA), FIPS 205 (SLH-DSA).
{ OID::ML_DSA_44 => { public: 1312, secret: 2560 }.freeze, OID::ML_DSA_65 => { public: 1952, secret: 4032 }.freeze, OID::ML_DSA_87 => { public: 2592, secret: 4896 }.freeze, OID::ML_KEM_512 => { public: 800, secret: 1632 }.freeze, OID::ML_KEM_768 => { public: 1184, secret: 2400 }.freeze, OID::ML_KEM_1024 => { public: 1568, secret: 3168 }.freeze, OID::SLH_DSA_SHA2_128S => { public: 32, secret: 64 }.freeze, OID::SLH_DSA_SHA2_128F => { public: 32, secret: 64 }.freeze, OID::SLH_DSA_SHA2_192S => { public: 48, secret: 96 }.freeze, OID::SLH_DSA_SHA2_192F => { public: 48, secret: 96 }.freeze, OID::SLH_DSA_SHA2_256S => { public: 64, secret: 128 }.freeze, OID::SLH_DSA_SHA2_256F => { public: 64, secret: 128 }.freeze, OID::SLH_DSA_SHAKE_128S => { public: 32, secret: 64 }.freeze, OID::SLH_DSA_SHAKE_128F => { public: 32, secret: 64 }.freeze, OID::SLH_DSA_SHAKE_192S => { public: 48, secret: 96 }.freeze, OID::SLH_DSA_SHAKE_192F => { public: 48, secret: 96 }.freeze, OID::SLH_DSA_SHAKE_256S => { public: 64, secret: 128 }.freeze, OID::SLH_DSA_SHAKE_256F => { public: 64, secret: 128 }.freeze, }.freeze
Class Attribute Summary collapse
-
.max_input_size ⇒ Integer?
Maximum input size in bytes, or nil to disable.
Class Method Summary collapse
-
._c_build_pkcs8 ⇒ PqcAsn1::SecureBuffer
Build a PKCS#8 / OneAsymmetricKey DER structure.
-
._c_build_spki ⇒ String
Build a SubjectPublicKeyInfo (SPKI) DER structure.
-
._c_parse_pkcs8 ⇒ PqcAsn1::DER::KeyInfo
Parse a PKCS#8 / OneAsymmetricKey DER structure.
-
._c_parse_spki ⇒ PqcAsn1::DER::KeyInfo
Parse a SubjectPublicKeyInfo DER structure.
-
.build_composite_spki ⇒ Object
Build a composite SubjectPublicKeyInfo DER structure.
-
.build_encrypted_pkcs8(encryption_algorithm_der, encrypted_data) ⇒ String
Build an EncryptedPrivateKeyInfo DER structure (RFC 5958).
-
.build_pkcs8(oid, secret_key, parameters: nil, public_key: nil, validate: true) ⇒ PqcAsn1::SecureBuffer
Build a PKCS#8 DER structure.
-
.build_spki(oid, public_key, parameters: nil, validate: true) ⇒ String
Build an SPKI DER structure.
- .check_input_size!(der) ⇒ Object
-
.parse_auto(der) ⇒ PqcAsn1::DER::KeyInfo, PqcAsn1::DER::EncryptedKeyInfo
Detect the DER format and dispatch to the appropriate parser.
-
.parse_composite_spki(_der) ⇒ Object
Parse a composite SubjectPublicKeyInfo DER structure.
-
.parse_encrypted_pkcs8(der) ⇒ PqcAsn1::DER::EncryptedKeyInfo
Parse an EncryptedPrivateKeyInfo DER structure (RFC 5958).
-
.parse_pem(pem) ⇒ PqcAsn1::DER::KeyInfo, PqcAsn1::DER::EncryptedKeyInfo
Decode a PEM string and parse the contained DER structure.
-
.parse_pkcs8(der) ⇒ Object
Parse a PKCS#8 DER structure with optional input size limit.
-
.parse_spki(der) ⇒ Object
Parse an SPKI DER structure with optional input size limit.
-
.validate_key_size(oid, key_bytesize, type) ⇒ true
Validate that a key's byte size matches the expected size for the given OID and type (:public or :secret).
Class Attribute Details
.max_input_size ⇒ Integer?
Returns maximum input size in bytes, or nil to disable.
813 814 815 |
# File 'lib/pqc_asn1.rb', line 813 def max_input_size @max_input_size end |
Class Method Details
._c_build_pkcs8 ⇒ PqcAsn1::SecureBuffer
Build a PKCS#8 / OneAsymmetricKey DER structure.
|
|
# File 'lib/pqc_asn1.rb', line 828
|
._c_build_spki ⇒ String
Build a SubjectPublicKeyInfo (SPKI) DER structure.
|
|
# File 'lib/pqc_asn1.rb', line 827
|
._c_parse_pkcs8 ⇒ PqcAsn1::DER::KeyInfo
Parse a PKCS#8 / OneAsymmetricKey DER structure.
|
|
# File 'lib/pqc_asn1.rb', line 830
|
._c_parse_spki ⇒ PqcAsn1::DER::KeyInfo
Parse a SubjectPublicKeyInfo DER structure.
|
|
# File 'lib/pqc_asn1.rb', line 829
|
.build_composite_spki ⇒ Object
Build a composite SubjectPublicKeyInfo DER structure.
Composite key structures (PQC + traditional hybrid algorithms) are defined in draft NIST/IETF standards and are not yet implemented. This placeholder stabilises the API surface.
1069 1070 1071 1072 1073 |
# File 'lib/pqc_asn1.rb', line 1069 def self.build_composite_spki(**) raise NotImplementedError, "Composite key support is not yet implemented. " \ "See https://github.com/msuliq/pqc_asn1/issues for status." end |
.build_encrypted_pkcs8(encryption_algorithm_der, encrypted_data) ⇒ String
Build an EncryptedPrivateKeyInfo DER structure (RFC 5958).
This gem is a codec — the caller must perform the actual encryption and provide the resulting ciphertext together with the AlgorithmIdentifier DER that describes the encryption scheme.
1011 1012 1013 1014 1015 1016 1017 1018 |
# File 'lib/pqc_asn1.rb', line 1011 def self.build_encrypted_pkcs8(encryption_algorithm_der, encrypted_data) raise ArgumentError, "encryption_algorithm_der must not be nil" if encryption_algorithm_der.nil? raise ArgumentError, "encrypted_data must not be nil" if encrypted_data.nil? enc_data_tlv = PqcAsn1::DER.write_tlv(0x04, encrypted_data) content = encryption_algorithm_der.b + enc_data_tlv PqcAsn1::DER.write_tlv(0x30, content).freeze end |
.build_pkcs8(oid, secret_key, parameters: nil, public_key: nil, validate: true) ⇒ PqcAsn1::SecureBuffer
Build a PKCS#8 DER structure.
When validate: true, checks that the secret key size matches
the expected size for the given OID (if known) before encoding.
|
|
# File 'lib/pqc_asn1.rb', line 250
|
.build_spki(oid, public_key, parameters: nil, validate: true) ⇒ String
Build an SPKI DER structure.
When validate: true, checks that the public key size matches
the expected size for the given OID (if known) before encoding.
|
|
# File 'lib/pqc_asn1.rb', line 243
|
.check_input_size!(der) ⇒ Object
892 893 894 895 896 897 898 899 900 901 902 |
# File 'lib/pqc_asn1.rb', line 892 def check_input_size!(der) limit = PqcAsn1::DER.max_input_size return unless limit size = der.respond_to?(:bytesize) ? der.bytesize : 0 return if size <= limit raise ArgumentError, "input size #{size} exceeds maximum allowed #{limit} bytes; " \ "increase PqcAsn1::DER.max_input_size or set to nil to disable" end |
.parse_auto(der) ⇒ PqcAsn1::DER::KeyInfo, PqcAsn1::DER::EncryptedKeyInfo
Detect the DER format and dispatch to the appropriate parser.
Accepts a String (SPKI, PKCS#8, or EncryptedPrivateKeyInfo) or a SecureBuffer (always dispatched as PKCS#8 — public keys have no need for secure memory).
916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 |
# File 'lib/pqc_asn1.rb', line 916 def self.parse_auto(der) # SecureBuffer input is always PKCS#8 — public keys don't need # secure memory, so dispatch directly without format detection. return parse_pkcs8(der) if der.is_a?(PqcAsn1::SecureBuffer) case detect_format(der) when :spki then parse_spki(der) when :pkcs8 then parse_pkcs8(der) when :encrypted_pkcs8 then parse_encrypted_pkcs8(der) when nil raise PqcAsn1::DERError.new( "cannot detect DER format: input does not start with a valid DER SEQUENCE", code: :der_parse ) else raise PqcAsn1::DERError.new( "cannot detect DER format: expected SPKI, PKCS#8, or EncryptedPrivateKeyInfo", code: :der_parse ) end end |
.parse_composite_spki(_der) ⇒ Object
Parse a composite SubjectPublicKeyInfo DER structure.
1078 1079 1080 1081 1082 |
# File 'lib/pqc_asn1.rb', line 1078 def self.parse_composite_spki(_der) raise NotImplementedError, "Composite key support is not yet implemented. " \ "See https://github.com/msuliq/pqc_asn1/issues for status." end |
.parse_encrypted_pkcs8(der) ⇒ PqcAsn1::DER::EncryptedKeyInfo
Parse an EncryptedPrivateKeyInfo DER structure (RFC 5958).
This gem is a codec — it does not decrypt the key material.
The returned EncryptedKeyInfo carries the opaque encryption_algorithm
DER and raw encrypted_data bytes so the caller can pass them to
their cipher implementation.
975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 |
# File 'lib/pqc_asn1.rb', line 975 def self.parse_encrypted_pkcs8(der) check_input_size!(der) cursor = Cursor.new(der) outer = cursor.read_sequence unless cursor.eof? raise PqcAsn1::DERError.new( "EncryptedPrivateKeyInfo parse error: trailing data after outer SEQUENCE", code: :trailing_data ) end # AlgorithmIdentifier is a SEQUENCE — capture the full TLV so callers # can pass it directly to their cipher without re-encoding. algo_tlv = outer.read_raw(0x30) encrypted_data = outer.read_octet_string unless outer.eof? raise PqcAsn1::DERError.new( "EncryptedPrivateKeyInfo parse error: extra fields after encryptedData", code: :extra_fields ) end EncryptedKeyInfo.new(algo_tlv, encrypted_data) end |
.parse_pem(pem) ⇒ PqcAsn1::DER::KeyInfo, PqcAsn1::DER::EncryptedKeyInfo
Decode a PEM string and parse the contained DER structure.
Uses the PEM label to choose the parser:
"PUBLIC KEY" → {parse_spki}
"PRIVATE KEY" → {parse_pkcs8}
"ENCRYPTED PRIVATE KEY" → {parse_encrypted_pkcs8}
Any other label raises PEMError.
950 951 952 953 954 955 956 957 958 959 960 961 962 963 |
# File 'lib/pqc_asn1.rb', line 950 def self.parse_pem(pem) result = PqcAsn1::PEM.decode_auto(pem) case result.label when "PUBLIC KEY" then parse_spki(result.data) when "PRIVATE KEY" then parse_pkcs8(result.data) when "ENCRYPTED PRIVATE KEY" then parse_encrypted_pkcs8(result.data) else raise PqcAsn1::PEMError.new( "unsupported PEM label #{result.label.inspect}: " \ "expected \"PUBLIC KEY\", \"PRIVATE KEY\", or \"ENCRYPTED PRIVATE KEY\"", code: :pem_label ) end end |
.parse_pkcs8(der) ⇒ Object
Parse a PKCS#8 DER structure with optional input size limit.
|
|
# File 'lib/pqc_asn1.rb', line 264
|
.parse_spki(der) ⇒ Object
Parse an SPKI DER structure with optional input size limit.
|
|
# File 'lib/pqc_asn1.rb', line 258
|
.validate_key_size(oid, key_bytesize, type) ⇒ true
Validate that a key's byte size matches the expected size for the given OID and type (:public or :secret).
Delegates to the C-level OID.validate_key_size table for known algorithms (O(1) lookup, no Ruby hash involved). Falls back to KEY_SIZES (built-in) then REGISTERED_KEY_SIZES (user-registered).
Raises ArgumentError if the OID is unknown (not in any table) or if the key size does not match the expected size.
1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 |
# File 'lib/pqc_asn1.rb', line 1035 def self.validate_key_size(oid, key_bytesize, type) # Fast path: C-level table for built-in NIST PQC algorithms. c_result = PqcAsn1::OID.validate_key_size(oid, key_bytesize, type == :public) return true if c_result # Fallback: built-in KEY_SIZES, then user-registered REGISTERED_KEY_SIZES. sizes = KEY_SIZES[oid] || REGISTERED_KEY_SIZES[oid] unless sizes name = (oid.respond_to?(:name) && oid.name) ? oid.name : oid.to_s raise ArgumentError, "unknown OID #{name} — cannot validate key size; " \ "register it with OID.register or pass validate: false" end expected = sizes[type] return true unless expected if key_bytesize != expected name = (oid.respond_to?(:name) && oid.name) ? oid.name : oid.to_s raise ArgumentError, "#{type} key size #{key_bytesize} does not match expected " \ "#{expected} for #{name}" end true end |