File Coverage

blib/lib/Auth/Yubikey_WebClient.pm
Criterion Covered Total %
statement 18 95 18.9
branch 0 24 0.0
condition 0 9 0.0
subroutine 6 10 60.0
pod 4 4 100.0
total 28 142 19.7


line stmt bran cond sub pod time code
1             package Auth::Yubikey_WebClient;
2              
3 1     1   21054 use warnings;
  1         3  
  1         35  
4 1     1   6 use strict;
  1         3  
  1         31  
5 1     1   832 use MIME::Base64;
  1         765  
  1         76  
6 1     1   643 use Digest::HMAC_SHA1 qw(hmac_sha1 hmac_sha1_hex);
  1         24481  
  1         77  
7 1     1   1497 use LWP::Simple;
  1         131895  
  1         9  
8 1     1   491 use URI::Escape;
  1         3  
  1         1235  
9              
10             =head1 NAME
11              
12             Auth::Yubikey_WebClient - Authenticating the Yubikey against the Yubico Web API
13              
14             =head1 VERSION
15              
16             Version 3.00
17              
18             =cut
19              
20             our $VERSION = '3.00';
21              
22             =head1 SYNOPSIS
23              
24             Authenticate against the Yubico server via the Web API in Perl
25              
26             Sample CGI script :-
27              
28             #!/usr/bin/perl
29              
30             use CGI;
31             $cgi = new CGI;
32             $otp = $cgi->param("otp");
33              
34             print "Content-type: text/html\n\n";
35             print "\n";
36             print "
Yubikey :
\n";
37              
38             use Auth::Yubikey_WebClient;
39              
40             $id = "";
41             $api = "";
42             $nonce = "";
43              
44             if($otp)
45             {
46             $result = Auth::Yubikey_WebClient::yubikey_webclient($otp,$id,$api,$nonce);
47             # result can be either ERR or OK
48              
49             print "Authentication result : $result
";
50             }
51              
52             print "\n";
53              
54              
55             =head1 FUNCTIONS
56              
57             =head2 new
58              
59             Creates a new Yubikey Webclient connection
60              
61             use Auth::Yubikey_WebClient;
62              
63             my $yubi = Auth::Yubikey_WebClient->new({
64             id => ,
65             api => '' ,
66             nonce => ''
67             });
68              
69             You can overwrite the URL called if you want to call an alternate authentication server as well :-
70              
71             use Auth::Yubikey_WebClient;
72              
73             my $yubi = Auth::Yubikey_WebClient->new({
74             id => ,
75             api => '' ,
76             nonce => '',
77             url => 'http://www.otherserver.com/webapi.php'
78             });
79              
80             =cut
81              
82             sub new
83             {
84 0     0 1   my ($class,$options_ref) = @_;
85 0           my $self = {};
86              
87 0   0       bless $self, ref $class || $class;
88            
89 0 0         if(! defined $options_ref)
90             {
91 0           die "You did not pass any parameters to the Yubikey Web Client initialization";
92             }
93 0           my %options = %{$options_ref};
  0            
94              
95             # grab the variables from the initialization
96 0 0         if(defined $options{id})
97             {
98 0           $self->{id} = $options{id};
99             }
100             else
101             {
102 0           die "Can not start without a Yubikey ID";
103             }
104              
105 0 0         if(defined $options{api})
106             {
107 0           $self->{api} = $options{api};
108              
109 0 0         if(length($self->{api}) % 4 != 0)
110             {
111 0           die "Your API key must be in 4 byte lengths";
112             }
113             }
114             else
115             {
116 0           die "Can not start without a Yubikey API key";
117             }
118              
119 0 0         $self->{nonce} = defined $options{nonce} ? $options{nonce} : '';
120              
121 0 0         $self->{url} = defined $options{url} ? $options{url} : 'http://api2.yubico.com/wsapi/2.0/verify';
122              
123 0           return $self;
124             }
125              
126             =head2 debug
127              
128             Displays the debug info
129              
130             $yubi->debug();
131              
132             Prints out some debug information. Useful to be called after authentication to see what Yubico sent back. You can also call the variables yourself, for example if you'd like to see what the token ID is, call $yubi->{publicid}. The same goes for all the other variables printed in debug.
133              
134             =cut
135              
136             sub debug
137             {
138 0     0 1   my ($self) = @_;
139              
140 0           print "id = $self->{id}\n";
141 0           print "api = $self->{api}\n";
142 0           print "url = $self->{url}\n";
143 0           print "nonce = $self->{nonce}\n";
144 0           print "params = $self->{params}\n";
145 0           print "status = $self->{status}\n";
146 0           print "otp = $self->{otp}\n";
147 0           print "publicid = $self->{publicid}\n";
148 0           print "t = $self->{t}\n";
149 0           print "sl = $self->{sl}\n";
150 0           print "timestamp = $self->{timestamp}\n";
151 0           print "sessioncounter = $self->{sessioncounter}\n";
152 0           print "sessionuse = $self->{sessionuse}\n";
153              
154             # print "response = $self->{response}\n";
155            
156             }
157              
158             =head2 yubikey_webclient
159              
160             =cut
161              
162             sub yubikey_webclient
163             {
164 0     0 1   my ($otp,$id,$api,$nonce) = @_;
165              
166 0           my $yubi_tmp = new Auth::Yubikey_WebClient ( { id => $id, api => $api, nonce => $nonce } );
167              
168 0           return $yubi_tmp->otp($otp);
169             }
170              
171             =head2 otp
172              
173             Check a OTP for validity
174              
175             $result = $yubi->otp($otp);
176              
177             Call the otp procedure with the input from the yubikey. It will return the result.
178              
179             This function will also setup a few internal variables that was returned from Yubico.
180              
181             =cut
182              
183             sub otp
184             {
185 0     0 1   my ($self,$otp) = @_;
186              
187 0           chomp($otp);
188 0           $self->{otp} = $otp;
189              
190             # lets do a basic sanity check on the otp, before we blast it off to yubico...
191 0 0 0       if($self->{otp} !~ /[cbdefghijklnrtuv]/i || length($self->{otp}) < 32)
192             {
193 0           $self->{status} = "ERR_BAD_OTP";
194 0           return $self->{status};
195             }
196              
197             # Generate nonce unless passed
198 0 0         $self->{nonce} = hmac_sha1_hex(time, rand()) unless $self->{nonce};
199              
200             # Start generating the parameters
201 0           $self->{params} = "id=$self->{id}&nonce=$self->{nonce}&otp=" . uri_escape($self->{otp}) . "×tamp=1";
202 0           $self->{params} .= '&h=' . uri_escape(encode_base64(hmac_sha1($self->{params}, decode_base64($self->{api})), ''));
203              
204             # pass the request to yubico
205 0           $self->{response} = get($self->{url} . "?$self->{params}");
206 0           chomp($self->{response});
207              
208 0 0         if($self->{response} !~ /status=ok/i)
209             {
210             # If the status is not ok, let's not even go through the rest...
211 0           $self->{response} =~ m/status=(.+)/;
212 0           $self->{status} = "ERR_$1";
213 0           $self->{status} =~ s/\s//g;
214 0           return $self->{status};
215             }
216              
217             #extract each of the lines, and store in a hash...
218              
219 0           my %result;
220 0           foreach (split(/\n/,$self->{response}))
221             {
222 0           chomp;
223 0 0         if($_ =~ /=/)
224             {
225 0           ($a,$b) = split(/=/,$_,2);
226 0           $b =~ s/\s//g;
227 0           $result{$a} = $b;
228 0           $self->{$a} = $b;
229             }
230             }
231              
232             # save the h parameter, that's what we'll be comparing to
233              
234 0           my $signatur=$result{h};
235 0           delete $result{h};
236 0           my $datastring='';
237              
238 0           my $key;
239 0           foreach $key (sort keys %result)
240             {
241 0           $result{$key} =~ s/\s//g;
242 0           $datastring .= "$key=$result{$key}&";
243             }
244 0           $datastring = substr($datastring,0,length($datastring)-1);
245              
246             # Check that nonce and OTP are the ones we asked for
247 0           $self->{status} = "ERR_MSG_AUTH";
248              
249 0 0 0       return "ERR_MSG_AUTH" unless ($self->{nonce} eq $result{nonce} and $self->{otp} eq $result{otp});
250              
251 0           my $hmac = encode_base64(hmac_sha1($datastring,decode_base64($self->{api})));
252              
253 0           chomp($hmac);
254              
255 0 0         if($hmac eq $signatur)
256             {
257 0           $self->{publicid} = substr(lc($self->{otp}),0,12);
258 0           $self->{status} = "OK";
259 0           return "OK";
260             }
261             else
262             {
263 0           $self->{status} = "ERR_HMAC";
264 0           return "ERR_HMAC";
265             }
266             }
267              
268             =head1 USAGE
269              
270             Before you can use this module, you need to register for an API key at Yubico. This is as simple as logging onto and entering your Yubikey's OTP and your email address. Once you have the API and ID, you need to provide those details to the module to work.
271              
272             =head1 AUTHOR
273              
274             Phil Massyn, C<< >>
275              
276             =head1 BUGS
277              
278             Please report any bugs or feature requests to C, or through
279             the web interface at L. I will be notified, and then you'll
280             automatically be notified of progress on your bug as I make changes.
281              
282              
283              
284              
285             =head1 SUPPORT
286              
287             You can find documentation for this module with the perldoc command.
288              
289             perldoc Auth::Yubikey_WebClient
290              
291              
292             You can also look for information at:
293              
294             =over 4
295              
296             =item * RT: CPAN's request tracker
297              
298             L
299              
300             =item * AnnoCPAN: Annotated CPAN documentation
301              
302             L
303              
304             =item * CPAN Ratings
305              
306             L
307              
308             =item * Search CPAN
309              
310             L
311              
312             =back
313              
314             =head1 Version history
315              
316             0.04 - Fixed bug L
317             1.00 - Added validation of the request to Yubico (Thanks to Kirill Miazine)
318             2.00 - Added nounce coding (Thanks to Ludvig af Klinteberg)
319             2.01 - Response turning into an array due to \r bug (Thanks to Peter Norin)
320              
321             =head1 ACKNOWLEDGEMENTS
322              
323             =head1 COPYRIGHT & LICENSE
324              
325             Copyright 2010 Phil Massyn, all rights reserved.
326              
327             This program is free software; you can redistribute it and/or modify it
328             under the same terms as Perl itself.
329              
330              
331             =cut
332              
333             1; # End of Auth::Yubikey_WebClient