Add google:JWT
Errietta Kostala [Mon, 19 Jan 2015 16:20:27 +0000 (16:20 +0000)]
lib/Google/.JWT.pm.swp [new file with mode: 0644]
lib/Google/JWT.pm [new file with mode: 0644]

diff --git a/lib/Google/.JWT.pm.swp b/lib/Google/.JWT.pm.swp
new file mode 100644 (file)
index 0000000..589cdf1
Binary files /dev/null and b/lib/Google/.JWT.pm.swp differ
diff --git a/lib/Google/JWT.pm b/lib/Google/JWT.pm
new file mode 100644 (file)
index 0000000..7d8acd0
--- /dev/null
@@ -0,0 +1,180 @@
+package Google::JWT;
+
+use Crypt::OpenSSL::X509;
+use JSON::WebToken;
+use IO::All;
+use JSON::MaybeXS;
+use MIME::Base64;
+use LWP::Simple qw(get);
+use Date::Parse qw(str2time);
+
+use warnings;
+use strict;
+use strictures 1;
+
+=head1 NAME
+
+Google::JWT - JSON Web Token handler for Google tokens.
+
+=head1 SYNOPSIS
+
+my $tok = Google->JWT->decode($token);
+
+=head1 DESCRIPTION
+
+Retrieves Google's public certificates, and then retrieves the key from the
+cert using L<Crypt::OpenSSL::X509>. Finally, uses the pubkey to decrypt a
+Google token using L<JSON::WebToken>.
+
+=cut
+
+=head1 METHODS
+
+=head2 retrieve_certs
+
+Retrieves a pair of JSON-encoded certificates from the given URL (defaults to
+Google's public cert url), and returns the decoded JSON object.
+
+=head3 ARGUMENTS
+
+=over
+
+=item url
+
+Optional. Location where certificates are located.
+Defaults to https://www.googleapis.com/oauth2/v1/certs.
+
+=back
+
+=head3 RETURNS
+
+Decoded JSON object containing certificates.
+
+=cut
+
+sub retrieve_certs {
+    my ($self, $url) = @_;
+
+    $url ||= 'https://www.googleapis.com/oauth2/v1/certs';
+    return decode_json(get($url));
+}
+
+=head2 get_key_from_cert
+
+Given a pair of certificates $certs (defaults to L</retrieve_certs>),
+this function returns the public key of the cert identified by $kid.
+
+=head3 ARGUMENTS
+
+=over
+
+=item $kid
+
+Required. Index of the certificate hash $hash where the cert we want is
+located.
+
+=item $certs
+
+Optional. A (hashref) pair of certificates.
+It's retrieved using L</retrieve_certs> if not given,
+or if the pair is expired.
+
+=back
+
+=head3 RETURNS
+
+Public key of certificate.
+
+=cut
+
+sub get_key_from_cert {
+    my ($self, $kid, $certs) = @_;
+
+    $certs ||= $self->retrieve_certs;
+    my $cert = $certs->{$kid};
+    my $x509 = Crypt::OpenSSL::X509->new_from_string($cert);
+
+    if ($self->is_cert_expired($x509)) {
+        # If we ended up here, we were given
+        # an old $certs string from the user.
+        # Let's force getting another.
+        return $self->get_key_from_cert($kid);
+    }
+
+    return $x509->pubkey;
+}
+
+=head2 is_cert_expired
+
+Returns if a given L<Crypt::OpenSSL::X509> certificate is expired.
+
+=cut
+
+sub is_cert_expired {
+    my ($self, $x509) = @_;
+
+    my $expiry = str2time($x509->notAfter);
+
+    return time > $expiry;
+}
+
+=head2 decode
+
+Returns the decoded information contained in a user's token.
+
+=head3 ARGUMENTS
+
+=over
+
+=item $token
+
+Required. The user's token from Google+.
+
+=item $pubkey
+
+Optional. A public key string with which to decode the token.
+If not given, the public key will be retrieved from $certs.
+
+=item $certs
+
+Optional. A pair of public key certs retrieved from Google.
+If not given, or if the certificates have expired, a new
+pair of certificates is retrieved.
+
+=back
+
+=head2 RETURNS
+
+Decoded JSON object from the decrypted token.
+
+=cut
+
+sub decode {
+    my ($self, $token, $certs, $pubkey) = @_;
+
+    if (!$pubkey) {
+        my $details = decode_json(
+            MIME::Base64::decode_base64(
+                substr( $token, 0, CORE::index($token, '.') )
+            )
+        );
+
+        my $kid = $details->{kid};
+        $pubkey = $self->get_key_from_cert($kid, $certs);
+    }
+
+    return JSON::WebToken->decode($token, $pubkey);
+}
+
+=head1 AUTHOR
+
+Errietta Kostala <e.kostala@shadowcat.co.uk>
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;