File Coverage

blib/lib/Acme/JWT.pm
Criterion Covered Total %
statement 99 102 97.0
branch 21 26 80.7
condition 2 3 66.6
subroutine 19 20 95.0
pod 0 6 0.0
total 141 157 89.8


line stmt bran cond sub pod time code
1             package Acme::JWT;
2 2     2   892 use strict;
  2         3  
  2         82  
3 2     2   11 use warnings;
  2         3  
  2         95  
4             our $VERSION = '0.04';
5              
6 2     2   2376 use JSON qw/decode_json encode_json/;
  2         36973  
  2         10  
7 2     2   2395 use MIME::Base64 qw/encode_base64url decode_base64url/;
  2         1539  
  2         168  
8 2     2   1641 use Try::Tiny;
  2         3034  
  2         125  
9 2     2   266767 use Digest::SHA qw/hmac_sha256 hmac_sha384 hmac_sha512/;
  2         9748  
  2         236  
10 2     2   3073 use Crypt::OpenSSL::RSA;
  2         18821  
  2         195  
11              
12             our $has_sha2;
13             BEGIN {
14 2     2   5 $has_sha2 = 0;
15 2 50       17 if (UNIVERSAL::can('Crypt::OpenSSL::RSA', 'use_sha512_hash')) {
16 2         3824 $has_sha2 = 1;
17             }
18             }
19              
20             sub encode {
21 7     7 0 43751 my $self = shift;
22 7         20 my ($payload, $key, $algorithm) = @_;
23 7 100       26 unless (defined($algorithm)) {
24 2         4 $algorithm = 'HS256';
25             }
26 7 100       21 unless ($algorithm) {
27 1         2 $algorithm = 'none';
28             }
29 7         15 my $segments = [];
30 7         29 my $header = {
31             typ => 'JWT',
32             alg => $algorithm,
33             };
34 7         70 push(@$segments, encode_base64url(encode_json($header)));
35 7         148 push(@$segments, encode_base64url(encode_json($payload)));
36 7         81 my $signing_input = join('.', @$segments);
37 7 100       25 unless ($algorithm eq 'none') {
38 6         25 my $signature = $self->sign($algorithm, $key, $signing_input);
39 5         17 push(@$segments, encode_base64url($signature));
40             } else {
41 1         4 push(@$segments, '');
42             }
43 6         73 return join('.', @$segments);
44             }
45              
46             sub decode {
47 7     7 0 3772 my $self = shift;
48 7         13 my ($jwt, $key, $verify) = @_;
49 7 100       18 unless (defined($verify)) {
50 5         8 $verify = 1;
51             }
52 7         29 my $segments = [split(/\./, $jwt)];
53 7 50 66     41 die 'Not enough or to many segments' unless (@$segments == 2 or @$segments == 3);
54 7         15 my ($header_segment, $payload_segment, $crypt_segment) = @$segments;
55 7         14 my $signing_input = join('.', $header_segment, $payload_segment);
56 7         11 my $header;
57             my $payload;
58 0         0 my $signature;
59             try {
60 7     7   244 $header = decode_json(decode_base64url($header_segment));
61 7         99 $payload = decode_json(decode_base64url($payload_segment));
62 7 100       77 $signature = decode_base64url($crypt_segment) if ($verify);
63             } catch {
64 0     0   0 warn $_;
65 7         84 };
66 7 100       142 if ($verify) {
67 5         11 my $algo = $header->{alg};
68             my $hmac = sub {
69 3     3   14 my ($algo, $key, $signing_input, $signature) = @_;
70 3         8 $signature eq $self->sign_hmac($algo, $key, $signing_input);
71 5         21 };
72             my $verify_method = sub {
73 2     2   4 my ($algo, $key, $signing_input, $signature) = @_;
74 2         8 $self->verify_rsa($algo, $key, $signing_input, $signature);
75 5         19 };
76 5         16 my $algorithm = {
77             HS256 => $hmac,
78             HS384 => $hmac,
79             HS512 => $hmac,
80             };
81              
82 5 50       12 if ($has_sha2) {
83 5         23 $algorithm = {
84             %$algorithm,
85             (
86             RS256 => $verify_method,
87             RS384 => $verify_method,
88             RS512 => $verify_method,
89             ),
90             };
91             }
92 5 50       20 if (exists($algorithm->{$algo})) {
93 5 100       17 unless ($algorithm->{$algo}->($algo, $key, $signing_input, $signature)) {
94 2         29 die 'Signature verifacation failed';
95             }
96             } else {
97 0         0 die 'Algorithm not supported';
98             }
99             }
100 5         22 return $payload;
101             }
102              
103             sub sign {
104 6     6 0 10 my $self = shift;
105 6         13 my ($algo, $key, $signing_input) = @_;
106             my $hmac = sub {
107 3     3   5 my ($algo, $key, $signing_input) = @_;
108 3         10 $self->sign_hmac($algo, $key, $signing_input);
109 6         35 };
110             my $rsa = sub {
111 2     2   4 my ($algo, $key, $signing_input) = @_;
112 2         9 $self->sign_rsa($algo, $key, $signing_input);
113 6         21 };
114 6         26 my $algorithm = {
115             HS256 => $hmac,
116             HS384 => $hmac,
117             HS512 => $hmac,
118             };
119 6 50       18 if ($has_sha2) {
120 6         35 $algorithm = {
121             %$algorithm,
122             (
123             RS256 => $rsa,
124             RS384 => $rsa,
125             RS512 => $rsa,
126             ),
127             };
128             }
129 6 100       29 unless (exists($algorithm->{$algo})) {
130 1         13 die 'Unsupported signing method';
131             }
132 5         15 $algorithm->{$algo}->($algo, $key, $signing_input);
133             }
134              
135             sub sign_rsa {
136 2     2 0 4 my $self = shift;
137 2         5 my ($algo, $key, $msg) = @_;
138 2         12 $algo =~ s/\D+//;
139 2         96 my $private_key = Crypt::OpenSSL::RSA->new_private_key($key);
140 2         23 $private_key->can("use_sha${algo}_hash")->($private_key);
141 2         629 $private_key->sign($msg);
142             }
143              
144             sub verify_rsa {
145 2     2 0 4 my $self = shift;
146 2         4 my ($algo, $key, $signing_input, $signature) = @_;
147 2         7 $algo =~ s/\D+//;
148 2         48 my $public_key = Crypt::OpenSSL::RSA->new_public_key($key);
149 2         843 $public_key->can("use_sha${algo}_hash")->($public_key);
150 2         109 $public_key->verify($signing_input, $signature);
151             }
152              
153             sub sign_hmac {
154 6     6 0 9 my $self = shift;
155 6         10 my ($algo, $key, $msg) = @_;
156 6         22 $algo =~ s/\D+//;
157 6         36 my $method = $self->can("hmac_sha$algo");
158 6         140 $method->($msg, $key);
159             }
160              
161             1;
162             __END__