File Coverage

blib/lib/Crypt/Age/Stanza/X25519.pm
Criterion Covered Total %
statement 36 36 100.0
branch n/a
condition n/a
subroutine 8 8 100.0
pod 2 2 100.0
total 46 46 100.0


line stmt bran cond sub pod time code
1             package Crypt::Age::Stanza::X25519;
2             our $VERSION = '0.001';
3             our $AUTHORITY = 'cpan:GETTY';
4             # ABSTRACT: X25519 recipient stanza for age encryption
5              
6 4     4   25 use Moo;
  4         8  
  4         28  
7 4     4   2222 use Carp qw(croak);
  4         11  
  4         310  
8 4     4   424 use Crypt::Age::Keys;
  4         9  
  4         252  
9 4     4   36 use Crypt::Age::Primitives;
  4         8  
  4         161  
10 4     4   20 use Crypt::Age::Stanza;
  4         8  
  4         148  
11 4     4   19 use namespace::clean;
  4         7  
  4         36  
12              
13              
14             extends 'Crypt::Age::Stanza';
15              
16             has '+type' => (
17             default => 'X25519',
18             );
19              
20             has ephemeral_public => (
21             is => 'ro',
22             );
23              
24              
25             sub wrap {
26 15     15 1 63 my ($class, $file_key, $recipient_public_key) = @_;
27              
28             # Decode recipient public key (Bech32 -> raw bytes)
29 15         104 my $recipient_public = Crypt::Age::Keys->decode_public_key($recipient_public_key);
30              
31             # Generate ephemeral keypair
32 15         85 my ($ephemeral_public, $ephemeral_private) =
33             Crypt::Age::Primitives->x25519_generate_keypair;
34              
35             # Compute shared secret
36 15         100 my $shared_secret = Crypt::Age::Primitives->x25519_shared_secret(
37             $ephemeral_private,
38             $recipient_public
39             );
40              
41             # Derive wrap key
42 15         112 my $wrap_key = Crypt::Age::Primitives->derive_wrap_key(
43             $shared_secret,
44             $ephemeral_public,
45             $recipient_public
46             );
47              
48             # Wrap file key
49 15         70 my $wrapped_key = Crypt::Age::Primitives->wrap_file_key($wrap_key, $file_key);
50              
51             # Create stanza
52 15         91 return $class->new(
53             args => [Crypt::Age::Stanza::encode_base64_no_padding($ephemeral_public)],
54             body => $wrapped_key,
55             ephemeral_public => $ephemeral_public,
56             );
57             }
58              
59              
60             sub unwrap {
61 13     13 1 34 my ($self, $identity_secret_key) = @_;
62              
63             # Decode identity secret key (Bech32 -> raw bytes)
64 13         74 my $identity_private = Crypt::Age::Keys->decode_secret_key($identity_secret_key);
65              
66             # Get recipient's public key from identity
67 13         71 my $pk = Crypt::PK::X25519->new;
68 13         844 $pk->import_key_raw($identity_private, 'private');
69 13         28278 my $recipient_public = $pk->export_key_raw('public');
70              
71             # Decode ephemeral public key from stanza args
72 13         95 my $ephemeral_public = Crypt::Age::Stanza::decode_base64_no_padding($self->args->[0]);
73              
74             # Compute shared secret
75 13         70 my $shared_secret = Crypt::Age::Primitives->x25519_shared_secret(
76             $identity_private,
77             $ephemeral_public
78             );
79              
80             # Derive wrap key
81 13         97 my $wrap_key = Crypt::Age::Primitives->derive_wrap_key(
82             $shared_secret,
83             $ephemeral_public,
84             $recipient_public
85             );
86              
87             # Unwrap file key
88 13         36 my $file_key = eval {
89 13         81 Crypt::Age::Primitives->unwrap_file_key($wrap_key, $self->body);
90             };
91              
92 13         81 return $file_key; # Returns undef if unwrap failed
93             }
94              
95              
96              
97             1;
98              
99             __END__