line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package AnyEvent::Yubico; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
37966
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
30
|
|
4
|
1
|
|
|
1
|
|
1106
|
use AnyEvent::HTTP; |
|
1
|
|
|
|
|
52546
|
|
|
1
|
|
|
|
|
113
|
|
5
|
1
|
|
|
1
|
|
4162
|
use UUID::Tiny; |
|
1
|
|
|
|
|
76955
|
|
|
1
|
|
|
|
|
214
|
|
6
|
1
|
|
|
1
|
|
12
|
use MIME::Base64; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
55
|
|
7
|
1
|
|
|
1
|
|
1293
|
use Digest::HMAC_SHA1 qw(hmac_sha1); |
|
1
|
|
|
|
|
2596
|
|
|
1
|
|
|
|
|
98
|
|
8
|
1
|
|
|
1
|
|
1306
|
use URI::Escape; |
|
1
|
|
|
|
|
2803
|
|
|
1
|
|
|
|
|
1670
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
our $VERSION = '0.9.3'; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
# Creates a new Yubico instance to be used for validation of OTPs. |
13
|
|
|
|
|
|
|
sub new { |
14
|
2
|
|
|
2
|
0
|
2701
|
my $class = shift; |
15
|
2
|
|
|
|
|
16
|
my $self = { |
16
|
|
|
|
|
|
|
sign_request => 1, |
17
|
|
|
|
|
|
|
local_timeout => 30.0, |
18
|
|
|
|
|
|
|
urls => [ |
19
|
|
|
|
|
|
|
"https://api.yubico.com/wsapi/2.0/verify", |
20
|
|
|
|
|
|
|
"https://api2.yubico.com/wsapi/2.0/verify", |
21
|
|
|
|
|
|
|
"https://api3.yubico.com/wsapi/2.0/verify", |
22
|
|
|
|
|
|
|
"https://api4.yubico.com/wsapi/2.0/verify", |
23
|
|
|
|
|
|
|
"https://api5.yubico.com/wsapi/2.0/verify" |
24
|
|
|
|
|
|
|
] |
25
|
|
|
|
|
|
|
}; |
26
|
|
|
|
|
|
|
|
27
|
2
|
|
|
|
|
5
|
my $options = shift; |
28
|
|
|
|
|
|
|
|
29
|
2
|
|
|
|
|
11
|
$self = { %$self, %$options }; |
30
|
|
|
|
|
|
|
|
31
|
2
|
|
|
|
|
11
|
return bless $self, $class; |
32
|
|
|
|
|
|
|
}; |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
# Verifies the given OTP and returns a true value if the OTP could be |
35
|
|
|
|
|
|
|
# verified, false otherwise. |
36
|
|
|
|
|
|
|
sub verify { |
37
|
1
|
|
|
1
|
0
|
7
|
return verify_async(@_)->recv->{status} eq 'OK'; |
38
|
|
|
|
|
|
|
} |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
# Verifies the given OTP and returns a hash containing the server response. |
41
|
|
|
|
|
|
|
sub verify_sync { |
42
|
3
|
|
|
3
|
0
|
8734
|
return verify_async(@_)->recv |
43
|
|
|
|
|
|
|
} |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
# Non-blocking version of verify_sync, which returns a condition variable |
46
|
|
|
|
|
|
|
# (see AnyEvent->condvar for details). |
47
|
|
|
|
|
|
|
sub verify_async { |
48
|
5
|
|
|
5
|
0
|
14
|
my($self, $otp, $callback) = @_; |
49
|
|
|
|
|
|
|
|
50
|
5
|
|
|
|
|
31
|
my $nonce = create_UUID_as_string(UUID_V4); |
51
|
5
|
|
|
|
|
1636
|
$nonce =~ s/-//g; |
52
|
|
|
|
|
|
|
|
53
|
5
|
|
|
|
|
33
|
my $params = { |
54
|
|
|
|
|
|
|
id => $self->{client_id}, |
55
|
|
|
|
|
|
|
nonce => $nonce, |
56
|
|
|
|
|
|
|
otp => $otp |
57
|
|
|
|
|
|
|
}; |
58
|
|
|
|
|
|
|
|
59
|
5
|
50
|
|
|
|
31
|
if(exists $self->{timeout}) { |
60
|
0
|
|
|
|
|
0
|
$params->{timeout} = $self->{timeout}; |
61
|
|
|
|
|
|
|
} |
62
|
5
|
50
|
|
|
|
16
|
if(exists $self->{sl}) { |
63
|
0
|
|
|
|
|
0
|
$params->{sl} = $self->{sl}; |
64
|
|
|
|
|
|
|
} |
65
|
5
|
50
|
|
|
|
22
|
if($self->{timestamp}) { |
66
|
0
|
|
|
|
|
0
|
$params->{timestamp} = 1; |
67
|
|
|
|
|
|
|
} |
68
|
|
|
|
|
|
|
|
69
|
5
|
100
|
66
|
|
|
47
|
if($self->{sign_request} and !$self->{api_key} eq '') { |
70
|
4
|
|
|
|
|
15
|
$params->{h} = $self->sign($params); |
71
|
|
|
|
|
|
|
} |
72
|
|
|
|
|
|
|
|
73
|
5
|
|
|
|
|
11
|
my $query = ""; |
74
|
5
|
|
|
|
|
17
|
for my $key (keys %$params) { |
75
|
19
|
|
|
|
|
303
|
$query = "$query&$key=".uri_escape($params->{$key}); |
76
|
|
|
|
|
|
|
} |
77
|
5
|
|
|
|
|
62
|
$query = "?".substr($query, 1); |
78
|
|
|
|
|
|
|
|
79
|
5
|
|
|
|
|
9
|
my $last_response; |
80
|
5
|
|
|
|
|
11
|
my @requests = (); |
81
|
5
|
|
|
|
|
172
|
my $result_var = AnyEvent->condvar(cb => $callback); |
82
|
|
|
|
|
|
|
my $inner_var = AnyEvent->condvar(cb => sub { |
83
|
5
|
|
|
5
|
|
107
|
my $result = shift->recv; |
84
|
|
|
|
|
|
|
|
85
|
5
|
|
|
|
|
55
|
foreach my $req (@requests) { |
86
|
21
|
|
|
|
|
11224
|
undef $req; |
87
|
|
|
|
|
|
|
} |
88
|
|
|
|
|
|
|
|
89
|
5
|
100
|
|
|
|
76
|
if(exists $result->{status}) { |
|
|
50
|
|
|
|
|
|
90
|
3
|
|
|
|
|
21
|
$result_var->send($result); |
91
|
|
|
|
|
|
|
} elsif(exists $last_response->{status}) { |
92
|
|
|
|
|
|
|
#All responses returned replayed request. |
93
|
2
|
|
|
|
|
14
|
$result_var->send($last_response); |
94
|
|
|
|
|
|
|
} else { |
95
|
|
|
|
|
|
|
#Didn't get any valid responses. |
96
|
0
|
|
|
|
|
0
|
$result_var->croak("No valid response!"); |
97
|
|
|
|
|
|
|
} |
98
|
5
|
|
|
|
|
6493
|
}); |
99
|
|
|
|
|
|
|
|
100
|
5
|
|
|
|
|
37
|
foreach my $url (@{$self->{urls}}) { |
|
5
|
|
|
|
|
20
|
|
101
|
21
|
|
|
|
|
21789
|
$inner_var->begin(); |
102
|
|
|
|
|
|
|
push(@requests, http_get("$url$query", |
103
|
|
|
|
|
|
|
timeout => $self->{local_timeout}, |
104
|
|
|
|
|
|
|
tls_ctx => 'high', |
105
|
|
|
|
|
|
|
sub { |
106
|
9
|
|
|
9
|
|
3015554
|
my($body, $hdr) = @_; |
107
|
|
|
|
|
|
|
|
108
|
9
|
100
|
|
|
|
161
|
if(not $hdr->{Status} =~ /^2/) { |
109
|
|
|
|
|
|
|
#Error, store message if none exists. |
110
|
6
|
100
|
|
|
|
29
|
if(not exists $last_response->{status}) { |
111
|
2
|
|
|
|
|
8
|
$last_response->{status} = $hdr->{Reason}; |
112
|
|
|
|
|
|
|
} |
113
|
6
|
|
|
|
|
27
|
$inner_var->end(); |
114
|
6
|
|
|
|
|
63
|
return; |
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
|
117
|
3
|
|
|
|
|
84
|
my $response = parse_response($body); |
118
|
|
|
|
|
|
|
|
119
|
3
|
50
|
|
|
|
16
|
if(! exists $response->{status}) { |
120
|
|
|
|
|
|
|
#Response does not look valid, discard. |
121
|
0
|
|
|
|
|
0
|
$inner_var->end(); |
122
|
0
|
|
|
|
|
0
|
return; |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
3
|
100
|
|
|
|
95
|
if(! $self->{api_key} eq '') { |
126
|
2
|
|
|
|
|
8
|
my $signature = $response->{h}; |
127
|
2
|
|
|
|
|
5
|
delete $response->{h}; |
128
|
2
|
50
|
|
|
|
11
|
if(! $signature eq $self->sign($response)) { |
129
|
0
|
|
|
|
|
0
|
$response->{status} = "BAD_RESPONSE_SIGNATURE"; |
130
|
|
|
|
|
|
|
} |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
|
133
|
3
|
|
|
|
|
6
|
$last_response = $response; |
134
|
|
|
|
|
|
|
|
135
|
3
|
50
|
|
|
|
14
|
if($response->{status} eq "REPLAYED_REQUEST") { |
136
|
|
|
|
|
|
|
#Replayed request, wait for next. |
137
|
0
|
|
|
|
|
0
|
$inner_var->end(); |
138
|
|
|
|
|
|
|
} else { |
139
|
|
|
|
|
|
|
#Definitive response, return it. |
140
|
3
|
50
|
|
|
|
12
|
if($response->{status} eq "OK") { |
141
|
0
|
0
|
|
|
|
0
|
$inner_var->croak("Response nonce does not match!") if(! $nonce eq $response->{nonce}); |
142
|
0
|
0
|
|
|
|
0
|
$inner_var->croak("Response OTP does not match!") if(! $otp eq $response->{otp}); |
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
|
145
|
3
|
|
|
|
|
25
|
$inner_var->send($response); |
146
|
|
|
|
|
|
|
} |
147
|
21
|
|
|
|
|
448
|
})); |
148
|
|
|
|
|
|
|
} |
149
|
|
|
|
|
|
|
|
150
|
5
|
|
|
|
|
21053
|
return $result_var; |
151
|
|
|
|
|
|
|
}; |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
# Signs a parameter hash using the client API key. |
154
|
|
|
|
|
|
|
sub sign { |
155
|
8
|
|
|
8
|
0
|
7642
|
my ($self, $params) = @_; |
156
|
8
|
|
|
|
|
18
|
my $content = ""; |
157
|
|
|
|
|
|
|
|
158
|
8
|
|
|
|
|
135
|
foreach my $key (sort keys %$params) { |
159
|
23
|
|
|
|
|
73
|
$content = $content."&$key=$params->{$key}"; |
160
|
|
|
|
|
|
|
} |
161
|
8
|
|
|
|
|
26
|
$content = substr($content, 1); |
162
|
|
|
|
|
|
|
|
163
|
8
|
|
|
|
|
55
|
my $key = decode_base64($self->{api_key}); |
164
|
8
|
|
|
|
|
50
|
my $signature = encode_base64(hmac_sha1($content, $key), ''); |
165
|
|
|
|
|
|
|
|
166
|
8
|
|
|
|
|
302
|
return $signature; |
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
# Parses a response body into a hash. |
170
|
|
|
|
|
|
|
sub parse_response { |
171
|
3
|
|
|
3
|
0
|
34
|
my $body = shift; |
172
|
3
|
|
|
|
|
9
|
my $response = {}; |
173
|
|
|
|
|
|
|
|
174
|
3
|
50
|
|
|
|
18
|
if($body) { |
175
|
3
|
|
|
|
|
20
|
my @lines = split(' ', $body); |
176
|
3
|
|
|
|
|
11
|
foreach my $line (@lines) { |
177
|
11
|
|
|
|
|
20
|
my $index = index($line, '='); |
178
|
11
|
|
|
|
|
103
|
$response->{substr($line, 0, $index)} = substr($line, $index+1); |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
|
182
|
3
|
|
|
|
|
10
|
return $response; |
183
|
|
|
|
|
|
|
} |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
1; |
186
|
|
|
|
|
|
|
__END__ |