Crypt-JWT

 view release on metacpan or  search on metacpan

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

  }
  croak "pbes2_key_unwrap: invalid alg '$alg'";
  return undef;
}

# RSA KW
# https://tools.ietf.org/html/rfc7518#section-4.2
# https://tools.ietf.org/html/rfc7518#section-4.3

sub rsa_key_wrap {
  my ($kek_public, $pt_data, $alg) = @_;
  croak "rsa_key_wrap: no Crypt::PK::RSA" unless ref $kek_public eq 'Crypt::PK::RSA';
  my ($padding, $hash_name);
  if    ($alg eq 'RSA-OAEP')     { ($padding, $hash_name) = ('oaep', 'SHA1') }
  elsif ($alg eq 'RSA-OAEP-256') { ($padding, $hash_name) = ('oaep', 'SHA256') }
  elsif ($alg eq 'RSA1_5')       { $padding = 'v1.5' }
  croak "rsa_key_wrap: invalid algorithm '$alg'" unless $padding;
  my $ct_data = $kek_public->encrypt($pt_data, $padding, $hash_name);
  return $ct_data;
}

sub rsa_key_unwrap {
  my ($kek_private, $ct_data, $alg) = @_;
  croak "rsa_key_unwrap: no Crypt::PK::RSA" unless ref $kek_private eq 'Crypt::PK::RSA';
  croak "rsa_key_unwrap: no private key" unless $kek_private->is_private;
  my ($padding, $hash_name);
  if    ($alg eq 'RSA-OAEP')     { ($padding, $hash_name) = ('oaep', 'SHA1') }
  elsif ($alg eq 'RSA-OAEP-256') { ($padding, $hash_name) = ('oaep', 'SHA256') }
  elsif ($alg eq 'RSA1_5')       { $padding = 'v1.5' }
  croak "rsa_key_unwrap: invalid algorithm '$alg'" unless $padding;
  my $pt_data = $kek_private->decrypt($ct_data, $padding, $hash_name);
  return $pt_data;
}

# ConcatKDF - http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf
# ECDH KW   - https://tools.ietf.org/html/rfc7518#section-4.6

sub _concat_kdf {
  my ($hash_name, $key_size, $shared_secret, $algorithm, $apu, $apv) = @_;
  $apu = _decode_ecdh_info('apu', $apu);
  $apv = _decode_ecdh_info('apv', $apv);
  my $hsize = Crypt::Digest->hashsize($hash_name);
  my $count = int($key_size / $hsize);
  $count++ if ($key_size % $hsize) > 0;
  my $data = '';
  for my $i (1..$count) {
    $data .= digest_data($hash_name, pack("N", $i) .
                                     $shared_secret .
                                     pack("N", length($algorithm)) . $algorithm .
                                     pack("N", length($apu)) . $apu .
                                     pack("N", length($apv)) . $apv .
                                     pack("N", 8 *$key_size));
  }
  return substr($data, 0, $key_size);
}

sub ecdh_key_wrap {
  my ($kek_public, $enc, $apu, $apv) = @_;
  croak "ecdh_key_wrap: no Crypt::PK::ECC" unless ref $kek_public eq 'Crypt::PK::ECC';
  my $encryption_key_size = 256;
  if ($enc =~ /^A(128|192|256)CBC-HS/) {
    $encryption_key_size = $1*2;
  }
  if ($enc =~ /^A(128|192|256)GCM/) {
    $encryption_key_size = $1;
  }
  my $ephemeral = Crypt::PK::ECC->new()->generate_key($kek_public->curve2hash);
  my $shared_secret = $ephemeral->shared_secret($kek_public);
  my $ct_data = _concat_kdf('SHA256', $encryption_key_size/8, $shared_secret, $enc, $apu, $apv);
  return ($ct_data, $ephemeral->export_key_jwk('public'));
}

sub ecdh_key_unwrap {
  my ($kek_private, $enc, $epk, $apu, $apv) = @_;
  croak "ecdh_key_unwrap: no Crypt::PK::ECC" unless ref $kek_private eq 'Crypt::PK::ECC';
  croak "ecdh_key_unwrap: no private key" unless $kek_private->is_private;
  my $encryption_key_size = 256;
  if ($enc =~ /^A(128|192|256)CBC-HS/) {
    $encryption_key_size = $1*2;
  }
  if ($enc =~ /^A(128|192|256)GCM/) {
    $encryption_key_size = $1;
  }
  my $ephemeral = ref($epk) eq 'Crypt::PK::ECC' ? $epk : Crypt::PK::ECC->new(ref $epk ? $epk : \$epk);
  my $shared_secret = $kek_private->shared_secret($ephemeral);
  my $pt_data = _concat_kdf('SHA256', $encryption_key_size/8, $shared_secret, $enc, $apu, $apv);
  return $pt_data;
}

sub ecdhaes_key_wrap {
  my ($kek_public, $pt_data, $alg, $apu, $apv) = @_;
  croak "ecdhaes_key_wrap: no Crypt::PK::(ECC|X25519)" unless ref($kek_public) =~ /^Crypt::PK::(ECC|X25519)$/;
  my $encryption_key_size = 256;
  if ($alg =~ /^ECDH-ES\+A(128|192|256)KW$/) {
    $encryption_key_size = $1;
  }
  my $ephemeral;
  if (ref($kek_public) eq 'Crypt::PK::ECC') {
    $ephemeral = Crypt::PK::ECC->new->generate_key($kek_public->curve2hash);
  }
  else {
    $ephemeral = Crypt::PK::X25519->new->generate_key();
  }
  my $shared_secret = $ephemeral->shared_secret($kek_public);
  my $kek = _concat_kdf('SHA256', $encryption_key_size/8, $shared_secret, $alg, $apu, $apv);
  # RFC 7518 sec 4.6 wraps via "AES Key Wrap algorithm specified in RFC 3394"
  return (aes_key_wrap($kek, $pt_data, 'AES', 0), $ephemeral->export_key_jwk('public'));
}

sub ecdhaes_key_unwrap {
  my ($kek_private, $ct_data, $alg, $epk, $apu, $apv) = @_;
  croak "ecdhaes_key_unwrap: no Crypt::PK::(ECC|X25519)" unless ref($kek_private) =~ /^Crypt::PK::(ECC|X25519)$/;
  croak "ecdhaes_key_unwrap: no private key" unless $kek_private->is_private;
  my $encryption_key_size = 256;
  if ($alg =~ /^ECDH-ES\+A(128|192|256)KW$/) {
    $encryption_key_size = $1;
  }
  my $ephemeral;
  if (ref($kek_private) eq 'Crypt::PK::ECC') {
    $ephemeral = ref($epk) eq 'Crypt::PK::ECC' ? $epk : Crypt::PK::ECC->new(ref $epk ? $epk : \$epk);
  }
  else {
    $ephemeral = ref($epk) eq 'Crypt::PK::X25519' ? $epk : Crypt::PK::X25519->new(ref $epk ? $epk : \$epk);
  }
  my $shared_secret = $kek_private->shared_secret($ephemeral);
  my $kek = _concat_kdf('SHA256', $encryption_key_size/8, $shared_secret, $alg, $apu, $apv);
  my $pt_data = aes_key_unwrap($kek, $ct_data, 'AES', 0);
  return $pt_data;
}

1;

=pod

=head1 NAME

Crypt::KeyWrap - Key management/wrapping algorithms defined in RFC7518 (JWA)



( run in 0.558 second using v1.01-cache-2.11-cpan-e1769b4cff6 )