# $Id: TypeKey.pm 1915 2006-02-06 06:26:33Z btrott $ package Authen::TypeKey; use strict; use base qw( Class::ErrorHandler ); use Math::BigInt lib => 'GMP,Pari'; use MIME::Base64 qw( decode_base64 ); use Digest::SHA1 qw( sha1 ); use LWP::UserAgent; use HTTP::Status qw( RC_NOT_MODIFIED ); our $VERSION = '0.05'; sub new { my $class = shift; my $tk = bless { }, $class; $tk->skip_expiry_check(0); $tk->expires(600); $tk->key_url('http://www.typekey.com/extras/regkeys.txt'); $tk->version(1.1); $tk->token(''); $tk; } sub _var { my $tk = shift; my $var = shift; $tk->{$var} = shift if @_; $tk->{$var}; } sub key_cache { shift->_var('key_cache', @_) } sub skip_expiry_check { shift->_var('skip_expiry_check', @_) } sub expires { shift->_var('expires', @_) } sub key_url { shift->_var('key_url', @_) } sub token { shift->_var('token', @_) } sub version { shift->_var('version', @_) } sub ua { shift->_var('ua', @_) } sub verify { my $tk = shift; my($email, $username, $name, $ts, $sig); if (@_ == 1) { my $q = $_[0]; if (ref $q eq 'HASH') { ($email, $username, $name, $ts, $sig) = map $_[0]->{$_}, qw( email name nick ts sig ); } else { ($email, $username, $name, $ts, $sig) = map $q->param($_), qw( email name nick ts sig ); } } else { ## Later we could process arguments passed in a hash. return $tk->error("usage: verify(\$query)"); } for ($email, $sig) { tr/ /+/; } return $tk->error("TypeKey data has expired") unless $tk->skip_expiry_check || $ts + $tk->expires >= time; my $key = $tk->_fetch_key($tk->key_url) or return; my($r, $s) = split /:/, $sig; return $tk->error("Invalid signature $sig") unless $r && $s; $sig = {}; $sig->{r} = Math::BigInt->new("0b" . unpack("B*", decode_base64($r))); $sig->{s} = Math::BigInt->new("0b" . unpack("B*", decode_base64($s))); my $msg = join '::', $email, $username, $name, $ts, $tk->version >= 1.1 ? ($tk->token) : (); unless ($tk->_verify($msg, $key, $sig)) { return $tk->error("TypeKey signature verification failed"); } { name => $username, nick => $name, email => $email, ts => $ts }; } sub _verify { my $tk = shift; my($msg, $key, $sig) = @_; my $u1 = Math::BigInt->new("0b" . unpack("B*", sha1($msg))); $sig->{s}->bmodinv($key->{q}); $u1 = ($u1 * $sig->{s}) % $key->{q}; $sig->{s} = ($sig->{r} * $sig->{s}) % $key->{q}; $key->{g}->bmodpow($u1, $key->{p}); $key->{pub_key}->bmodpow($sig->{s}, $key->{p}); $u1 = ($key->{g} * $key->{pub_key}) % $key->{p}; $u1 %= $key->{q}; $u1 == $sig->{r}; } sub _fetch_key { my $tk = shift; my($uri) = @_; my $cache = $tk->key_cache; ## If it's a callback, call it and return the return value. return $cache->($tk, $uri) if $cache && ref($cache) eq 'CODE'; ## Otherwise, load the key. my $data; my $ua = $tk->ua || LWP::UserAgent->new; if ($cache) { my $res = $ua->mirror($uri, $cache); return $tk->error("Failed to fetch key: " . $res->status_line) unless $res->is_success || $res->code == RC_NOT_MODIFIED; open my $fh, $cache or return $tk->error("Can't open $cache: $!"); $data = do { local $/; <$fh> }; close $fh; } else { my $res = $ua->get($uri); return $tk->error("Failed to fetch key: " . $res->status_line) unless $res->is_success; $data = $res->content; } chomp $data; my $key = {}; for my $f (split /\s+/, $data) { my($k, $v) = split /=/, $f, 2; $key->{$k} = Math::BigInt->new($v); } $key; } 1; __END__ =head1 NAME Authen::TypeKey - TypeKey authentication verification =head1 SYNOPSIS use CGI; use Authen::TypeKey; my $q = CGI->new; my $tk = Authen::TypeKey->new; $tk->token('typekey-token'); my $res = $tk->verify($q) or die $tk->errstr; =head1 DESCRIPTION I is an implementation of verification for signatures generated by TypeKey authentication. For information on the TypeKey protocol and using TypeKey in other applications, see I. =head1 USAGE =head2 Authen::TypeKey->new Create a new I object. =head2 $tk->token([ $typekey_token ]) Your TypeKey token, which you passed to TypeKey when creating the original sign-in link. This is required to successfully validate the signature in TypeKey 1.1 and higher, which includes the token in the plaintext. This must be set B calling I. =head2 $tk->verify($query) Verify a TypeKey signature based on the other parameters given. The signature and other parameters are found in the I<$query> object, which should be either a hash reference, or any object that supports a I method--for example, a I or I object. If the signature is successfully verified, I returns a reference to a hash containing the following values. =over 4 =item * name The unique username of the TypeKey user. =item * nick The user's display name. =item * email The user's email address. If the user has chosen not to pass his/her email address, this will contain the SHA-1 hash of the string CemailE>. =item * ts The timestamp at which the signature was generated, expressed as seconds since the epoch. =back If verification is unsuccessful, I will return C, and the error message can be found in C<$tk-Eerrstr>. =head2 $tk->key_cache([ $cache ]) Provide a caching mechanism for the TypeKey public key. If I<$cache> is a CODE reference, it is treated as a callback that should return the public key. The callback will be passed two arguments: the I object, and the URI of the key. It should return a hash reference with the I

, I, I, and I keys set to I objects representing the pieces of the DSA public key. Otherwise, I<$cache> should be the path to a local file where the public key will be cached/mirrored. If I<$cache> is not set, the key is not cached. By default, no caching occurs. =head2 $tk->skip_expiry_check([ $boolean ]) Get/set a value indicating whether I should check the expiration date and time in the TypeKey parameters. The default is to check the expiration date and time. =head2 $tk->expires([ $secs ]) Get/set the amount of time at which a TypeKey signature is intended to expire. The default value is 600 seconds, i.e. 10 minutes. =head2 $tk->key_url([ $url ]) Get/set the URL from which the TypeKey public key can be obtained. The default URL is I. =head2 $tk->ua([ $user_agent ]) Get/set the LWP::UserAgent-like object which will be used to retrieve the regkeys from the network. Needs to support I and I methods. By default, LWP::UserAgent is used, and this method as a getter returns C unless the user agent has been previously set. =head2 $tk->version([ $version ]) Get/set the version of the TypeKey protocol to use. The default version is C<1.1>. =head1 LICENSE I is free software; you may redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR & COPYRIGHT Except where otherwise noted, I is Copyright 2004 Six Apart Ltd, cpan@sixapart.com. All rights reserved. =cut