File Coverage

blib/lib/Net/OpenID/Connect/IDToken.pm
Criterion Covered Total %
statement 66 66 100.0
branch 19 20 95.0
condition 6 8 75.0
subroutine 17 17 100.0
pod 0 4 0.0
total 108 115 93.9


line stmt bran cond sub pod time code
1             package Net::OpenID::Connect::IDToken;
2 4     4   97059 use 5.008005;
  4         15  
3 4     4   21 use strict;
  4         8  
  4         95  
4 4     4   22 use warnings;
  4         15  
  4         204  
5              
6             our $VERSION = "0.03";
7              
8 4     4   3303 use parent qw/Exporter/;
  4         1284  
  4         22  
9              
10 4     4   3736 use MIME::Base64 qw/encode_base64url/;
  4         3000  
  4         343  
11 4     4   3682 use Digest::SHA;
  4         18626  
  4         217  
12 4     4   3625 use JSON::WebToken qw//;
  4         68134  
  4         116  
13              
14 4     4   2636 use Net::OpenID::Connect::IDToken::Exception;
  4         12  
  4         123  
15 4     4   2472 use Net::OpenID::Connect::IDToken::Constants;
  4         11  
  4         3643  
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 85 __PACKAGE__->encode(@_);
32             }
33              
34             sub decode_id_token {
35 4     4 0 6355 __PACKAGE__->decode(@_);
36             }
37              
38             sub encode {
39 9     9 0 16354 my ($class, $claims, $key, $alg, $opts, $extra_headers) = @_;
40 9   100     31 $alg ||= "HS256";
41 9         15 my $id_token_claims = +{};
42              
43 9 100       31 if ( my $token = $opts->{token} ) {
44 4         16 $id_token_claims->{at_hash} = $class->_generate_token_hash($token, $alg);
45             }
46 9 100       91 if ( my $code = $opts->{code} ) {
47 4         14 $id_token_claims->{c_hash} = $class->_generate_token_hash($code, $alg);
48             }
49              
50 9         110 return $JWT_ENCODE->(+{ %$claims, %$id_token_claims }, $key, $alg, $extra_headers);
51             }
52              
53             sub _generate_token_hash {
54 16     16   2340 my ($class, $token, $alg) = @_;
55 16         29 my $bits = substr($alg, 2); # 'HS256' -> '256'
56              
57 16         62 my $sha = Digest::SHA->new($bits);
58 16 100       227 unless ( $sha ) {
59 1         18 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         54 $sha->add($token);
65              
66 15         145 return encode_base64url(substr($sha->digest, 0, $bits / 16));
67             }
68              
69             sub decode {
70 11     11 0 5014 my ($class, $id_token, $key, $tokens) = @_;
71              
72 11 100       27 if ( $key ) {
73             my $tokens_verify_code = sub {
74 6     6   342 my ($header, $claims) = @_;
75              
76 6 100 66     29 if ( $tokens && $tokens->{token} ) {
77 3         10 $class->_verify_at_hash($tokens->{token}, $header->{alg}, $claims->{at_hash});
78             }
79 4 50 66     14 if ( $tokens && $tokens->{code} ) {
80 3         11 $class->_verify_c_hash($tokens->{code}, $header->{alg}, $claims->{c_hash});
81             }
82              
83 2         6 return $key;
84 6         26 };
85 6         15 return $JWT_DECODE->($id_token, $tokens_verify_code, 1);
86             }
87             else {
88 5         14 return $JWT_DECODE->($id_token, $key, 0);
89             }
90             }
91              
92             sub _verify_at_hash {
93 3     3   8 my ($class, $access_token, $alg, $at_hash) = @_;
94 3 100       9 unless ( $at_hash ) {
95 1         12 Net::OpenID::Connect::IDToken::Exception->throw(
96             code => ERROR_IDTOKEN_TOKEN_HASH_NOT_FOUND,
97             message => "at_hash not found in given JWT's claims",
98             );
99             }
100 2         6 my $expected_hash = $class->_generate_token_hash($access_token, $alg);
101 2 100       31 if ( $at_hash ne $expected_hash ) {
102 1         10 Net::OpenID::Connect::IDToken::Exception->throw(
103             code => ERROR_IDTOKEN_TOKEN_HASH_INVALID,
104             message => sprintf("at_hash is invalid: got = %s, expected = %s", $at_hash, $expected_hash),
105             );
106             }
107             }
108              
109             sub _verify_c_hash {
110 3     3   8 my ($class, $authorization_code, $alg, $c_hash) = @_;
111 3 100       10 unless ( $c_hash ) {
112 1         6 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         6 my $expected_hash = $class->_generate_token_hash($authorization_code, $alg);
118 2 100       26 if ( $c_hash ne $expected_hash ) {
119 1         8 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__