package Crypt::Perl::ECDSA::Parse; =encoding utf-8 =head1 NAME Crypt::Perl::ECDSA::Parse - ECDSA key parsing =head1 SYNOPSIS use Crypt::Perl::ECDSA::Parse (); #These accept either DER or PEM, native format or PKCS8. # my $prkey = Crypt::Perl::ECDSA::Parse::private($buffer); my $pbkey = Crypt::Perl::ECDSA::Parse::public($buffer); =head1 DISCUSSION See L and L for descriptions of the interfaces of these two classes. =cut use strict; use warnings; use Try::Tiny; use Crypt::Perl::ASN1 (); use Crypt::Perl::PKCS8 (); use Crypt::Perl::ToDER (); use Crypt::Perl::ECDSA::ECParameters (); use Crypt::Perl::X (); sub private { my ($pem_or_der) = @_; require Crypt::Perl::ECDSA::PrivateKey; Crypt::Perl::ToDER::ensure_der($pem_or_der); my $asn1 = _private_asn1(); my $asn1_ec = $asn1->find('ECPrivateKey'); my $struct; try { $struct = $asn1_ec->decode($pem_or_der); } catch { my $ec_err = $_; my $asn1_pkcs8 = $asn1->find('PrivateKeyInfo'); try { my $pk8_struct = $asn1_pkcs8->decode($pem_or_der); #It still might succeed, even if this is wrong, so don’t die(). if ( $pk8_struct->{'privateKeyAlgorithm'}{'algorithm'} ne Crypt::Perl::ECDSA::ECParameters::OID_ecPublicKey() ) { warn "Unknown private key algorithm OID: “$pk8_struct->{'privateKeyAlgorithm'}{'algorithm'}”"; } my $asn1_params = $asn1->find('EcpkParameters'); my $params = $asn1_params->decode($pk8_struct->{'privateKeyAlgorithm'}{'parameters'}); $struct = $asn1_ec->decode($pk8_struct->{'privateKey'}); $struct->{'parameters'} = $params; } catch { die Crypt::Perl::X::create('Generic', "Failed to decode private key as either ECDSA native ($ec_err) or PKCS8 ($_)"); }; }; my $key_parts = { version => $struct->{'version'}, private => Crypt::Perl::BigInt->from_bytes($struct->{'privateKey'}), public => Crypt::Perl::BigInt->from_bytes($struct->{'publicKey'}[0]), }; return Crypt::Perl::ECDSA::PrivateKey->new($key_parts, $struct->{'parameters'}); } sub public { my ($pem_or_der) = @_; require Crypt::Perl::ECDSA::PublicKey; Crypt::Perl::ToDER::ensure_der($pem_or_der); my $asn1 = _public_asn1(); my $asn1_ec = $asn1->find('ECPublicKey'); my $struct; try { $struct = $asn1_ec->decode($pem_or_der); } catch { my $ec_err = $_; my $asn1_pkcs8 = $asn1->find('SubjectPublicKeyInfo'); try { my $spk_struct = $asn1_pkcs8->decode($pem_or_der); #It still might succeed, even if this is wrong, so don’t die(). if ( $spk_struct->{'algorithm'}{'algorithm'} ne Crypt::Perl::ECDSA::ECParameters::OID_ecPublicKey() ) { warn "Unknown private key algorithm OID: “$spk_struct->{'algorithm'}{'algorithm'}”"; } my $asn1_params = $asn1->find('EcpkParameters'); my $params = $asn1_params->decode($spk_struct->{'algorithm'}{'parameters'}); $struct = { publicKey => $spk_struct->{'subjectPublicKey'} }; $struct->{'keydata'}{'parameters'} = $params; } catch { die Crypt::Perl::X::create('Generic', "Failed to decode public key as either ECDSA native ($ec_err) or SubjectPublicKeyInfo ($_)"); }; }; return Crypt::Perl::ECDSA::PublicKey->new( $struct->{'publicKey'}[0], $struct->{'keydata'}{'parameters'}, ); } sub jwk { my ($hr) = @_; require Crypt::Perl::ECDSA::NIST; require Crypt::Perl::ECDSA::EC::DB; require Crypt::Perl::Math; require MIME::Base64; my $curve_name = Crypt::Perl::ECDSA::NIST::get_curve_name_for_nist($hr->{'crv'}); my $curve_hr = Crypt::Perl::ECDSA::EC::DB::get_curve_data_by_name($curve_name); my $keylen = $curve_hr->{'p'}->bit_length(); my $pub_half_byte_length = Crypt::Perl::Math::ceil( $keylen / 8 ); my $x = MIME::Base64::decode_base64url($hr->{'x'}); my $y = MIME::Base64::decode_base64url($hr->{'y'}); #Make sure both halves are the proper length. substr($_, 0, 0) = ("\0" x ($pub_half_byte_length - length)) for ($x, $y); my $public = Crypt::Perl::BigInt->from_bytes("\x{04}$x$y"); if ($hr->{'d'}) { require Crypt::Perl::ECDSA::PrivateKey; require Crypt::Perl::JWK; my %args = ( version => 1, public => $public, private => Crypt::Perl::JWK::jwk_num_to_bigint($hr->{'d'}), ); return Crypt::Perl::ECDSA::PrivateKey->new_by_curve_name(\%args, $curve_name); } require Crypt::Perl::ECDSA::PublicKey; return Crypt::Perl::ECDSA::PublicKey->new_by_curve_name( $public, $curve_name); } #---------------------------------------------------------------------- sub _private_asn1 { my $template = join("\n", Crypt::Perl::ECDSA::PrivateKey->ASN1_PRIVATE(), Crypt::Perl::PKCS8::ASN1()); return Crypt::Perl::ASN1->new()->prepare($template); } sub _public_asn1 { my $template = join("\n", Crypt::Perl::ECDSA::PublicKey->ASN1_PUBLIC(), Crypt::Perl::PKCS8::ASN1()); return Crypt::Perl::ASN1->new()->prepare($template); } 1;