Commit | Line | Data |
927341ac |
1 | package Google::JWT; |
2 | |
3 | use Crypt::OpenSSL::X509; |
4 | use JSON::WebToken; |
5 | use IO::All; |
6 | use JSON::MaybeXS; |
7 | use MIME::Base64; |
8 | use LWP::Simple qw(get); |
9 | use Date::Parse qw(str2time); |
10 | |
11 | use warnings; |
12 | use strict; |
13 | use strictures 1; |
14 | |
15 | =head1 NAME |
16 | |
17 | Google::JWT - JSON Web Token handler for Google tokens. |
18 | |
19 | =head1 SYNOPSIS |
20 | |
21 | my $tok = Google->JWT->decode($token); |
22 | |
23 | =head1 DESCRIPTION |
24 | |
25 | Retrieves Google's public certificates, and then retrieves the key from the |
26 | cert using L<Crypt::OpenSSL::X509>. Finally, uses the pubkey to decrypt a |
27 | Google token using L<JSON::WebToken>. |
28 | |
29 | =cut |
30 | |
31 | =head1 METHODS |
32 | |
33 | =head2 retrieve_certs |
34 | |
35 | Retrieves a pair of JSON-encoded certificates from the given URL (defaults to |
36 | Google's public cert url), and returns the decoded JSON object. |
37 | |
38 | =head3 ARGUMENTS |
39 | |
40 | =over |
41 | |
42 | =item url |
43 | |
44 | Optional. Location where certificates are located. |
45 | Defaults to https://www.googleapis.com/oauth2/v1/certs. |
46 | |
47 | =back |
48 | |
49 | =head3 RETURNS |
50 | |
51 | Decoded JSON object containing certificates. |
52 | |
53 | =cut |
54 | |
55 | sub retrieve_certs { |
56 | my ($self, $url) = @_; |
57 | |
58 | $url ||= 'https://www.googleapis.com/oauth2/v1/certs'; |
59 | return decode_json(get($url)); |
60 | } |
61 | |
62 | =head2 get_key_from_cert |
63 | |
64 | Given a pair of certificates $certs (defaults to L</retrieve_certs>), |
65 | this function returns the public key of the cert identified by $kid. |
66 | |
67 | =head3 ARGUMENTS |
68 | |
69 | =over |
70 | |
71 | =item $kid |
72 | |
73 | Required. Index of the certificate hash $hash where the cert we want is |
74 | located. |
75 | |
76 | =item $certs |
77 | |
78 | Optional. A (hashref) pair of certificates. |
79 | It's retrieved using L</retrieve_certs> if not given, |
80 | or if the pair is expired. |
81 | |
82 | =back |
83 | |
84 | =head3 RETURNS |
85 | |
86 | Public key of certificate. |
87 | |
88 | =cut |
89 | |
90 | sub get_key_from_cert { |
91 | my ($self, $kid, $certs) = @_; |
92 | |
93 | $certs ||= $self->retrieve_certs; |
94 | my $cert = $certs->{$kid}; |
95 | my $x509 = Crypt::OpenSSL::X509->new_from_string($cert); |
96 | |
97 | if ($self->is_cert_expired($x509)) { |
98 | # If we ended up here, we were given |
99 | # an old $certs string from the user. |
100 | # Let's force getting another. |
101 | return $self->get_key_from_cert($kid); |
102 | } |
103 | |
104 | return $x509->pubkey; |
105 | } |
106 | |
107 | =head2 is_cert_expired |
108 | |
109 | Returns if a given L<Crypt::OpenSSL::X509> certificate is expired. |
110 | |
111 | =cut |
112 | |
113 | sub is_cert_expired { |
114 | my ($self, $x509) = @_; |
115 | |
116 | my $expiry = str2time($x509->notAfter); |
117 | |
118 | return time > $expiry; |
119 | } |
120 | |
121 | =head2 decode |
122 | |
123 | Returns the decoded information contained in a user's token. |
124 | |
125 | =head3 ARGUMENTS |
126 | |
127 | =over |
128 | |
129 | =item $token |
130 | |
131 | Required. The user's token from Google+. |
132 | |
133 | =item $pubkey |
134 | |
135 | Optional. A public key string with which to decode the token. |
136 | If not given, the public key will be retrieved from $certs. |
137 | |
138 | =item $certs |
139 | |
140 | Optional. A pair of public key certs retrieved from Google. |
141 | If not given, or if the certificates have expired, a new |
142 | pair of certificates is retrieved. |
143 | |
144 | =back |
145 | |
146 | =head2 RETURNS |
147 | |
148 | Decoded JSON object from the decrypted token. |
149 | |
150 | =cut |
151 | |
152 | sub decode { |
153 | my ($self, $token, $certs, $pubkey) = @_; |
154 | |
155 | if (!$pubkey) { |
156 | my $details = decode_json( |
157 | MIME::Base64::decode_base64( |
158 | substr( $token, 0, CORE::index($token, '.') ) |
159 | ) |
160 | ); |
161 | |
162 | my $kid = $details->{kid}; |
163 | $pubkey = $self->get_key_from_cert($kid, $certs); |
164 | } |
165 | |
166 | return JSON::WebToken->decode($token, $pubkey); |
167 | } |
168 | |
169 | =head1 AUTHOR |
170 | |
171 | Errietta Kostala <e.kostala@shadowcat.co.uk> |
172 | |
173 | =head1 LICENSE |
174 | |
175 | This library is free software. You can redistribute it and/or modify |
176 | it under the same terms as Perl itself. |
177 | |
178 | =cut |
179 | |
180 | 1; |