package Crypt::Perl::RSA::Parse; =encoding utf-8 =head1 NAME Crypt::Perl::RSA::Parse - RSA key parsing =head1 SYNOPSIS use Crypt::Perl::RSA::Parse (); #These accept either DER or PEM, native format or PKCS8. # my $prkey = Crypt::Perl::RSA::Parse::private($buffer); my $pbkey = Crypt::Perl::RSA::Parse::public($buffer); =head1 DISCUSSION See L<Crypt::Perl::RSA::PrivateKey> and L<Crypt::Perl::RSA::PublicKey> for descriptions of the interfaces that this module returns. =cut use strict; use warnings; use Try::Tiny; use Crypt::Format (); use Module::Load (); use Crypt::Perl::ASN1 (); use Crypt::Perl::RSA::Template (); use Crypt::Perl::X (); sub _asn1 { return Crypt::Perl::ASN1->new()->prepare( Crypt::Perl::RSA::Template::get_template('INTEGER'), ); } sub private { my ( $pem_or_der) = @_; _ensure_der($pem_or_der); my $key_obj; try { my $parsed = _decode_rsa($pem_or_der); $key_obj = _new_private($parsed); } catch { my $rsa_err = $_; try { $key_obj = private_pkcs8($pem_or_der); } catch { die Crypt::Perl::X::create('Generic', "Failed to parse as either RSA ($rsa_err) or PKCS8 ($_)"); }; }; return $key_obj; } #Like private(), but only does PKCS8. sub private_pkcs8 { my ($pem_or_der) = @_; _ensure_der($pem_or_der); my $pkcs8 = _decode_pkcs8($pem_or_der); my $parsed = _decode_rsa_within_pkcs8_or_die($pkcs8); return _new_private($parsed); } #Checks for RSA format first, then falls back to PKCS8. sub public { my ($pem_or_der) = @_; _ensure_der($pem_or_der); my $key_obj; try { my $parsed = _decode_rsa_public($pem_or_der); $key_obj = _new_public($parsed); } catch { my $rsa_err = $_; try { $key_obj = public_SPKI($pem_or_der); } catch { die Crypt::Perl::X::create('Generic', "Failed to parse as either RSA ($rsa_err) or SubjectPublicKeyInfo ($_)"); }; }; return $key_obj; } #Like public(), but only does SubjectPublicKeyInfo. sub public_SPKI { my ($pem_or_der) = @_; _ensure_der($pem_or_der); my $spki = _decode_spki($pem_or_der); my $parsed = _decode_rsa_public_within_spki_or_die($spki); return _new_public($parsed); } my %JTK_TO_NEW = qw( n modulus e publicExponent d privateExponent p prime1 q prime2 dp exponent1 dq exponent2 qi coefficient ); sub jwk { my ($hr) = @_; my %constr_args; Module::Load::load('Crypt::Perl::JWK'); for my $k (keys %$hr) { next if !$JTK_TO_NEW{$k}; $constr_args{ $JTK_TO_NEW{$k} } = Crypt::Perl::JWK::jwk_num_to_bigint($hr->{$k}); } if ($hr->{'d'}) { $constr_args{'version'} = 0; Module::Load::load('Crypt::Perl::RSA::PrivateKey'); return Crypt::Perl::RSA::PrivateKey->new( \%constr_args ); } Module::Load::load('Crypt::Perl::RSA::PublicKey'); return Crypt::Perl::RSA::PublicKey->new( \%constr_args ); } #---------------------------------------------------------------------- sub _decode_macro { my ( $der_r, $macro ) = ( \$_[0], $_[1] ); my $parser = _asn1()->find($macro); return $parser->decode($$der_r); } #Checks for RSA format first, then falls back to PKCS8. sub _decode_rsa { my ($der_r) = (\$_[0]); return _decode_macro( $$der_r, 'RSAPrivateKey' ); } sub _decode_rsa_public { my ($der_r) = (\$_[0]); return _decode_macro( $$der_r, 'RSAPublicKey' ); } sub _decode_rsa_within_pkcs8_or_die { my ($pkcs8_hr) = @_; my $dec; try { $dec = _decode_rsa( $pkcs8_hr->{'privateKey'} ); } catch { die Crypt::Perl::X::create('Generic', "Failed to parse RSA within PKCS8: $_"); }; return $dec; } sub _decode_rsa_public_within_spki_or_die { my ($spki_hr) = @_; my $dec; try { $dec = _decode_rsa_public( $spki_hr->{'subjectPublicKey'}[0] ); } catch { die Crypt::Perl::X::create('Generic', "Failed to parse RSA within SubjectPublicKeyInfo: $_"); }; return $dec; } sub _decode_pkcs8 { my ($der_r) = (\$_[0]); return _decode_macro( $$der_r, 'PrivateKeyInfo' ); } sub _decode_spki { my ($der_r) = (\$_[0]); return _decode_macro( $$der_r, 'SubjectPublicKeyInfo' ); } sub _new_public { my ($parsed_hr) = @_; Module::Load::load('Crypt::Perl::RSA::PublicKey'); return Crypt::Perl::RSA::PublicKey->new($parsed_hr); } sub _new_private { my ($parsed_hr) = @_; Module::Load::load('Crypt::Perl::RSA::PrivateKey'); return Crypt::Perl::RSA::PrivateKey->new($parsed_hr); } #Modifies in-place. sub _pem_to_der { $_[0] = Crypt::Format::pem2der(@_); return; } sub _ensure_der { my ($pem_or_der_r) = \$_[0]; if ( $$pem_or_der_r =~ m<\A-> ) { _pem_to_der($$pem_or_der_r); } return; } 1;