File Coverage

blib/lib/JSON/WebEncryption.pm
Criterion Covered Total %
statement 116 121 95.8
branch 6 12 50.0
condition 13 20 65.0
subroutine 18 20 90.0
pod 0 7 0.0
total 153 180 85.0


line stmt bran cond sub pod time code
1             package JSON::WebEncryption;
2              
3 1     1   36400 use strict;
  1         3  
  1         40  
4              
5 1     1   854 use parent 'Exporter';
  1         410  
  1         5  
6              
7             our $VERSION = '0.05';
8              
9 1     1   63 use Carp qw(croak);
  1         6  
  1         65  
10 1     1   1021 use Crypt::CBC;
  1         6257  
  1         50  
11 1     1   1055 use Crypt::OpenSSL::RSA;
  1         11525  
  1         60  
12 1     1   1205 use JSON qw(decode_json encode_json);
  1         18773  
  1         6  
13 1     1   2286 use Digest::SHA qw(hmac_sha256 hmac_sha512);
  1         5431  
  1         122  
14 1     1   973 use MIME::Base64 qw(encode_base64url decode_base64url);
  1         836  
  1         1551  
15              
16             our @EXPORT = qw( encode_jwe decode_jwe );
17              
18             our %allowed_alg = (
19             "dir" => [ \&_alg_dir_encode, \&_alg_dir_decode ],
20             "RSA1_5" => [ \&_alg_RSA1_5_encode, \&_alg_RSA1_5_decode ],
21             );
22              
23             our %allowed_enc = (
24             # Type keysize ivsize pading integrity func
25             "A128CBC-HS256" => ['Rijndael', '128', '128', 'PKCS#5', \&hmac_sha256], # AES 128 in CBC with SHA256 HMAC integrity check
26             "A256CBC-HS512" => ['Rijndael', '256', '128', 'PKCS#5', \&hmac_sha512], # AES 256 in CBC with SHA512 HMAC integrity check
27             "BF128BC-HS256" => ['Blowfish', '128', '64', 'PKCS#5', \&hmac_sha256], # Blowfish 128 in CBC with SHA256 HMAC integrity check
28             );
29              
30             our %crypt_padding_map = (
31             'PKCS#5' => 'standard'
32             );
33              
34             # -----------------------------------------------------------------------------
35              
36             sub new {
37 2     2 0 3568 my($caller, %arg) = @_;
38              
39 2         9 my $self = bless {}, $caller;
40              
41 2         14 $self->{alg} = $arg{alg};
42 2         7 $self->{enc} = $arg{enc};
43 2         5 $self->{key} = $arg{key};
44 2         6 $self->{private_key} = $arg{private_key};
45 2         3 $self->{public_key} = $arg{public_key};
46              
47 2         8 return $self;
48             }
49              
50             # -----------------------------------------------------------------------------
51              
52             sub encode_from_hash {
53 0     0 0 0 my ($self, $hash) = @_;
54              
55 0         0 return $self->encode(encode_json($hash));
56             }
57              
58             # -----------------------------------------------------------------------------
59              
60             sub decode_to_hash {
61 0     0 0 0 my ($self, $jwe) = @_;
62              
63 0         0 return decode_json($self->decode($jwe));
64             }
65              
66             # -----------------------------------------------------------------------------
67              
68             sub encode
69             {
70 4     4 0 15 my ($self, $plaintext, $enc, $key, $alg, $extra_headers ) = @_;
71              
72 4   66     19 $alg //= $self->{alg};
73 4   66     15 $enc //= $self->{enc};
74              
75 4         9 my $alg_params = $allowed_alg{$alg};
76 4         10 my $enc_params = $allowed_enc{$enc};
77              
78 4 50       11 croak "Unsupported alg value $alg. Possible values are ".join( ', ', (keys %allowed_alg) ) unless $alg_params;
79 4 50       8 croak "Unsupported enc value $enc. Possible values are ".join( ', ', (keys %allowed_enc) ) unless $enc_params;
80              
81 4         11 my $ivsize = $enc_params->[2] / 8; # /8 to get it in bytes
82 4         6 my $integrity_fn = $enc_params->[4];
83              
84 4         19 my $iv = Crypt::CBC->random_bytes($ivsize);
85              
86 4         4200 my $encoder = $alg_params->[0];
87              
88 4         19 my ($ciphertext, $encrypted_key) = &$encoder( $self, $enc_params, $key, $iv, $plaintext );
89              
90 4   50     29 $extra_headers //= {};
91              
92 4         21 my $header = {
93             typ => 'JWE',
94             alg => $alg,
95             enc => $enc,
96             %$extra_headers,
97             };
98              
99 4         8 my @segment;
100 4         59 push @segment, encode_base64url( encode_json($header) );
101 4         75 push @segment, encode_base64url( $encrypted_key );
102 4         40 push @segment, encode_base64url( $iv );
103 4         39 push @segment, encode_base64url( $ciphertext );
104              
105 4         42 my $to_be_signed = join('.', @segment);
106              
107 4         105 my $icheck = encode_base64url( &$integrity_fn( $to_be_signed ) );
108              
109 4         64 return $to_be_signed.".$icheck";
110             }
111              
112             # -----------------------------------------------------------------------------
113              
114             sub encode_jwe
115             {
116 2     2 0 68351 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
117 2         13 __PACKAGE__->encode(@_);
118             }
119              
120             # -----------------------------------------------------------------------------
121              
122             sub decode
123             {
124 4     4 0 176 my ($self, $jwe, $key) = @_;
125              
126 4         47 my @segment = split( /\./, $jwe );
127              
128             # Decode the header first, to see what we're dealing with
129             #
130 4         20 my $header = decode_json( decode_base64url( $segment[0] ) );
131              
132 4 50       82 croak "Cannot decode a non JWE message." if $header->{typ} ne 'JWE';
133              
134 4         10 my $alg_params = $allowed_alg{$header->{alg}};
135 4         10 my $enc_params = $allowed_enc{$header->{enc}};
136              
137 4 50       13 croak "Unsupported enc value in JWE. JWE may be decoded with env values of ".join( ', ', (keys %allowed_enc) ) unless $enc_params;
138 4 50       12 croak "Unsupported alg value in JWE. JWE may be decoded with alg values of ".join( ', ', (keys %allowed_alg) ) unless $alg_params;
139              
140 4         11 my $encrypted_key = decode_base64url( $segment[1] );
141 4         39 my $iv = decode_base64url( $segment[2] );
142 4         39 my $ciphertext = decode_base64url( $segment[3] );
143 4         30 my $icheckB64 = $segment[4];
144              
145 4         7 my $integrity_fn = $enc_params->[4];
146              
147 4         20 my $signed_section = substr( $jwe, 0, rindex($jwe, '.') );
148              
149 4 50       63 if( $icheckB64 ne encode_base64url( &$integrity_fn($signed_section) ) )
150             {
151 0         0 croak "Cannot decode JWE." ;
152             }
153              
154 4         49 my $decoder = $alg_params->[1];
155              
156 4         13 my $plaintext = &$decoder( $self, $enc_params, $key, $iv, $ciphertext, $encrypted_key );
157              
158 4         579 return $plaintext;
159             }
160              
161             # -----------------------------------------------------------------------------
162              
163             sub decode_jwe
164             {
165 2     2 0 197 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
166 2         10 __PACKAGE__->decode(@_);
167             }
168              
169             # -----------------------------------------------------------------------------
170              
171             sub _getCipher
172             {
173 8     8   17 my ($cipherType, $symetric_key, $padding, $iv, $keysize) = @_;
174 8         65 my $cipher = Crypt::CBC->new( -literal_key => 1,
175             -key => $symetric_key,
176             -keysize => $keysize,
177             -iv => $iv,
178             #-header => 'salt', # Openssl Compatible
179             -header => 'none',
180             -padding => $padding,
181             -cipher => $cipherType
182             );
183             }
184              
185              
186             # -----------------------------------------------------------------------------
187              
188             sub _alg_dir_encode
189             {
190 2     2   7 my ( $self, $enc_params, $key, $iv, $plaintext ) = @_;
191              
192 2   66     11 $key //= $self->{key};
193              
194 2         4 my $cipherType = $enc_params->[0];
195 2         7 my $keysize = $enc_params->[1] / 8; # /8 to get it in bytes
196 2         6 my $padding = $crypt_padding_map{ $enc_params->[3] };
197              
198 2         7 my $cipher = _getCipher( $cipherType, $key, $padding, $iv, $keysize );
199 2         269 my $ciphertext = $cipher->encrypt( $plaintext );
200 2         200 my $encrypted_key = '';
201              
202 2         18 return ($ciphertext, $encrypted_key);
203             }
204              
205             # -----------------------------------------------------------------------------
206              
207             sub _alg_dir_decode
208             {
209 2     2   6 my ( $self, $enc_params, $key, $iv, $ciphertext ) = @_;
210              
211 2   66     10 $key //= $self->{key};
212              
213 2         5 my $cipherType = $enc_params->[0];
214 2         5 my $keysize = $enc_params->[1] / 8; # /8 to get it in bytes
215 2         6 my $padding = $crypt_padding_map{ $enc_params->[3] };
216              
217 2         6 my $cipher = _getCipher( $cipherType, $key, $padding, $iv, $keysize );
218 2         246 return $cipher->decrypt( $ciphertext );
219             }
220              
221             # -----------------------------------------------------------------------------
222              
223             sub _alg_RSA1_5_encode
224             {
225 2     2   5 my ( $self, $enc_params, $public_key, $iv, $plaintext ) = @_;
226              
227 2   66     11 $public_key //= $self->{public_key};
228              
229 2         6 my $cipherType = $enc_params->[0];
230 2         5 my $keysize = $enc_params->[1] / 8; # /8 to get it in bytes
231 2         7 my $padding = $crypt_padding_map{ $enc_params->[3] };
232              
233             # Purely alg = RSA1_5
234 2         51 my $rsa = Crypt::OpenSSL::RSA->new_public_key( $public_key ); # Key passed in is a Public Key
235 2         810 $rsa->use_pkcs1_oaep_padding;
236              
237 2         11 my $CEK = Crypt::CBC->random_bytes( $keysize );
238 2         2078 my $encrypted_key = $rsa->encrypt( $CEK );
239              
240 2         9 my $cipher = _getCipher( $cipherType, $CEK, $padding, $iv, $keysize );
241 2         399 my $ciphertext = $cipher->encrypt( $plaintext );
242              
243 2         291 return ($ciphertext, $encrypted_key);
244             }
245              
246             # -----------------------------------------------------------------------------
247              
248             sub _alg_RSA1_5_decode
249             {
250 2     2   7 my ( $self, $enc_params, $private_key, $iv, $ciphertext, $encrypted_key ) = @_;
251              
252 2   66     9 $private_key //= $self->{private_key};
253              
254 2         5 my $cipherType = $enc_params->[0];
255 2         6 my $keysize = $enc_params->[1] / 8; # /8 to get it in bytes
256 2         5 my $padding = $crypt_padding_map{ $enc_params->[3] };
257              
258             # Decrypt the encryption key using the Private Key
259 2         107 my $rsa = Crypt::OpenSSL::RSA->new_private_key( $private_key ); # Key passed in is a Private Key
260 2         8 $rsa->use_pkcs1_oaep_padding;
261 2         1675 my $CEK = $rsa->decrypt( $encrypted_key );
262              
263             # Use the encryption key to decrypt the message
264 2         8 my $cipher = _getCipher( $cipherType, $CEK, $padding, $iv, $keysize );
265              
266 2         263 return $cipher->decrypt( $ciphertext );
267             }
268              
269             # -----------------------------------------------------------------------------
270             1;
271              
272             __END__