Crypt-JWS-OpenSSL
view release on metacpan or search on metacpan
lib/Crypt/JWS/OpenSSL/Algorithm/RSA.pm view on Meta::CPAN
package Crypt::JWS::OpenSSL::Algorithm::RSA;
$Crypt::JWS::OpenSSL::Algorithm::RSA::VERSION = '0.005';
use Moo;
with qw(
Crypt::JWS::OpenSSL::Role::Algorithm
Crypt::JWS::OpenSSL::Role::Encoder
);
use Crypt::OpenSSL::Random 0.15;
use Crypt::OpenSSL::RSA 0.33;
use Crypt::OpenSSL::Bignum;
use Crypt::JWS::OpenSSL::Local;
use Digest::SHA;
use Carp qw( croak );
use namespace::clean;
my $RSA_ALGO_MAP = {
'RS256' => [ 'use_sha256_hash', 'use_pkcs1_padding' ],
'RS384' => [ 'use_sha384_hash', 'use_pkcs1_padding' ],
'RS512' => [ 'use_sha512_hash', 'use_pkcs1_padding' ],
'PS256' => [ 'use_sha256_hash', 'use_pkcs1_pss_padding' ],
'PS384' => [ 'use_sha384_hash', 'use_pkcs1_pss_padding' ],
'PS512' => [ 'use_sha512_hash', 'use_pkcs1_pss_padding' ],
};
has 'can_use_pkcs1_padding' => ( is => 'lazy' );
has '_version_supports_pss' => ( is => 'lazy' );
sub can_use_pkcs1_pss_padding {
return ($_[0]->_version_supports_pss && $Crypt::JWS::OpenSSL::Local::HAS_OPENSSL3 ) ? 1 : 0;
}
sub sign {
my( $self, $request) = parse_params(@_);
my $algorithm = $request->{algorithm};
my $keyref = ref( $request->{key} );
my $rsa;
if ($keyref) {
if ( $keyref eq 'HASH' ) {
my $keyparams = $self->_get_private_key_params( $request->{key} );
$rsa = Crypt::OpenSSL::RSA->new_key_from_parameters( @$keyparams, 'check' => 1 );
} else {
croak 'invalid jwk reference';
}
} else {
$rsa = Crypt::OpenSSL::RSA->new_private_key($request->{key});
}
for my $method ( @{ $RSA_ALGO_MAP->{$algorithm} } ) {
$rsa->$method;
}
return $rsa->sign($request->{message});
}
sub verify {
my( $self, $request) = parse_params(@_);
my $algorithm = $request->{algorithm};
my $keyref = ref( $request->{key} );
my $rsa;
if ($keyref) {
if ( $keyref eq 'HASH' ) {
my $keyparams = $self->_get_public_key_params( $request->{key} );
$rsa = Crypt::OpenSSL::RSA->new_key_from_parameters( @$keyparams );
} else {
croak 'invalid jwk reference';
}
} else {
$rsa = Crypt::OpenSSL::RSA->new_public_key($request->{key});
}
for my $method ( @{ $RSA_ALGO_MAP->{$algorithm} } ) {
$rsa->$method;
}
return $rsa->verify($request->{message}, $request->{signature}) ? 1 : 0;
}
sub _get_private_key_params {
my( $self, $jwk) = @_;
my ($n, $e, $d, $p, $q );
my @bignums = ();
if (exists($jwk->{'n'}) && defined($jwk->{'n'})) {
$n = Crypt::OpenSSL::Bignum->new_from_bin($self->decode_jwk_element($jwk->{'n'}));
}
if (exists($jwk->{'e'}) && defined($jwk->{'e'})) {
$e = Crypt::OpenSSL::Bignum->new_from_bin($self->decode_jwk_element($jwk->{'e'}));
}
if (exists($jwk->{'d'}) && defined($jwk->{'d'})) {
$d = Crypt::OpenSSL::Bignum->new_from_bin($self->decode_jwk_element($jwk->{'d'}));
}
if (exists($jwk->{'p'}) && defined($jwk->{'p'})) {
$p = Crypt::OpenSSL::Bignum->new_from_bin($self->decode_jwk_element($jwk->{'p'}));
}
if (exists($jwk->{'q'}) && defined($jwk->{'q'})) {
$q = Crypt::OpenSSL::Bignum->new_from_bin($self->decode_jwk_element($jwk->{'q'}));
}
if ( $n && $e && $d ) {
push( @bignums, $n, $e, $d );
} else {
croak 'could not extract parameters from private RSA JWK';
}
if ( $p && $q ) {
push( @bignums, $p, $q );
} else {
push( @bignums, undef, undef);
}
return \@bignums;
}
sub _get_public_key_params {
my( $self, $jwk) = @_;
my ( $n, $e, $d, $p, $q );
if (exists($jwk->{'n'}) && defined($jwk->{'n'})) {
$n = Crypt::OpenSSL::Bignum->new_from_bin($self->decode_jwk_element($jwk->{'n'}));
}
if (exists($jwk->{'e'}) && defined($jwk->{'e'})) {
$e = Crypt::OpenSSL::Bignum->new_from_bin($self->decode_jwk_element($jwk->{'e'}));
}
unless ( $n && $e ) {
croak 'could not extract parameters from public RSA JWK';
}
my @bignums = ( $n, $e, $d, $p, $q );
return \@bignums;
}
sub _build_can_use_pkcs1_padding {
my $self = shift;
my $_rsa_ver = $self->numify_version($Crypt::OpenSSL::RSA::VERSION);
### version 0.36 and 0.37 are broken
if ( $_rsa_ver > 0.35 && $_rsa_ver < 0.38 ) {
return 0;
}
## this version works, but we must not call
## use_pkcs1_padding
if ( $_rsa_ver == 0.35 ) {
for my $algo ( 'RS256', 'RS384', 'RS512' ) {
delete $RSA_ALGO_MAP->{$algo}->[1];
}
}
## apart from above versions >= 0.33 work
return 1;
}
sub _build__version_supports_pss {
my $self = shift;
my $_rsa_ver = $self->numify_version($Crypt::OpenSSL::RSA::VERSION);
return ( $_rsa_ver >= 0.36 ) ? 1 : 0;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Crypt::JWS::OpenSSL::Algorithm::RSA - Sign and verify tokens using RSA algorithms
=head1 VERSION
version 0.005
=head1 SYNOPSIS
use Crypt::JWS::OpenSSL::Algorithm::RSA;
my $jws = Crypt::JWS::OpenSSL::Algorithm::RSA->new;
my $token = $jws->sign(
algorithm => 'RS256',
key => $rsa_private_key_pem_content,
message => join('.', $base64urlEncodedHeader, $base64urlEncodedClaims),
);
my $is_verified = $jws->verify(
algorithm => 'RS256',
key => $rsa_public_key_pem_content,
message => join('.', $base64urlEncodedHeader, $base64urlEncodedClaims),
signature => $base64urlDecodedSignature,
);
=head1 DESCRIPTION
This module uses L<Crypt::OpenSSL::RSA> to sign and verify JWTs using
RSA digital signatures.
It is used within L<Crypt::JWS::OpenSSL> but it can be used directly
if you handle the Base64 url encoding and decoding elswhere.
=head1 METHODS
=head2 sign
my $token = $jwt->sign(
algorithm => $algo,
key => $key,
message => $message
);
The method accepts an even numbered list or a hash reference. It returns a signature.
=over
=item C<algorithm>
The name of the algorithm supported by the C<key> parameter.
=item C<key>
The pem encoded RSA private key or a hash reference containing a JWK.
It is more efficient to convert a JWK to a pem encoded key and use that
for signing.
See L<Crypt::JWS::OpenSSL::Util::JWK>
=item C<message>
The Base64 url encoded JSON header and the Base64 url encoded JSON claims joined together with a '.' ( dot ).
=back
The final token is produced by Base64 encoding the signature returned by this method and adding it
to the C<message> seperated by a '.' ( dot );
This method returns a raw signature.
=head2 verify
my $is_verified = $jwt->verify(
( run in 1.230 second using v1.01-cache-2.11-cpan-8dfa8b56332 )