Crypt-JWT

 view release on metacpan or  search on metacpan

lib/Crypt/JWT.pm  view on Meta::CPAN

use strict;
use warnings;

our $VERSION = '0.038';

use Exporter 'import';
our %EXPORT_TAGS = ( all => [qw(decode_jwt encode_jwt)] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our @EXPORT = qw();

use Carp;
use Crypt::Misc qw(decode_b64u encode_b64u slow_eq);
use JSON qw(decode_json encode_json);
use Crypt::PK::RSA;
use Crypt::PK::ECC;
use Crypt::PK::Ed25519;
use Crypt::PK::X25519;
use Crypt::PRNG qw(random_bytes);
use Crypt::KeyWrap ':all';
use Crypt::AuthEnc::GCM qw(gcm_encrypt_authenticate gcm_decrypt_verify);
use Crypt::Mac::HMAC qw(hmac);
use Compress::Raw::Zlib;
use Scalar::Util qw(looks_like_number);

# DoS guards on decode
our $MAX_PBES2_ITER     = 3_000_000;         # max accepted PBES2 'p2c' (iteration count)
our $MAX_INFLATED_SIZE  = 10 * 1024 * 1024;  # max accepted size of payload after 'zip' inflation

# Key-strength minimums. Values lower than the RFC 7518 strict requirement
# but high enough that the cryptographic security argument holds (see
# SECURITY CONSIDERATIONS in the POD). Tunable at startup if a deployer has
# a stronger or weaker policy.
our $MIN_HMAC_KEY_LEN   = 4;                 # minimum HMAC key length (bytes) for HS256/HS384/HS512
our $MIN_RSA_BITS       = 2048;              # minimum RSA modulus size (bits) for RS*/PS*/RSA-OAEP*/RSA1_5

# Compact-serialization token shape; precompiled to avoid rebuilding per decode_jwt() call.
my $TOKEN_RE_STRICT  = qr/^([a-zA-Z0-9_-]+)=*\.([a-zA-Z0-9_-]*)=*\.([a-zA-Z0-9_-]*)=*(?:\.([a-zA-Z0-9_-]+)=*\.([a-zA-Z0-9_-]+)=*)?$/;
my $TOKEN_RE_PADDING = qr/^([a-zA-Z0-9_-]+=*)\.([a-zA-Z0-9_-]*=*)\.([a-zA-Z0-9_-]*=*)(?:\.([a-zA-Z0-9_-]+=*)\.([a-zA-Z0-9_-]+=*))?$/;

# JWS: https://tools.ietf.org/html/rfc7515
# JWE: https://tools.ietf.org/html/rfc7516
# JWK: https://tools.ietf.org/html/rfc7517
# JWA: https://tools.ietf.org/html/rfc7518
# JWT: https://tools.ietf.org/html/rfc7519
# X25519/Ed25519 https://tools.ietf.org/html/rfc8037

sub _prepare_rsa_key {
  my ($key) = @_;
  croak "JWT: undefined RSA key" unless defined $key;
  croak "JWT: invalid RSA key (cannot be scalar)" unless ref $key;
  # we need Crypt::PK::RSA object
  my $pk;
  if    (ref($key) eq 'Crypt::PK::RSA')                 { $pk = $key }
  elsif (ref($key) eq 'HASH' || ref($key) eq 'SCALAR')  { $pk = Crypt::PK::RSA->new($key) }
  elsif (ref($key) eq 'ARRAY')                          { $pk = Crypt::PK::RSA->new(@$key) }
  else {
    # handle also: Crypt::OpenSSL::RSA, Crypt::X509, Crypt::OpenSSL::X509
    my $str;
    if (ref($key) eq 'Crypt::OpenSSL::RSA') {
      # https://metacpan.org/pod/Crypt::OpenSSL::RSA
      $str = $key->is_private ? $key->get_private_key_string : $key->get_public_key_string;
    }
    elsif (ref($key) =~ /^Crypt::(X509|OpenSSL::X509)$/) {
      # https://metacpan.org/pod/Crypt::X509
      # https://metacpan.org/pod/Crypt::OpenSSL::X509
      $str = $key->pubkey;
    }
    $pk = Crypt::PK::RSA->new(\$str) if defined $str && !ref($str);
  }
  croak "JWT: invalid RSA key" unless $pk;
  # RFC 7518 sec 3.3: "A key of size 2048 bits or larger MUST be used".
  # Check via Crypt::PK::RSA->size which returns the modulus size in bytes.
  my $bits = $pk->size * 8;
  croak "JWT: RSA modulus too small ($bits bits, minimum $MIN_RSA_BITS)" if $bits < $MIN_RSA_BITS;
  return $pk;
}

sub _prepare_ecc_key {
  my ($key) = @_;
  croak "JWT: undefined ECC key" unless defined $key;
  croak "JWT: invalid ECC key (cannot be scalar)" unless ref $key;
  # we need Crypt::PK::ECC object
  return $key                       if ref($key) eq 'Crypt::PK::ECC';
  return Crypt::PK::ECC->new($key)  if ref($key) eq 'HASH' || ref($key) eq 'SCALAR';
  return Crypt::PK::ECC->new(@$key) if ref($key) eq 'ARRAY';
  croak "JWT: invalid ECC key";
}

sub _prepare_ed25519_key {
  my ($key) = @_;
  croak "JWT: undefined Ed25519 key" unless defined $key;
  croak "JWT: invalid Ed25519 key (cannot be scalar)" unless ref $key;
  # we need Crypt::PK::Ed25519 object
  return $key                           if ref($key) eq 'Crypt::PK::Ed25519';
  return Crypt::PK::Ed25519->new($key)  if ref($key) eq 'HASH' || ref($key) eq 'SCALAR';
  return Crypt::PK::Ed25519->new(@$key) if ref($key) eq 'ARRAY';
  croak "JWT: invalid Ed25519 key";
}

sub _prepare_ecdh_key {
  my ($key) = @_;
  croak "JWT: undefined ECDH key" unless defined $key;
  croak "JWT: invalid ECDH key (cannot be scalar)" unless ref $key;

  # we need Crypt::PK::X25519 or Crypt::PK::ECC object
  return $key if ref($key) =~ /^Crypt::PK::(ECC|X25519)$/;

  if (ref($key) eq 'HASH' || ref($key) eq 'SCALAR') {
    #HACK: this is ugly
    my $rv = eval { Crypt::PK::ECC->new($key) } || eval { Crypt::PK::X25519->new($key) };
    return $rv if defined $rv;
  }
  if (ref($key) eq 'ARRAY') {
    #HACK: this is ugly
    my $rv = eval { Crypt::PK::ECC->new(@$key) } || eval { Crypt::PK::X25519->new(@$key) };
    return $rv if defined $rv;
  }
  croak "JWT: invalid ECDH key";
}

sub _prepare_oct_key {

lib/Crypt/JWT.pm  view on Meta::CPAN

 dir                 string (raw octets) or perl HASH ref with JWK, kty=>'oct', length depends on 'enc' algorithm
 A128KW              string (raw octets) 16 bytes (or perl HASH ref with JWK, kty=>'oct')
 A192KW              string (raw octets) 24 bytes (or perl HASH ref with JWK, kty=>'oct')
 A256KW              string (raw octets) 32 bytes (or perl HASH ref with JWK, kty=>'oct')
 A128GCMKW           string (raw octets) 16 bytes (or perl HASH ref with JWK, kty=>'oct')
 A192GCMKW           string (raw octets) 24 bytes (or perl HASH ref with JWK, kty=>'oct')
 A256GCMKW           string (raw octets) 32 bytes (or perl HASH ref with JWK, kty=>'oct')
 PBES2-HS256+A128KW  string (raw octets) of any length (or perl HASH ref with JWK, kty=>'oct')
 PBES2-HS384+A192KW  string (raw octets) of any length (or perl HASH ref with JWK, kty=>'oct')
 PBES2-HS512+A256KW  string (raw octets) of any length (or perl HASH ref with JWK, kty=>'oct')
 RSA-OAEP            private RSA key, perl HASH ref with JWK key structure,
                     a reference to SCALAR string with PEM or DER or JSON/JWK data,
                     an instance of Crypt::PK::RSA or Crypt::OpenSSL::RSA
 RSA-OAEP-256        private RSA key, see RSA-OAEP
 RSA1_5              private RSA key, see RSA-OAEP
 ECDH-ES             private ECC or X25519 key, perl HASH ref with JWK key structure,
                     a reference to SCALAR string with PEM or DER or JSON/JWK data,
                     an instance of Crypt::PK::ECC
 ECDH-ES+A128KW      private ECC or X25519 key, see ECDH-ES
 ECDH-ES+A192KW      private ECC or X25519 key, see ECDH-ES
 ECDH-ES+A256KW      private ECC or X25519 key, see ECDH-ES

Example using the key from C<jwk> token header:

 my $data = decode_jwt(token=>$t, key_from_jwk_header=>1);
 my ($header, $data) = decode_jwt(token=>$t, decode_header=>1, key_from_jwk_header=>1);

Examples with raw octet keys:

 #string
 my $data = decode_jwt(token=>$t, key=>'secretkey');
 #binary key
 my $data = decode_jwt(token=>$t, key=>pack("H*", "788A6E38F36B7596EF6A669E94"));
 #perl HASH ref with JWK structure (key type 'oct')
 my $data = decode_jwt(token=>$t, key=>{kty=>'oct', k=>"GawgguFyGrWKav7AX4VKUg"});

Examples with RSA keys:

 my $pem_key_string = <<'EOF';
 -----BEGIN PRIVATE KEY-----
 MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCoVm/Sl5r+Ofky
 jioRSZK26GW6WyjyfWKddsSi13/NOtCn0rRErSF/u3QrgGMpWFqKohqbi1VVC+SZ
 ...
 8c1vm2YFafgdkSk9Qd1oU2Fv1aOQy4VovOFzJ3CcR+2r7cbRfcpLGnintHtp9yek
 02p+d5g4OChfFNDhDtnIqjvY
 -----END PRIVATE KEY-----
 EOF

 my $jwk_key_json_string = '{"kty":"RSA","n":"0vx7agoebG...L6tSoc_BJECP","e":"AQAB"}';

 #a reference to SCALAR string with PEM or DER or JSON/JWK data,
 my $data = decode_jwt(token=>$t, key=>\$pem_key_string);
 my $data = decode_jwt(token=>$t, key=>\$der_key_string);
 my $data = decode_jwt(token=>$t, key=>\$jwk_key_json_string);

 #instance of Crypt::PK::RSA
 my $data = decode_jwt(token=>$t, key=>Crypt::PK::RSA->new('keyfile.pem'));
 my $data = decode_jwt(token=>$t, key=>Crypt::PK::RSA->new(\$pem_key_string));

 #instance of Crypt::OpenSSL::RSA
 my $data = decode_jwt(token=>$t, key=>Crypt::OpenSSL::RSA->new_private_key($pem_key_string));

 #instance of Crypt::X509 (public key only)
 my $data = decode_jwt(token=>$t, key=>Crypt::X509->new(cert=>$cert));

 #instance of Crypt::OpenSSL::X509 (public key only)
 my $data = decode_jwt(token=>$t, key=>Crypt::OpenSSL::X509->new_from_file('cert.pem'));
 my $data = decode_jwt(token=>$t, key=>Crypt::OpenSSL::X509->new_from_string($cert));

 #perl HASH ref with JWK structure (key type 'RSA')
 my $rsa_priv = {
   kty => "RSA",
   n   => "0vx7agoebGcQSuuPiLJXZpt...eZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
   e   => "AQAB",
   d   => "X4cTteJY_gn4FYPsXB8rdXi...FLN5EEaG6RoVH-HLKD9Mdx5ooGURknhnrRwUkC7h5fJLMWbFAKLWY2v7B6NqSzUvx0_YSf",
   p   => "83i-7IvMGXoMXCskv73TKr8...Z27zvoj6pbUQyLPBQxtPnwD20-60eTmD2ujMt5PoMrm8RmNhVWtjjMmMjOpSicFHjXOuVI",
   q   => "3dfOR9cuYq-0S-mkFLzgItg...q3hWeMuG0ouqnb3obLyuqjVZQ1dIrdgTnCdYzBcOW5r37AFXjift_NGiovonzhKpoVVS78",
   dp  => "G4sPXkc6Ya9y8oJW9_ILj4...zi_H7TkS8x5SdX3oE0oiYwxIiemTAu0UOa5pgFGyJ4c8t2VF40XRugKTP8akhFo5tA77Qe",
   dq  => "s9lAH9fggBsoFR8Oac2R_E...T2kGOhvIllTE1efA6huUvMfBcpn8lqW6vzzYY5SSF7pMd_agI3G8IbpBUb0JiraRNUfLhc",
   qi  => "GyM_p6JrXySiz1toFgKbWV...4ypu9bMWx3QJBfm0FoYzUIZEVEcOqwmRN81oDAaaBk0KWGDjJHDdDmFW3AN7I-pux_mHZG",
 };
 my $data = decode_jwt(token=>$t, key=>$rsa_priv);

Examples with ECC keys:

 my $pem_key_string = <<'EOF';
 -----BEGIN EC PRIVATE KEY-----
 MHcCAQEEIBG1c3z52T8XwMsahGVdOZWgKCQJfv+l7djuJjgetdbDoAoGCCqGSM49
 AwEHoUQDQgAEoBUyo8CQAFPeYPvv78ylh5MwFZjTCLQeb042TjiMJxG+9DLFmRSM
 lBQ9T/RsLLc+PmpB1+7yPAR+oR5gZn3kJQ==
 -----END EC PRIVATE KEY-----
 EOF

 my $jwk_key_json_string = '{"kty":"EC","crv":"P-256","x":"MKB..7D4","y":"4Et..FyM"}';

 #a reference to SCALAR string with PEM or DER or JSON/JWK data,
 my $data = decode_jwt(token=>$t, key=>\$pem_key_string);
 my $data = decode_jwt(token=>$t, key=>\$der_key_string);
 my $data = decode_jwt(token=>$t, key=>\$jwk_key_json_string);

 #instance of Crypt::PK::ECC
 my $data = decode_jwt(token=>$t, key=>Crypt::PK::ECC->new('keyfile.pem'));
 my $data = decode_jwt(token=>$t, key=>Crypt::PK::ECC->new(\$pem_key_string));

 #perl HASH ref with JWK structure (key type 'EC')
 my $ecc_priv = {
   kty => "EC",
   crv => "P-256",
   x   => "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
   y   => "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
   d   => "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE",
 };
 my $data = decode_jwt(token=>$t, key=>$ecc_priv);

=item keypass

Optional. When the C<key> parameter is an encrypted private RSA or ECC
key (PEM/DER), this parameter holds the password used to decrypt it.

=item kid_keys



( run in 0.698 second using v1.01-cache-2.11-cpan-c966e8aa7e8 )