File Coverage

blib/lib/Net/OpenID/Connect/IDToken.pm
Criterion Covered Total %
statement 67 67 100.0
branch 20 20 100.0
condition 7 8 87.5
subroutine 17 17 100.0
pod 0 4 0.0
total 111 116 95.6


line stmt bran cond sub pod time code
1             package Net::OpenID::Connect::IDToken;
2 4     4   108822 use 5.008005;
  4         16  
  4         171  
3 4     4   27 use strict;
  4         8  
  4         137  
4 4     4   22 use warnings;
  4         23  
  4         268  
5              
6             our $VERSION = "0.02";
7              
8 4     4   4120 use parent qw/Exporter/;
  4         1442  
  4         21  
9              
10 4     4   10208 use MIME::Base64 qw/encode_base64url/;
  4         3414  
  4         440  
11 4     4   13267 use Digest::SHA;
  4         22822  
  4         256  
12 4     4   4762 use JSON::WebToken qw//;
  4         70448  
  4         95  
13              
14 4     4   2566 use Net::OpenID::Connect::IDToken::Exception;
  4         8  
  4         105  
15 4     4   2166 use Net::OpenID::Connect::IDToken::Constants;
  4         55  
  4         3326  
16              
17             our @EXPORT = qw/encode_id_token decode_id_token/;
18              
19              
20             our $JWT_ENCODE = sub {
21             my ($claims, $key, $alg, $extra_headers) = @_;
22             JSON::WebToken->encode($claims, $key, $alg, $extra_headers);
23             };
24              
25             our $JWT_DECODE = sub {
26             my ($id_token, $key, $to_be_verified) = @_;
27             JSON::WebToken->decode($id_token, $key, $to_be_verified);
28             };
29              
30             sub encode_id_token {
31 1     1 0 131 __PACKAGE__->encode(@_);
32             }
33              
34             sub decode_id_token {
35 4     4 0 6745 __PACKAGE__->decode(@_);
36             }
37              
38             sub encode {
39 9     9 0 22698 my ($class, $claims, $key, $alg, $opts, $extra_headers) = @_;
40 9   100     45 $alg ||= "HS256";
41 9         20 my $id_token_claims = +{};
42              
43 9 100       41 if ( my $token = $opts->{token} ) {
44 4         19 $id_token_claims->{a_hash} = $class->_generate_token_hash($token, $alg);
45             }
46 9 100       160 if ( my $code = $opts->{code} ) {
47 4         15 $id_token_claims->{c_hash} = $class->_generate_token_hash($code, $alg);
48             }
49              
50 9         189 return $JWT_ENCODE->(+{ %$claims, %$id_token_claims }, $key, $alg, $extra_headers);
51             }
52              
53             sub _generate_token_hash {
54 16     16   3607 my ($class, $token, $alg) = @_;
55 16         47 my $bits = substr($alg, 2); # 'HS256' -> '256'
56              
57 16         92 my $sha = Digest::SHA->new($bits);
58 16 100       442 unless ( $sha ) {
59 1         25 Net::OpenID::Connect::IDToken::Exception->throw(
60             code => ERROR_IDTOKEN_INVALID_ALGORITHM,
61             message => sprintf("%s is not supported as SHA-xxx algorithm.", $bits),
62             );
63             }
64 15         64 $sha->add($token);
65              
66 15         209 return encode_base64url(substr($sha->digest, 0, $bits / 16));
67             }
68              
69             sub decode {
70 11     11 0 6345 my ($class, $id_token, $key, $tokens) = @_;
71              
72 11 100       26 if ( $key ) {
73             my $tokens_verify_code = sub {
74 6     6   330 my ($header, $claims) = @_;
75              
76 6 100 100     39 if ( $tokens && $tokens->{token} ) {
77 3         16 $class->_verify_a_hash($tokens->{token}, $header->{alg}, $claims->{a_hash});
78             }
79 4 100 66     23 if ( $tokens && $tokens->{code} ) {
80 3         17 $class->_verify_c_hash($tokens->{code}, $header->{alg}, $claims->{c_hash});
81             }
82              
83 2         7 return $key;
84 6         88 };
85 6         19 return $JWT_DECODE->($id_token, $tokens_verify_code, 1);
86             }
87             else {
88 5         15 return $JWT_DECODE->($id_token, $key, 0);
89             }
90             }
91              
92             sub _verify_a_hash {
93 3     3   11 my ($class, $access_token, $alg, $a_hash) = @_;
94 3 100       10 unless ( $a_hash ) {
95 1         12 Net::OpenID::Connect::IDToken::Exception->throw(
96             code => ERROR_IDTOKEN_TOKEN_HASH_NOT_FOUND,
97             message => "a_hash not found in given JWT's claims",
98             );
99             }
100 2         10 my $expected_hash = $class->_generate_token_hash($access_token, $alg);
101 2 100       60 if ( $a_hash ne $expected_hash ) {
102 1         13 Net::OpenID::Connect::IDToken::Exception->throw(
103             code => ERROR_IDTOKEN_TOKEN_HASH_INVALID,
104             message => sprintf("a_hash is invalid: got = %s, expected = %s", $a_hash, $expected_hash),
105             );
106             }
107             }
108              
109             sub _verify_c_hash {
110 3     3   9 my ($class, $authorization_code, $alg, $c_hash) = @_;
111 3 100       9 unless ( $c_hash ) {
112 1         7 Net::OpenID::Connect::IDToken::Exception->throw(
113             code => ERROR_IDTOKEN_CODE_HASH_NOT_FOUND,
114             message => "c_hash not found in given JWT's claims",
115             );
116             }
117 2         7 my $expected_hash = $class->_generate_token_hash($authorization_code, $alg);
118 2 100       41 if ( $c_hash ne $expected_hash ) {
119 1         12 Net::OpenID::Connect::IDToken::Exception->throw(
120             code => ERROR_IDTOKEN_CODE_HASH_INVALID,
121             message => sprintf("c_hash is invalid: got = %s, expected = %s", $c_hash, $expected_hash),
122             );
123             }
124             }
125              
126             1;
127             __END__