line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#!/usr/bin/perl |
2
|
|
|
|
|
|
|
package Authen::OATH::OCRA; |
3
|
1
|
|
|
1
|
|
19617
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
27
|
|
4
|
1
|
|
|
1
|
|
4
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
28
|
|
5
|
1
|
|
|
1
|
|
1532
|
use Math::BigInt; |
|
1
|
|
|
|
|
26354
|
|
|
1
|
|
|
|
|
6
|
|
6
|
1
|
|
|
1
|
|
24052
|
use Moose; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
use Carp; |
8
|
|
|
|
|
|
|
use Digest::SHA qw(hmac_sha1 hmac_sha256 hmac_sha512); |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
has 'ocrasuite' => ( |
11
|
|
|
|
|
|
|
'is' => 'rw', |
12
|
|
|
|
|
|
|
'isa' => 'Str', |
13
|
|
|
|
|
|
|
); |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
has 'key' => ( |
16
|
|
|
|
|
|
|
'is' => 'rw', |
17
|
|
|
|
|
|
|
'isa' => 'Str', |
18
|
|
|
|
|
|
|
); |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
has 'counter' => ( |
21
|
|
|
|
|
|
|
'is' => 'rw', |
22
|
|
|
|
|
|
|
'isa' => 'Int' |
23
|
|
|
|
|
|
|
); |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
has 'question' => ( |
26
|
|
|
|
|
|
|
'is' => 'rw', |
27
|
|
|
|
|
|
|
'isa' => 'Str' |
28
|
|
|
|
|
|
|
); |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
has 'password' => ( |
31
|
|
|
|
|
|
|
'is' => 'rw', |
32
|
|
|
|
|
|
|
'isa' => 'Str' |
33
|
|
|
|
|
|
|
); |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
has 'session_information' => ( |
36
|
|
|
|
|
|
|
'is' => 'rw', |
37
|
|
|
|
|
|
|
'isa' => 'Str' |
38
|
|
|
|
|
|
|
); |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
has 'timestamp' => ( |
41
|
|
|
|
|
|
|
'is' => 'rw', |
42
|
|
|
|
|
|
|
'isa' => 'Int' |
43
|
|
|
|
|
|
|
); |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
=head1 NAME |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
OCRA - OATH Challenge-Response Algorithm |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
=head1 VERSION |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
Version 1.01 |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
=cut |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
our $VERSION = "1.01"; |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
=head1 SYNOPSIS |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
use Authen::OATH::OCRA; |
60
|
|
|
|
|
|
|
my $key = '7110eda4d09e062aa5e4a390b0a572ac0d2c0220'; |
61
|
|
|
|
|
|
|
my $question = 'This is the challenge'; |
62
|
|
|
|
|
|
|
my $ocrasuite = 'OCRA-1:HOTP-SHA1-6:QA32'; |
63
|
|
|
|
|
|
|
my $ocra = Authen::OATH::OCRA->new( |
64
|
|
|
|
|
|
|
ocrasuite => $ocrasuite, |
65
|
|
|
|
|
|
|
key => $key, # key must be hex encoded |
66
|
|
|
|
|
|
|
question => $question |
67
|
|
|
|
|
|
|
); |
68
|
|
|
|
|
|
|
my $otp = $ocra->ocra(); |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
Parameters may be set after object instantiation using accesor methods before the ocra() method is called |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
use Authen::OATH::OCRA; |
73
|
|
|
|
|
|
|
my $ocra = Authen::OATH::OCRA->new(); |
74
|
|
|
|
|
|
|
$ocra->ocrasuite('OCRA-1:HOTP-SHA512-6:C-QA32-PSHA1-S20-T1M'); |
75
|
|
|
|
|
|
|
$ocra->key('7110eda4d09e062aa5e4a390b0a572ac0d2c0220'); |
76
|
|
|
|
|
|
|
$ocra->counter(77777777); |
77
|
|
|
|
|
|
|
$ocra->question("I bet you can't"); |
78
|
|
|
|
|
|
|
$ocra->password('f7c3bc1d808e04732adf679965ccc34ca7ae3441'); |
79
|
|
|
|
|
|
|
$ocra->session_information('Some session info'); |
80
|
|
|
|
|
|
|
$ocra->timestamp(1234567890); |
81
|
|
|
|
|
|
|
my $otp = $ocra->ocra(); |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=head1 Description |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
Implementation of the OATH Challenge-Response authentication algorithm |
86
|
|
|
|
|
|
|
as defined by The Initiative for Open Authentication OATH (http://www.openauthentication.org) |
87
|
|
|
|
|
|
|
in RFC 6287 (http://tools.ietf.org/html/rfc6287) |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=head1 PARAMETERS |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
Minimum required parameters are: ocrasuite, key and question. |
94
|
|
|
|
|
|
|
Aditional parameters (counter, password or session_information) may be required depending on the specified ocrasuite. |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
Accesor methods are provided for each parameter |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
=head2 ocrasuite |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
Text string that specifies the operation mode for OCRA. For further information see http://tools.ietf.org/html/rfc6287#section-6 |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
=head2 key |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
Text string with the shared secret key known to both parties, must be in hexadecimal format |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
=head2 counter |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
An unsigned integer value, must be sinchronized between both parties |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
=head2 question |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
Text string with the challenge question |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=head2 password |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
Text string with the hash (SHA-1 , SHA-256 and SHA-512 are supported) value of PIN/password that is known to both parties, must be in hexadecimal format |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
=head2 session_information |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
Text string that contains information about the current session, must be UTF-8 encoded |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
=head2 timestamp |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
Defaults to system time if required by the OCRA Suite and not provided, use only if you need to set the time manually. An unsigned integer value representing the manual Unix Time in the granularity specified in the OCRA Suite |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
=head1 SUBROUTINES/METHODS |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
=head2 ocra |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
Returns a text string with the One Time Password for the provided parameters |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
my $otp = $ocra->ocra(); |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
ocra() passed all the test vectors contained in the RFC document. |
135
|
|
|
|
|
|
|
=cut |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
sub ocra { |
138
|
|
|
|
|
|
|
my ($self) = @_; |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
#Validate that min required parameters are present |
141
|
|
|
|
|
|
|
croak "Parameter \"ocrasuite\" is required" |
142
|
|
|
|
|
|
|
unless defined( $self->{ocrasuite} ); |
143
|
|
|
|
|
|
|
croak "Parameter \"question\" is required" |
144
|
|
|
|
|
|
|
unless defined( $self->{question} ); |
145
|
|
|
|
|
|
|
croak "Parameter \"key\" is required" unless defined( $self->{key} ); |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
#Validate the OCRA Suite format and parse sub parameters into variables |
148
|
|
|
|
|
|
|
croak "Invalid ocrasuite" |
149
|
|
|
|
|
|
|
unless $self->{ocrasuite} =~ /^ |
150
|
|
|
|
|
|
|
OCRA-1:HOTP-SHA |
151
|
|
|
|
|
|
|
(1|256|512)- |
152
|
|
|
|
|
|
|
(\d+): |
153
|
|
|
|
|
|
|
(C-)?Q |
154
|
|
|
|
|
|
|
(A|N|H)\d+ |
155
|
|
|
|
|
|
|
(-PSHA(1|256|512))? |
156
|
|
|
|
|
|
|
(-S(\d+))? |
157
|
|
|
|
|
|
|
(-T(\d+)(S|H|M))? |
158
|
|
|
|
|
|
|
$ |
159
|
|
|
|
|
|
|
/x; |
160
|
|
|
|
|
|
|
my $sha = $1; |
161
|
|
|
|
|
|
|
my $digits = $2; |
162
|
|
|
|
|
|
|
my $has_counter = $3; |
163
|
|
|
|
|
|
|
my $question_format = $4; |
164
|
|
|
|
|
|
|
my $has_password = $5; |
165
|
|
|
|
|
|
|
my $password_format = $6; |
166
|
|
|
|
|
|
|
my $has_session = $7; |
167
|
|
|
|
|
|
|
my $session_size = $8; |
168
|
|
|
|
|
|
|
my $has_timestamp = $9; |
169
|
|
|
|
|
|
|
my $period = $10; |
170
|
|
|
|
|
|
|
my $time_unit = $11; |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
#Validate parameters included in the OCRA Suite |
173
|
|
|
|
|
|
|
croak "Must request at least 4 digits" if $digits < 4; |
174
|
|
|
|
|
|
|
croak "Must request at most 10 digits" if $digits > 10; |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
#Validate if additional parameters required |
177
|
|
|
|
|
|
|
#in the provided OCRA Suite are present |
178
|
|
|
|
|
|
|
croak "Parameter \"counter\" is required for the provided ocrasuite" |
179
|
|
|
|
|
|
|
if $has_counter && !defined( $self->{counter} ); |
180
|
|
|
|
|
|
|
croak "Parameter \"password\" is required for the provided ocrasuite" |
181
|
|
|
|
|
|
|
if $has_password && !defined( $self->{password} ); |
182
|
|
|
|
|
|
|
croak |
183
|
|
|
|
|
|
|
"Parameter \"session_information\" is required for the provided ocrasuite" |
184
|
|
|
|
|
|
|
if $has_session && !defined( $self->{session_information} ); |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
#Initiate the data input with the Ocra Suite and the separator byte |
187
|
|
|
|
|
|
|
my $datainput = $self->ocrasuite . "\0"; |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
#Concatenate encoded Counter padded with 8 zeros at left |
190
|
|
|
|
|
|
|
$datainput .= _hex_to_bytes( _dec_to_hex( $self->counter ), 8 ) |
191
|
|
|
|
|
|
|
if $has_counter; |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
#Encode the Question on the specified format |
194
|
|
|
|
|
|
|
my $question; |
195
|
|
|
|
|
|
|
$question = _str_to_hex( $self->question ) if $question_format eq 'A'; |
196
|
|
|
|
|
|
|
$question = _dec_to_hex( $self->question ) if $question_format eq 'N'; |
197
|
|
|
|
|
|
|
$question = _check_hex( $self->question ) if $question_format eq 'H'; |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
#Concatenate encoded Question padded with 128 zeros at right |
200
|
|
|
|
|
|
|
$datainput .= pack( "H*", |
201
|
|
|
|
|
|
|
_check_hex($question) |
202
|
|
|
|
|
|
|
. "\0" x ( 256 - length( _check_hex($question) ) ) ); |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
#Concatenate encoded password and pad with zeros |
205
|
|
|
|
|
|
|
#to the left depending on the specified SHA |
206
|
|
|
|
|
|
|
my %password_size = ( 1 => 20, 256 => 32, 512 => 64 ); |
207
|
|
|
|
|
|
|
$datainput |
208
|
|
|
|
|
|
|
.= _hex_to_bytes( $self->password, $password_size{$password_format} ) |
209
|
|
|
|
|
|
|
if $has_password; |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
#Concatenate encoded Session Information padded with zeros at left |
212
|
|
|
|
|
|
|
$datainput .= _hex_to_bytes( _str_to_hex( $self->session_information ), |
213
|
|
|
|
|
|
|
$session_size ) |
214
|
|
|
|
|
|
|
if $has_session; |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
#Assign timestamp value |
217
|
|
|
|
|
|
|
if ($has_timestamp) { |
218
|
|
|
|
|
|
|
my $timestamp; |
219
|
|
|
|
|
|
|
if ( $self->{timestamp} ) { |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
#use provided timestamp |
222
|
|
|
|
|
|
|
$timestamp = $self->timestamp; |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
else { |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
#if timestamp is not provided, query the system |
227
|
|
|
|
|
|
|
#time and calculate according to provided parameters |
228
|
|
|
|
|
|
|
my %timestep = ( S => 1, M => 60, H => 3600 ); |
229
|
|
|
|
|
|
|
$timestamp = int( time() / ( $period * $timestep{$time_unit} ) ); |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
#Concatenate encoded timestamp padded with 8 zeros at left |
233
|
|
|
|
|
|
|
$datainput .= _hex_to_bytes( _dec_to_hex($timestamp), 8 ); |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
#Encode the Key |
237
|
|
|
|
|
|
|
my $key = pack( 'H*', _check_hex( $self->key ) ); |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
#Compute the HMAC |
240
|
|
|
|
|
|
|
my $hash; |
241
|
|
|
|
|
|
|
{ |
242
|
|
|
|
|
|
|
no strict 'refs'; |
243
|
|
|
|
|
|
|
$hash = &{"hmac_sha$sha"}( $datainput, $key ); |
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
#Dynamic Truncation |
247
|
|
|
|
|
|
|
my $offset = hex substr unpack( "H*", $hash ), -1; |
248
|
|
|
|
|
|
|
my $dt = unpack "N" => substr $hash, $offset, 4; |
249
|
|
|
|
|
|
|
$dt &= 0x7fffffff; |
250
|
|
|
|
|
|
|
$dt = Math::BigInt->new($dt); |
251
|
|
|
|
|
|
|
my $modulus = 10**$digits; |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
#Compute the HOTP value |
254
|
|
|
|
|
|
|
return sprintf( "%0${digits}d", $dt->bmod($modulus) ); |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
#Private method, encodes a string in hexadecimal format |
259
|
|
|
|
|
|
|
sub _str_to_hex { |
260
|
|
|
|
|
|
|
my ($str) = @_; |
261
|
|
|
|
|
|
|
return unpack( 'H*', $str ); |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
#Private method, encodes a decimal in hexadecimal format |
266
|
|
|
|
|
|
|
sub _dec_to_hex { |
267
|
|
|
|
|
|
|
my ($dec) = @_; |
268
|
|
|
|
|
|
|
my $big_int = Math::BigInt->new($dec); |
269
|
|
|
|
|
|
|
return _check_hex( $big_int->as_hex() ); |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
#Private method, validates hexadecimal format, removes sign and preceding 0x or x |
273
|
|
|
|
|
|
|
sub _check_hex { |
274
|
|
|
|
|
|
|
my ($num) = @_; |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
if ($num =~ s/ |
277
|
|
|
|
|
|
|
^ |
278
|
|
|
|
|
|
|
( [+-]? ) |
279
|
|
|
|
|
|
|
(0?x)? |
280
|
|
|
|
|
|
|
( |
281
|
|
|
|
|
|
|
[0-9a-fA-F]* |
282
|
|
|
|
|
|
|
( _ [0-9a-fA-F]+ )* |
283
|
|
|
|
|
|
|
) |
284
|
|
|
|
|
|
|
$ |
285
|
|
|
|
|
|
|
//x |
286
|
|
|
|
|
|
|
) |
287
|
|
|
|
|
|
|
{ |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
return $3; |
290
|
|
|
|
|
|
|
} |
291
|
|
|
|
|
|
|
else { croak "$num: not in hex format"; } |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
} |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
#private method, encodes hexadecimal to binary, pads with zeros at left |
296
|
|
|
|
|
|
|
sub _hex_to_bytes { |
297
|
|
|
|
|
|
|
my ( $hex, $pad ) = @_; |
298
|
|
|
|
|
|
|
$hex = _check_hex($hex); |
299
|
|
|
|
|
|
|
my $length = length($hex); |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
return pack( 'H*', "\0" x ( ( $pad * 2 ) - $length ) . $hex ); |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
} |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
=head1 AUTHOR |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
Pascual De Ruvo, C<< <pderuvo at gmail.com> >> |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
=head1 BUGS |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
Please report any bugs or feature requests to C<bug-authen-oath-ocra at rt.cpan.org>, or through |
312
|
|
|
|
|
|
|
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Authen-OATH-OCRA>. I will be notified, and then you'll |
313
|
|
|
|
|
|
|
automatically be notified of progress on your bug as I make changes. |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
=head1 SUPPORT |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
perldoc Authen::OATH::OCRA |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
You can also look for information at: |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
=over 4 |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
=item * OATH: Initiative for Open Authentication |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
L<http://www.openauthentication.org> |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
=item * OCRA: OATH Challenge-Response Algorithm RFC |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
L<http://tools.ietf.org/html/rfc6287> |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Authen-OATH-OCRA> |
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
L<http://annocpan.org/dist/Authen-OATH-OCRA> |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
=item * CPAN Ratings |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
L<http://cpanratings.perl.org/d/Authen-OATH-OCRA> |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
=item * Search CPAN |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
L<http://search.cpan.org/dist/Authen-OATH-OCRA/> |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
=back |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
Copyright 2012 Pascual De Ruvo. |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
358
|
|
|
|
|
|
|
under the terms of either: the GNU General Public License as published |
359
|
|
|
|
|
|
|
by the Free Software Foundation; or the Artistic License. |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
See http://dev.perl.org/licenses/ for more information. |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
=cut |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
1; # End of Authen::OATH::OCRA |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
################################################################################ |
369
|
|
|
|
|
|
|
# EOF |