use v5.38; use feature 'class'; no warnings qw(experimental::class); # https://perldoc.perl.org/functions/pack#pack-TEMPLATE,LIST class KeyData { use MIME::Base64; use Carp qw(carp croak); #use Smart::Comments; field $key_data :param; field $noisy :param; field @packets; ADJUST { $self->readpgp; } method dearmor_pgp_key () { if ($key_data =~ m/-----BEGIN PGP PUBLIC KEY BLOCK-----/) { $key_data =~ s/-----BEGIN PGP PUBLIC KEY BLOCK-----\s*//s; $key_data =~ s/Comment:(.*)\s*//g; $key_data =~ s/Version:(.*)\s*//g; $key_data =~ s/Hash:(.*)\s*//g; $key_data =~ s/Charset:(.*)\s*//g; $key_data =~ s/-----END PGP PUBLIC KEY BLOCK-----\s*//s; $key_data = decode_base64($key_data); } elsif ($key_data =~ m/-----BEGIN PGP/) { carp 'This isn\'t key data.'; return 'stop'; } } method add ($Class) { push @packets, $Class; } method readpgp () { if ($key_data =~ m/-----BEGIN PGP/) { $key_data = $self->dearmor_pgp_key(); } return if $key_data eq 'stop'; my $offset = 0; my $i = 0; while ($offset < length($key_data)) { my $packet_size = $self->parse_pgp_packet(substr($key_data, $offset)); return unless $packet_size; $offset += $packet_size; say "offset: $offset\n" if $noisy; } } method parse_pgp_packet ($data) { my $byte = unpack('B8', substr($data, 0, 1)); # Packet Header ## # $byte my @bits = split(//, $byte); if ($bits[0] != 1) { carp "Invalid data"; return; } my $format = 'legacy'; ($bits[1] == 0 ? say 'Legacy Format' : (say 'OpenPGP Format' and $format = 'modern')) if $noisy; if ($bits[1] == 1) { $format = 'modern'; } my $tag = ''; if ($format eq 'legacy') { for my $b (@bits[2..5]) { $tag .= $b; } $tag = scalar reverse $tag; $tag = unpack('C', pack('b4', $tag)); } else { for my $b (@bits[2..7]) { $tag .= $b; } $tag = scalar reverse $tag; $tag = unpack('C', pack('b6', $tag)); } my $length_field; my $length_of_length; if ($format eq 'legacy') { # https://www.rfc-editor.org/rfc/rfc9580#section-4.2.2 if ($bits[7] and $bits[6]) { $length_of_length = 'bullshit'; die; } elsif ($bits[7]) { $length_of_length = 2; $length_field = unpack('n', substr($data, 1, $length_of_length)); } elsif ($bits[6]) { $length_of_length = 4; $length_field = unpack('N', substr($data, 1, $length_of_length)); warn "Give me the key that caused this to be emited"; } else { $length_of_length = 1; $length_field = unpack('C', substr($data, 1, $length_of_length)); } } else { # modern # https://www.rfc-editor.org/rfc/rfc9580#section-4.2.1 my $first_octet = unpack('C', substr($data, 1, 1)); my $second_octet = unpack('C', substr($data, 2, 1)); if ($first_octet < 192) { $length_of_length = 1; $length_field = $first_octet; } if ($first_octet >= 192 and $first_octet < 255) { $length_of_length = 2; $length_field = ((($first_octet - 192) << 8) + ($second_octet) + 192); # probably fine } if ($first_octet == 255) { $length_of_length = 4; #$subpacket_length = [4-octet scalar starting at 2nd_octet]; $length_field = (unpack('N', substr $data, 1, 4)); # idk warn "Give me the key that caused this to be emited"; } } my %tags = ( 0 => 'Reserved (Lol!)', 1 => 'Public Key Encrypted Session Key Packet', 2 => 'Signature Packet', 3 => 'Symmetric Key Encrypted Session Key Packet', 4 => 'One-Pass Signature Packet', 5 => 'Secret Key Packet', 6 => 'Public Key', 7 => 'Secret Subkey Packet', 8 => 'Compressed Data Packet', 9 => 'Symmetrically Encrypted Data Packet', 10 => 'Marker Packet', 11 => 'Literal Data Packet', 12 => 'Trust Packet', 13 => 'User ID Packet', 14 => 'Public Subkey Packet', 17 => 'User Attribute Packet', 18 => 'Symmetrically Encrypted and Integrity Protected Data Packet', 19 => 'Reserved', 20 => 'Reserved', 21 => 'Padding Packet', ); exists $tags{$tag} and say $tags{$tag} . " with length: $length_field" or say 'Unknown Packet' if $noisy; if ($tag == 2) { # sigs my $sigs = Signature->new(noisy => $noisy); $sigs->go($data, $length_field, $length_of_length); $self->add($sigs); } elsif ($tag == 6) { # pubkey my $pubs = PublicKey->new(pub_or_sub => 'pub', noisy => $noisy); $pubs->go($data, $length_field, $length_of_length); $self->add($pubs); } elsif ($tag == 14) { # subpubkey my $subpubs = PublicKey->new(pub_or_sub => 'sub', noisy => $noisy); $subpubs->go($data, $length_field, $length_of_length); $self->add($subpubs); } elsif ($tag == 13) { # user id my $uid = UserID->new(uid => substr($data, 2, $length_field)); say $uid->uid() if $noisy; $self->add($uid); } else { carp "Not handling this data '$tags{$tag}' ($tag)"; return; } return $length_field + $length_of_length + 1; # Return the number of bytes we just processed } # like `gpg --with-colons --list-keys --with-fingerprint --with-fingerprint` but not it. method machine_readable_gnupg () { # https://github.com/gpg/gnupg/blob/master/doc/DETAILS # 1 type of record # 2 validity # 3 key length # 4 pub algo # 5 keyid (fingerprint - 16) # 6 creation date # 7 expiration date # 8 Certificate S/N, UID hash, trust signature info jjakke-edition: pub/sub/sig: key-version # 9 ownertrust # a uid: userid jjakke-edition: sig: signature target # b signature class # c key capabilities # d issuer certificate fingerprint 'or other info' # # below is spaced out for me to read better # # 1 2 3 4 5 6 7 8 9 a # pub:u:255:22:9904E01052985080:1730221618:1856365618: :u: # 1 23456789 a b # fpr:::::::::50B750CC829A462D016AD9679904E01052985080: # 1 2 3 4 5 6 7 8 9 a # uid: : : : : : : : :Jake Thoughts (jjakke) : # 1 2 3 4 5 6 7 8 9 a b c d # sig: : :22:9904E01052985080:1730221618: : : : :[selfsig]: :13x: # 1 2 3 4 5 6 7 8 9 a # sub:u:255:18:190FCA50206CE28A:1730221618:1856365618: : : # 2 3 4 5 6 7 8 9 a # fpr: : : : : : : : :B99D79A47141D942494EBCF2190FCA50206CE28A: # 1 2 3 4 5 6 7 8 9 a b c d # sig: : :22:9904E01052985080:1730221618: : : : :[keybind]: :18x: my $string = ''; my $i = 0; for my $class (@packets) { if (builtin::blessed $class eq 'PublicKey') { $string .= $class->pub_or_sub(); $string .= ":REPLACEME:".$class->key_bits().":".$class->pub_key_algo().":"; if ($class->version() == '4') { # https://www.rfc-editor.org/rfc/rfc9580#section-5.5.4.2 $string .= substr($class->fingerprint(), -16); } else { # https://www.rfc-editor.org/rfc/rfc9580#section-5.5.4.3 $string .= substr($class->fingerprint(), 0, 16); } $string .= ":".$class->creation_time().":"; my $revoked = 0; my $expiration = 0; my $k = 0; for my $Signature (@packets) { if ($k > $i and builtin::blessed $Signature eq 'Signature') { if ($Signature->sigtype() eq '0x13' or $Signature->sigtype() eq '0x12' or $Signature->sigtype() eq '0x11' or $Signature->sigtype() eq '0x10' or $Signature->sigtype() eq '0x18' or $Signature->sigtype() eq '0x19' or $Signature->sigtype() eq '0x1F') { if ($Signature->key_expiration_time()) { $expiration = $class->creation_time() + $Signature->key_expiration_time(); $string .= $class->creation_time() + $Signature->key_expiration_time(); } last; } if ($Signature->sigtype() eq '0x20' or $Signature->sigtype() eq '0x28') { $revoked = 1; last; } } $k++; next; } if ($revoked) { $string =~ s/REPLACEME/r/; } if ($expiration and $expiration < time) { $string =~ s/REPLACEME/e/; } $string =~ s/REPLACEME/-/; $string .= ":" . $class->version()."::\n"; $string .= "fpr:::::::::" . $class->fingerprint() . ":\n"; } elsif (builtin::blessed $class eq 'UserID') { $string .= "uid:::::::::" . $class->uid() . ":\n"; } elsif (builtin::blessed $class eq 'Signature') { no warnings 'uninitialized'; my $keyid = $class->keyid_issuer(); # v6 doesn't generate these $keyid =~ s/\s//g; $keyid = lc $keyid; my $fpr_issuer = $class->fpr_issuer; # so that's what this is for $fpr_issuer =~ s/\s//g; $fpr_issuer = lc $fpr_issuer; # 1 2 3 4 5 6 7 8 9 a b c d # sig: : :22:9904E01052985080:1730221618: : : : :[keybind]: :18x: $class->sigtype eq '0x20' ? ($string .= "rev") : ($string .= 'sig'); $string .= ":::" . $class->pub_key_algo . ":" . $keyid . ":" . $class->creation . ":" . $class->expiration . ":". $class->version . "::". $class->signature_target .":" . $class->sigtype . "::" . $fpr_issuer . ":\n"; } $i++; } return $string; } } class PublicKey { use Digest::SHA qw(sha1_hex sha256_hex); field $version :reader; # 4/6 are supported. field $creation_time :reader; field $expiration_time :reader; field $pub_key_algo :reader; field $fingerprint :reader; field $key_bits :reader; field $pub_or_sub :param; field $noisy :param; method go ($data, $length_field, $length_of_length) { my $offset = $length_of_length + 1; $version = unpack('C', substr($data, $offset++, 1)); say "Key Version: $version" if $noisy; $creation_time = $self->octet_time(substr($data, $offset, 4)); say "Creation date: " . scalar gmtime $creation_time if $noisy; $offset += 4; if ($version == 3 or $version == 2) { my $_expiration = unpack('n', substr($data, $offset, 2)); # ^ 'days' so... $self->set_expiration( $_expiration * 86400 ); $offset += 2; } $pub_key_algo = unpack('C', substr($data, $offset++, 1)); my $PubKeyAlgos = PubKeyAlgos->new(); say "Pubkey Algo: " . $PubKeyAlgos->valid($pub_key_algo)->[0] . " ($pub_key_algo)" if $noisy; # https://www.rfc-editor.org/rfc/rfc9580#section-5.5.2.3 4o length of pubkey material in the next field. if ($version == 6) { $offset += 4; # skipping... } my $KeyBits = PublicKeyKeyBits->new(); $key_bits = $KeyBits->get_bits($data, $pub_key_algo, $offset); # ^ an misspelled scalar name causes segv when compiling O_O $self->calculate_fingerprint($data, $length_field, $length_of_length); } method calculate_fingerprint ($data, $length_field, $length_of_length) { if ($version == 4) { $fingerprint = sha1_hex(pack("C",0x99) . pack("n",$length_field) . substr($data,$length_of_length+1,$length_field)); say "Calculated fingerprint: $fingerprint" if $noisy; } elsif ($version == 6) { $fingerprint = sha256_hex(pack("C",0x9B) . pack("N",$length_field) . substr($data,$length_of_length+1,$length_field)); say "Calculated fingerprint: $fingerprint" if $noisy; } else { warn "Calculated fingerprint: unable to calcuate for version $version"; # v3 mainly # v3: The fingerprint of a version 3 key is formed by hashing the body (but not the 2-octet length) # of the MPIs that form the key material (public modulus n, followed by exponent e) with MD5 } } method pub_or_sub () { return $pub_or_sub; } method octet_time ($data) { my $_time; $_time = (unpack('N', substr $data, 0, 4)); return $_time; } method set_expiration ($epoch) { $expiration_time = $creation_time + $epoch; } } class PublicKeyKeyBits { # TODO: fix up later, good enough for now # https://www.rfc-editor.org/rfc/rfc9580#section-3.2 # https://www.rfc-editor.org/rfc/rfc9580#section-5.5.5 method get_bits ($data, $algo, $offset) { if ($algo == 1 or $algo == 2 or $algo == 3 or $algo == 16 or $algo == 17) { # RSA, Elgamal, DSA return unpack('n', substr($data, $offset++, 2)); } elsif ($algo == 18) { # ECDH my $oid_len = unpack('C', substr($data, $offset++, 1)); my $hex = $self->get_oid_hex($data, $offset,$oid_len); my $Curves = ECCCurves->new(); if ($Curves->valid( $hex )) { return $Curves->octets( $hex )->[4] * 8; } } elsif ($algo == 19) { # ECDSA my $oid_len = unpack('C', substr($data, $offset++, 1)); my $hex = $self->get_oid_hex($data, $offset,$oid_len); my $Curves = ECCCurves->new(); if ($Curves->valid( $hex )) { return $Curves->octets( $hex )->[4] * 8; } } elsif ($algo == 22) { # EdDSA (deprecated) # NOTE: https://www.rfc-editor.org/rfc/rfc9580#section-11.2.3 # For example, the length of an EdDSALegacy public key for the curve Ed25519Legacy is 263 bits: # 7 bits to represent the 0x40 prefix octet and 32 octets for the native value of the public key. # # I don't understand why gpg lists 255 as the bit size. #my $oid_len = unpack('C', substr($data, $offset++, 1)); #my $hex = $self->get_oid_hex($data, $offset,$oid_len); #my $Curves = ECCCurves->new(); #if ($Curves->valid( $hex )) { #return $Curves->octets( $hex )->[4] * 8; #} return 256; } elsif ($algo == 25) { # X25519 # https://www.rfc-editor.org/rfc/rfc7748#section-6.1 return 32 * 8; } elsif ($algo == 26) { # X448 # https://www.rfc-editor.org/rfc/rfc7748#section-6.2 return 56 * 8; } elsif ($algo == 27) { # Ed25519 # https://www.rfc-editor.org/rfc/rfc8032#section-5.1.5 return 256; } elsif ($algo == 28) { # Ed448 # https://www.rfc-editor.org/rfc/rfc8032#section-5.2.5 return 456; } } method get_oid_hex ($_data, $_offset, $_count) { my $i = 0; my $hex = ''; while ($i != $_count) { $hex .= sprintf("%02X", unpack('C', substr($_data, $_offset++, 1))); $i++; } return $hex; } } class UserID { field $uid :param; method uid () { return $uid; } } class Signature { field $noisy :param; field $version :reader; # 4/6 are supported. field $sigtype :reader; field $pub_key_algo :reader; field $hash_algo :reader; field $creation :reader; field $expiration :reader; field $key_expiration_time :reader; # for primary/sub key field $keyid_issuer :reader; # likely substr($fpr_issuer, -16); field $signature_target :reader; field $fpr_issuer :reader; field $key_flags :reader; field %subpackets; ADJUST { %subpackets = ( # not the full list of them, just the ones *I* care about. 2 => 'Signature Creation Time', # 4o 3 => 'Signature Expiration Time', # 4o nubmer of seconds after sig creation time 9 => 'Key Expiration Time', # 4o number of seconds after key creation time 16 => 'Issuer Key ID', # 8o (not existing for v6+ keys) 27 => 'Key Flags', # N octets of flags 31 => 'Signature Target', # what signature this refers to 33 => 'Issuer Fingerprint', # 1o key ver, N octets. v4 = 20o, v6 = 32o. MUST MATCH ); } method go($data, $length_field, $length_of_length) { my $offset = $length_of_length + 1; $version = unpack('C', substr($data, $offset++, 1)); # version say "version: $version" if $noisy; $sigtype = sprintf("0x%X", unpack('C', substr($data, $offset++, 1))); my $SignatureType = SignatureType->new(); say "signature type: ". $SignatureType->valid($sigtype) . " ($sigtype)" if $noisy; $pub_key_algo = unpack('C', substr($data, $offset++, 1)); my $PubKeyAlgos = PubKeyAlgos->new(); say "algo: " . $PubKeyAlgos->valid($pub_key_algo)->[0] . " ($pub_key_algo)" if $noisy; $hash_algo = unpack('C', substr($data, $offset++, 1)); my $HashAlgos = HashAlgos->new(); say "hash: " . $HashAlgos->valid($hash_algo)->[0] . " ($hash_algo)" if $noisy; my $hashed_subpacket_count; my $unhashed_subpacket_count; if ($version == 4) { # 2o $hashed_subpacket_count = unpack('n', substr($data, $offset, 2)); ## $hashed_subpacket_count $offset += 2; $unhashed_subpacket_count = unpack('n', substr($data, $offset + $hashed_subpacket_count, 2)); ## $unhashed_subpacket_count } else { # 6 4o $hashed_subpacket_count = unpack('N', substr($data, $offset, 4)); ## $hashed_subpacket_count $offset += 4; $unhashed_subpacket_count = unpack('N', substr($data, $offset + $hashed_subpacket_count, 4)); ## $unhashed_subpacket_count } $self->subpackets(substr($data, $offset, $hashed_subpacket_count)); $offset += $hashed_subpacket_count; if ($version == 4) { $offset += 2; } else { $offset += 4; } $self->subpackets(substr($data, $offset, $unhashed_subpacket_count)); } method subpackets ($data) { my $offset = 0; my $length = length($data); # ## $length while ($offset < length($data)) { # ## $offset my ($length_of_length, $subpacket_length, $typeid) = $self->subpacket_length($data, $offset); # ## $length_of_length # ## $subpacket_length # ## $typeid $offset += $length_of_length; if (exists $subpackets{$typeid}) { #print "$subpackets{$typeid} !! "; if ($typeid == 33) { (undef, $fpr_issuer) = $self->issuer_fingerprint( substr($data, $offset + $length_of_length, $subpacket_length) ); say $fpr_issuer if $noisy; } if ($typeid == 2) { $creation = $self->octet_time( substr($data, $offset + $length_of_length, $subpacket_length) ); say scalar gmtime $creation if $noisy; } if ($typeid == 27) { $key_flags = sprintf("%02X ", unpack('C', substr $data, $offset+$length_of_length, $subpacket_length)); # TODO say "0x$key_flags" if $noisy; } if ($typeid == 3) { $expiration = $self->octet_time( substr($data, $offset + $length_of_length, $subpacket_length) ); say scalar gmtime ( $creation + $expiration); } if ($typeid == 9) { $key_expiration_time = $self->octet_time( substr($data, $offset + $length_of_length, $subpacket_length) ); say $key_expiration_time if $noisy; } if ($typeid == 16) { $keyid_issuer = $self->key_id( substr($data, $offset + $length_of_length, $subpacket_length) ); say $keyid_issuer if $noisy; } if ($typeid == 31) { $signature_target = $self->_signature_target( substr($data, $offset + $length_of_length, $subpacket_length) ); say $signature_target if $noisy; } } $offset += $subpacket_length + 1; } } method _signature_target ($data) { my $offset = 0; my $algo = unpack('C', substr($data, $offset++, 1)); my $hash = unpack('C', substr($data, $offset++, 1)); my $_signature_target; # lmk when you see it (give key too) warn "_signature_target is untested"; while ($offset < length($data)) { $_signature_target .= sprintf("%02X ", unpack('C', substr($data, $offset++, 1))); } return $_signature_target; } # TODO doesnt verify lengths for the versions, rfc says it MUST match. method issuer_fingerprint ($data) { my $_fingerprint = ''; my $offset = 0; my $_version = unpack('C', substr($data, $offset++, 1)); while ($offset < length($data)) { $_fingerprint .= sprintf("%02X ", unpack('C', substr($data, $offset++, 1))); } return $_version,$_fingerprint; } # TODO RFC say they MUST match the repective bits on fingerprints method key_id ($data) { my $_keyid_issuer = ''; my $offset = 0; while ($offset < length($data)) { $_keyid_issuer .= sprintf("%02X ", unpack('C', substr($data, $offset++, 1))); } return $_keyid_issuer; } method octet_time ($data) { return (unpack('N', substr $data, 0, 4)); } # returns length_of_length, length subpacket, and typeid method subpacket_length ($data, $offset) { my ($length_of_length, $subpacket_length); my $first_octet = unpack('C', substr $data, $offset, 1); my $second_octet = unpack('C', substr $data, $offset+1, 1); my $typeid; if ($first_octet < 192) { $length_of_length = 1; $subpacket_length = $first_octet - 1; # The subpacket length field covers the encoded Subpacket # Type ID and the subpacket-specific data, and it does not # include the subpacket length field itself. $typeid = $second_octet; } if ($first_octet >= 192 and $first_octet < 255) { $length_of_length = 2; $subpacket_length = ((($first_octet - 192) << 8) + ($second_octet) + 192) - 2; $typeid = unpack('C', substr $data, $offset+2, 1); } if ($first_octet == 255) { $length_of_length = 5; #$subpacket_length = [4-octet scalar starting at 2nd_octet]; $subpacket_length = (unpack('N', substr $data, $offset+1, 4)) - 5; # idk warn "give me the key that caused this to be emitted"; $typeid = unpack('C', substr $data, $offset+4, 1); } return $length_of_length, $subpacket_length, $typeid; } } class SignatureType { field %types; ADJUST { %types = ( '0x00' => 'Binary Signature', '0x01' => 'Text Signature', '0x02' => 'Standalone Signature', '0x10' => 'Generic Certification Signature', '0x11' => 'Persona Certification Signature', '0x12' => 'Casual Certification Signature', '0x13' => 'Positive Certification Signature', '0x18' => 'Subkey Binding Signature', '0x19' => 'Primary Key Binding Signature', '0x1F' => 'Direct Key Signature', '0x20' => 'Key Revocation Signature', '0x28' => 'Subkey Revocation Signature', '0x30' => 'Certification Revocation Signature', '0x40' => 'Timestamp Signature', '0x50' => 'Third-Party Confirmation Signature', '0xFF' => 'Reserved', ); } method valid ($type) { exists $types{$type} and return $types{$type}; } } class Sigs::key_flags { field %key_flags; field %key_flags_letters; ADJUST { %key_flags = ( 0x01 => 'May be used to create User ID or Direct Key signatures', 0x02 => 'May be used to sign data', 0x04 => 'May be used to encrypt communications', 0x08 => 'May be used to encrypt storage', 0x10 => 'Private component of this key may have been split by a secret-sharing mechanism', 0x20 => 'May be used for authentication.', 0x80 => 'Private component may be in the pocession of more than one person', 0x0004 => 'Reserved (ADSK)', 0x0008 => 'Reserved (timestamping)', ); %key_flags_letters = ( 0x01 => 'Create UID|DK signatures', 0x02 => 'S', 0x04 => 'Ec', 0x08 => 'Es', 0x10 => 'Private component may have been split', 0x20 => 'A', 0x80 => 'Private component may be in the pocession of more than one person', 0x0004 => 'Reserved (ADSK)', 0x0008 => 'Reserved (timestamping)', ); } # can just look at the bits to see what the sig says the key can do. } # https://www.rfc-editor.org/rfc/rfc9580#section-9.1 class PubKeyAlgos { field %algos; ADJUST { %algos = ( # Algo .... Not adding the other details 0 => ['Reserved'], 1 => ['RSA (Encrypt or Sign)'], 2 => ['RSA Encrypt-Only'], 3 => ['RSA Sign-Only'], 16 => ['Elgamal (Encrypt-Only)'], 17 => ['DSA (Digital Signature Algorithm)'], 18 => ['ECDH public key algorithm'], 19 => ['ECDSA public key algorithm'], 20 => ['Reserved OR Elgamal Encrypt or Sign'], 21 => ['Reserved for Diffie-Hellman (X9.42)'], 22 => ['EdDSALegacy (deprecated)'], 23 => ['Reserved (AEDH)'], 24 => ['Reserved (AEDSA)'], 25 => ['X25519'], 26 => ['X448'], 27 => ['Ed25519'], 28 => ['Ed448'], ); # 27 & 25 are 'MUSTs' for rfc9580 # 17 & 16 were previous 'MUSTs' for rfc4880 } method valid ($number) { exists $algos{$number} and return $algos{$number}; if ($number < 110 or $number > 100) { return ['Private or Experimental Use']; } } } # https://www.rfc-editor.org/rfc/rfc9580#section-9.2 class ECCCurves { use Storable qw(dclone); field %helpme; ADJUST { %helpme = ( # ASN.1 OID OID Len Curve OID Octets Curve Name Usage Field Size '1.2.840.10045.3.1.7' => [8, '2A8648CE3D030107', 'NIST P-256', 'ECDSA,ECDH', 32], '1.3.132.0.34' => [5, '2B81040022', 'NIST P-384',' ECDSA,ECDH', 48], '1.3.132.0.35' => [5, '2B81040023', 'NIST P-521', 'ECDSA,ECDH', 66], '1.3.36.3.3.2.8.1.1.7' => [9, '2B2403030208010107', 'brainpoolP256r1','ECDSA,ECDH', 32], '1.3.36.3.3.2.8.1.1.11' => [9, '2B240303020801010B', 'brainpoolP384r1','ECDSA,ECDH', 48], '1.3.36.3.3.2.8.1.1.13' => [9, '2B240303020801010D', 'brainpoolP512r1','ECDSA,ECDH', 64], '1.3.6.1.4.1.11591.15.1' => [9, '2B06010401DA470F01', 'Ed25519Legacy','EdDSALegacy', 32], '1.3.6.1.4.1.3029.1.5.1' => [10,'2B060104019755010501', 'Curve25519Legacy', 'ECDH', 32] ); } method valid ($something) { ( $self->OID($something) or $self->octets($something) ) ? 1 : 0; } method OID ($oid) { exists $helpme{$oid} and return $helpme{oid}; } method octets ($octets) { my %octets; for my $oid (keys %helpme) { my $temp = $helpme{$oid}->[1]; $octets{$temp} = dclone($helpme{$oid}); $octets{$temp}->[1] = $oid; } exists $octets{$octets} and return $octets{$octets}; } } # https://www.rfc-editor.org/rfc/rfc9580#section-9.5 class HashAlgos { field %algos; ADJUST { %algos = ( # Algo Text Name v6 signature salt size OID 0 => ['Reserved', 'Reserved', 0], 1 => ['MD5', 'MD5', 0], 2 => ['SHA-1', 'SHA1', 0], 3 => ['RIPEMD-160','RIPEMD160', 0], 4 => ['Reserved', 'Reserved', 0], 5 => ['Reserved', 'Reserved', 0], 6 => ['Reserved', 'Reserved', 0], 7 => ['Reserved', 'Reserved', 0], 8 => ['SHA2-256', 'SHA256', 16], 9 => ['SHA2-384', 'SHA384', 24], 10 => ['SHA2-512', 'SHA512', 32], 11 => ['SHA2-224', 'SHA224', 16], 12 => ['SHA3-256', 'SHA3-256', 16], 13 => ['Reserved', 'Reserved', 0], 14 => ['SHA3-512', 'SHA3-512', 32], ); } method valid ($n) { exists $algos{$n} and return $algos{$n}; if ($n < 110 or $n > 100) { return ('Private or Experimental Use','Private or Experimental Use', 0); } } } # https://www.rfc-editor.org/rfc/rfc9580#section-9.6 class AEADAlgos { field %algos; ADJUST { %algos = ( # name, nonce length (octets), authentication tag length (octets) 0 => ['Reserved', 0, 0], 1 => ['EAX', 16, 16], 2 => ['OCB', 15, 16], 3 => ['GCM', 12, 16], ); } method valid ($n) { exists $algos{$n} and return $algos{$n}; if ($n < 110 or $n > 100) { return ('Private or Experimental Use','Private or Experimental Use', 0); } } }