line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package UID2::Client; |
2
|
3
|
|
|
3
|
|
150646
|
use strict; |
|
3
|
|
|
|
|
22
|
|
|
3
|
|
|
|
|
69
|
|
3
|
3
|
|
|
3
|
|
13
|
use warnings; |
|
3
|
|
|
|
|
3
|
|
|
3
|
|
|
|
|
126
|
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
our $VERSION = '0.01'; |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
use Class::Accessor::Lite ( |
8
|
3
|
|
|
|
|
17
|
rw => [qw(endpoint auth_key secret_key identity_scope http keys)], |
9
|
3
|
|
|
3
|
|
1132
|
); |
|
3
|
|
|
|
|
3058
|
|
10
|
|
|
|
|
|
|
|
11
|
3
|
|
|
3
|
|
313
|
use Carp; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
124
|
|
12
|
3
|
|
|
3
|
|
1755
|
use HTTP::Tiny; |
|
3
|
|
|
|
|
121477
|
|
|
3
|
|
|
|
|
97
|
|
13
|
3
|
|
|
3
|
|
948
|
use JSON; |
|
3
|
|
|
|
|
6869
|
|
|
3
|
|
|
|
|
17
|
|
14
|
3
|
|
|
3
|
|
691
|
use Crypt::PRNG qw(random_bytes); |
|
3
|
|
|
|
|
2457
|
|
|
3
|
|
|
|
|
123
|
|
15
|
3
|
|
|
3
|
|
412
|
use Crypt::Misc qw(encode_b64 decode_b64); |
|
3
|
|
|
|
|
6136
|
|
|
3
|
|
|
|
|
125
|
|
16
|
|
|
|
|
|
|
|
17
|
3
|
|
|
3
|
|
1141
|
use UID2::Client::Decryption; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
77
|
|
18
|
3
|
|
|
3
|
|
982
|
use UID2::Client::Key; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
115
|
|
19
|
3
|
|
|
3
|
|
936
|
use UID2::Client::KeyContainer; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
72
|
|
20
|
3
|
|
|
3
|
|
15
|
use UID2::Client::Timestamp; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
47
|
|
21
|
3
|
|
|
3
|
|
1002
|
use UID2::Client::IdentityScope; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
2507
|
|
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
sub new { |
24
|
59
|
|
|
59
|
1
|
83156
|
my ($class, $options) = @_; |
25
|
59
|
|
33
|
|
|
147
|
my $secret_key = $options->{secret_key} // croak 'secret_key required'; |
26
|
59
|
|
|
|
|
202
|
$secret_key = decode_b64($secret_key); |
27
|
59
|
|
|
|
|
68
|
my $http = do { |
28
|
59
|
50
|
33
|
|
|
224
|
if ($options->{http_options} && $options->{http}) { |
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
29
|
0
|
|
|
|
|
0
|
croak 'only one of http_options or http can be specified'; |
30
|
|
|
|
|
|
|
} elsif ($options->{http_options}) { |
31
|
0
|
|
|
|
|
0
|
HTTP::Tiny->new(%{$options->{http_options}}); |
|
0
|
|
|
|
|
0
|
|
32
|
|
|
|
|
|
|
} elsif ($options->{http}) { |
33
|
40
|
|
|
|
|
64
|
$options->{http}; |
34
|
|
|
|
|
|
|
} else { |
35
|
19
|
|
|
|
|
66
|
HTTP::Tiny->new; |
36
|
|
|
|
|
|
|
} |
37
|
|
|
|
|
|
|
}; |
38
|
|
|
|
|
|
|
bless { |
39
|
|
|
|
|
|
|
endpoint => $options->{endpoint} // croak('endpoint required'), |
40
|
|
|
|
|
|
|
auth_key => $options->{auth_key} // croak('auth_key required'), |
41
|
|
|
|
|
|
|
secret_key => $secret_key, |
42
|
59
|
|
33
|
|
|
1615
|
identity_scope => $options->{identity_scope} // UID2::Client::IdentityScope::UID2, |
|
|
|
33
|
|
|
|
|
|
|
|
50
|
|
|
|
|
43
|
|
|
|
|
|
|
http => $http, |
44
|
|
|
|
|
|
|
keys => undef, |
45
|
|
|
|
|
|
|
}, $class; |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
sub new_euid { |
49
|
0
|
|
|
0
|
1
|
0
|
my ($class, $options) = @_; |
50
|
0
|
|
|
|
|
0
|
$class->new({ %$options, identity_scope => UID2::Client::IdentityScope::EUID }); |
51
|
|
|
|
|
|
|
} |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
sub refresh { |
54
|
40
|
|
|
40
|
1
|
939
|
my $self = shift; |
55
|
40
|
|
|
|
|
54
|
eval { |
56
|
40
|
|
|
|
|
59
|
$self->keys(_parse_json($self->get_latest_keys)); |
57
|
40
|
50
|
|
|
|
211
|
}; if ($@) { |
58
|
0
|
|
|
|
|
0
|
return { is_success => undef, reason => $@ }; |
59
|
|
|
|
|
|
|
} |
60
|
40
|
|
|
|
|
84
|
+{ is_success => 1 }; |
61
|
|
|
|
|
|
|
} |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
sub refresh_json { |
64
|
14
|
|
|
14
|
1
|
2879
|
my ($self, $json) = @_; |
65
|
14
|
|
|
|
|
21
|
eval { |
66
|
14
|
|
|
|
|
27
|
$self->keys(_parse_json($json)); |
67
|
14
|
50
|
|
|
|
81
|
}; if ($@) { |
68
|
0
|
|
|
|
|
0
|
return { is_success => undef, reason => $@ }; |
69
|
|
|
|
|
|
|
} |
70
|
14
|
|
|
|
|
32
|
+{ is_success => 1 }; |
71
|
|
|
|
|
|
|
} |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
my $V2_NONCE_LEN = 8; |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
sub get_latest_keys { |
76
|
40
|
|
|
40
|
1
|
45
|
my $self = shift; |
77
|
40
|
|
|
|
|
85
|
my $nonce = random_bytes($V2_NONCE_LEN); |
78
|
40
|
|
|
|
|
451
|
my $res = $self->http->post($self->endpoint . '/v2/key/latest', { |
79
|
|
|
|
|
|
|
headers => { |
80
|
|
|
|
|
|
|
'Authorization' => 'Bearer ' . $self->auth_key, |
81
|
|
|
|
|
|
|
'Content-Type' => 'text/plain', |
82
|
|
|
|
|
|
|
}, |
83
|
|
|
|
|
|
|
content => $self->_make_v2_request($nonce), |
84
|
|
|
|
|
|
|
}); |
85
|
40
|
50
|
|
|
|
386
|
unless ($res->{success}) { |
86
|
0
|
0
|
|
|
|
0
|
if ($res->{status} == 599) { |
87
|
0
|
|
|
|
|
0
|
chomp(my $content = $res->{content}); |
88
|
0
|
|
|
|
|
0
|
croak $content; |
89
|
|
|
|
|
|
|
} else { |
90
|
0
|
|
|
|
|
0
|
croak "$res->{status} $res->{reason}"; |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
} |
93
|
40
|
|
|
|
|
79
|
$self->_parse_v2_response($res->{content}, $nonce); |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
sub _make_v2_request { |
97
|
40
|
|
|
40
|
|
389
|
my ($self, $nonce, $now) = @_; |
98
|
40
|
|
33
|
|
|
153
|
$now //= UID2::Client::Timestamp->now; |
99
|
40
|
|
|
|
|
78
|
my $data = pack 'q> a*', $now->get_epoch_milli, $nonce; |
100
|
40
|
|
|
|
|
83
|
my $payload = UID2::Client::Decryption::encrypt_gcm($data, $self->secret_key), |
101
|
|
|
|
|
|
|
my $version = 1; |
102
|
40
|
|
|
|
|
117
|
my $envelope = pack 'C a*', $version, $payload; |
103
|
40
|
|
|
|
|
287
|
encode_b64($envelope); |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
sub _parse_v2_response { |
107
|
40
|
|
|
40
|
|
68
|
my ($self, $envelope, $nonce) = @_; |
108
|
40
|
|
|
|
|
201
|
my $envelope_bytes = decode_b64($envelope); |
109
|
40
|
|
|
|
|
86
|
my $payload = UID2::Client::Decryption::decrypt_gcm($envelope_bytes, $self->secret_key); |
110
|
40
|
50
|
|
|
|
88
|
if (length($payload) < 16) { |
111
|
0
|
|
|
|
|
0
|
croak 'invalid payload'; |
112
|
|
|
|
|
|
|
} |
113
|
40
|
|
|
|
|
135
|
my ($res_nonce, $data) = unpack "x8 a${V2_NONCE_LEN} a*", $payload; |
114
|
40
|
50
|
|
|
|
73
|
if ($res_nonce ne $nonce) { |
115
|
0
|
|
|
|
|
0
|
croak 'nonce mismatch'; |
116
|
|
|
|
|
|
|
} |
117
|
40
|
|
|
|
|
125
|
$data; |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
sub _parse_json { |
121
|
54
|
|
|
54
|
|
89
|
my $content = shift; |
122
|
54
|
|
|
|
|
449
|
my $obj = decode_json($content); |
123
|
54
|
|
|
|
|
76
|
my @keys; |
124
|
54
|
|
|
|
|
58
|
for my $entry (@{$obj->{body}}) { |
|
54
|
|
|
|
|
113
|
|
125
|
132
|
|
|
|
|
855
|
$entry->{secret} = decode_b64($entry->{secret}); |
126
|
132
|
|
|
|
|
265
|
push @keys, UID2::Client::Key->new($entry); |
127
|
|
|
|
|
|
|
} |
128
|
54
|
|
|
|
|
531
|
UID2::Client::KeyContainer->new(@keys); |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
sub decrypt { |
132
|
18
|
|
|
18
|
1
|
925
|
my ($self, $token, $now) = @_; |
133
|
18
|
|
|
|
|
40
|
UID2::Client::Decryption::decrypt_token($token, $now, $self->keys, $self->identity_scope); |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
sub encrypt_data { |
137
|
31
|
|
|
31
|
1
|
1245
|
my ($self, $data, $request) = @_; |
138
|
31
|
|
|
|
|
60
|
$request->{identity_scope} = $self->identity_scope; |
139
|
31
|
100
|
|
|
|
138
|
$request->{keys} = $self->keys unless $request->{key}; |
140
|
31
|
|
|
|
|
103
|
UID2::Client::Decryption::encrypt_data($data, $request); |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
sub decrypt_data { |
144
|
30
|
|
|
30
|
1
|
6975
|
my ($self, $data) = @_; |
145
|
30
|
|
|
|
|
62
|
UID2::Client::Decryption::decrypt_data($data, $self->keys, $self->identity_scope); |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
1; |
149
|
|
|
|
|
|
|
__END__ |