line
stmt
bran
cond
sub
pod
time
code
1
package WWW::HKP;
2
3
2
2
29195
use warnings;
2
4
2
114
4
2
2
13
use strict;
2
4
2
96
5
2
2
12
use Carp;
2
8
2
208
6
2
2
56
use 5.010;
2
7
2
188
7
8
2
2
12290
use LWP::UserAgent 6.05;
2
171063
2
81
9
2
2
27
use URI 1.60;
2
37
2
53
10
2
2
11
use URI::Escape 3.31;
2
38
2
3205
11
12
=head1 NAME
13
14
WWW::HKP - Interface to HTTP Keyserver Protocol (HKP)
15
16
=head1 VERSION
17
18
Version 0.02
19
20
=cut
21
22
our $VERSION = '0.02';
23
24
25
=head1 SYNOPSIS
26
27
use WWW::HKP;
28
29
my $hkp = WWW::HKP->new();
30
31
$hkp->query(index => 'foo@bar.baz');
32
$hkp->query(get => 'DEADBEEF');
33
34
=head1 DESCRIPTION
35
36
This module implements the IETF draft of the OpenPGP HTTP Keyserver Protocol.
37
38
More Informationen about HKP is available at L.
39
40
=head1 FUNCTIONS
41
42
=head2 new([%options])
43
44
The C constructor method instanciates a new C object. The following example shows available options and its default values.
45
46
my $hkp = WWW::HKP->new(
47
host => 'localhost',
48
port => 11371
49
);
50
51
In most cases you just need to set the I parameter:
52
53
my $hkp = new WWW::HKP host => 'pool.sks-keyservers.net';
54
55
=cut
56
57
sub new($;%) {
58
2
2
1
731
my ($class, %options) = @_;
59
60
2
40
my $uri = URI->new('http:');
61
2
50
25581
$uri->host($options{host} || 'localhost');
62
2
50
594
$uri->port($options{port} || 11371);
63
64
2
139
my $ua = LWP::UserAgent->new;
65
2
8023
$ua->agent(__PACKAGE__.'/'.$VERSION);
66
67
2
163
my $self = {
68
ua => $ua,
69
uri => $uri,
70
};
71
72
2
33
47
return bless $self => (ref $class || $class);
73
}
74
75
0
0
0
sub _ua($) { shift->{ua} }
76
0
0
0
sub _uri($) { shift->{uri} }
77
78
sub _get($$) {
79
0
0
0
my ($self, %query) = @_;
80
0
0
$self->{error} = undef;
81
0
0
$self->_uri->path('/pks/lookup');
82
0
0
$self->_uri->query_form(%query);
83
0
0
my $response = $self->_ua->get($self->_uri);
84
0
0
0
0
if (defined $response and ref $response and $response->isa('HTTP::Response') and $response->is_success) {
0
0
85
0
0
return $response->decoded_content;
86
} else {
87
0
0
$self->{error} = $response->status_line;
88
0
0
return undef;
89
}
90
}
91
92
sub _post($%) {
93
0
0
0
my ($self, %query) = @_;
94
0
0
$self->{error} = undef;
95
0
0
$self->_uri->path('/pks/lookup');
96
0
0
my $response = $self->_ua->post($self->_uri, \%query);
97
0
0
0
0
if (defined $response and ref $response and $response->isa('HTTP::Response') and $response->is_success) {
0
0
98
0
0
return $response->decoded_content;
99
} else {
100
0
0
$self->{error} = $response->status_line;
101
0
0
return undef;
102
}
103
104
}
105
106
sub _parse_mr($$$) {
107
3
3
2501
my ($self, $lines, $filter_ok) = @_;
108
3
5
my $keys = {};
109
3
5
my $key;
110
3
5
my ($keyc, $keyn) = (0, 0);
111
3
24
foreach my $line (split /\r?\n/ => $lines) {
112
10
100
71
if ($line =~ /^info:(\d+):(\d+)$/) {
100
50
113
3
50
12
croak "unsupported hkp version: v$1" unless $1 == 1;
114
3
8
$keyc = $2;
115
} elsif ($line =~ /^pub:([0-9a-f]{8,16}):(\d*):(\d*):(\d*):(\d*):([der]*)$/i) {
116
3
4
$key = $1;
117
3
4
$keyn++;
118
3
13
my ($algo, $keylen, $created, $expires, $flags, $ok) = ($2, $3, $4, $5, $6, undef);
119
3
100
66
57
$ok = ((($created and $created > time) or ($expires and $expires < time) or (length $flags)) ? 0 : 1);
120
3
50
66
12
if ($filter_ok and !$ok) {
121
0
0
$key = undef;
122
0
0
next;
123
}
124
3
100
50
38
$keys->{$key} = {
100
50
100
125
algo => $algo,
126
keylen => $keylen,
127
created => $created || undef,
128
expires => $expires || undef,
129
revoked => ($flags =~ /r/ ? 1 : 0),
130
expired => ($flags =~ /e/ ? 1 : 0),
131
deleted => ($flags =~ /d/ ? 1 : 0),
132
ok => $ok,
133
uids => []
134
};
135
} elsif ($line =~ /^uid:([^:]*):(\d*):(\d*):([der]*)$/i) {
136
4
50
8
next unless defined $key;
137
4
12
my ($uid, $created, $expires, $flags, $ok) = ($1, $2, $3, $4, undef);
138
4
100
100
44
$ok = ((($created and $created > time) or ($expires and $expires < time) or (length $flags)) ? 0 : 1);
139
4
100
100
22
next if $filter_ok and !$ok;
140
2
100
50
3
push @{ $keys->{$key}->{uids} } => {
2
100
50
11
100
141
uid => uri_unescape($uid),
142
created => $created || undef,
143
expires => $expires || undef,
144
revoked => ($flags =~ /r/ ? 1 : 0),
145
expired => ($flags =~ /e/ ? 1 : 0),
146
deleted => ($flags =~ /d/ ? 1 : 0),
147
ok => $ok
148
};
149
} else {
150
0
0
carp "unknown line: $line";
151
}
152
}
153
3
50
48
carp "server said there where $keyc keys, but $keyn keys parsed" unless $keyc == $keyn;
154
3
15
return $keys;
155
}
156
157
=head2 query($type => $search [, %options ])
158
159
The C method implements both query operations of HKP: I and I
160
161
=cut
162
163
sub query($$$;%) {
164
0
0
1
my ($self, $type, $search, %options) = @_;
165
0
given ($type) {
166
167
=head3 I operation
168
169
$hkp->query(index => 'foo@bar.baz');
170
171
The first parameter must be I, the secondend parameter an email-address or key-id.
172
173
If any keys where found, a hashref is returned. Otherwise returns undef, an error message can be fetched with C<< $hkp->error() >>.
174
175
The returned hashref may look like this:
176
177
{
178
'DEADBEEF' => {
179
'algo' => '1',
180
'keylen' => '2048',
181
'created' => '1253025510',
182
'expires' => '1399901151',
183
'deleted' => 0,
184
'expired' => 0,
185
'revoked' => 0,
186
'ok' => 1,
187
'uids' => [
188
{
189
'uid' => 'Lorem Ipsum (This is an example) '
190
'created' => '1253025510',
191
'expires' => '1399901151',
192
'deleted' => 0,
193
'expired' => 0,
194
'revoked' => 0,
195
'ok' => 1
196
}
197
]
198
}
199
}
200
201
The keys of the hashref are key-ids. The meaning of the hashkeys in the seconded level:
202
203
=over
204
205
=item I
206
207
The algorithm of the key. The values are described in RFC 2440
208
209
=item I
210
211
The key length in bytes
212
213
=item I
214
215
Creation date of the key, in seconds since 1970-01-01 UTC.
216
217
=item I
218
219
Expiration date of the key
220
221
=item I, I, I
222
223
Indication details, whether the key is deleted, expired or revoked. If the flag is that, the value is C<1>, otherwise C<0>.
224
225
=item I
226
227
The creation date and expiration date is checked against C. If it doesn't match or any of the flags obove are set, I will be C<0>, otherwise C<1>.
228
229
=item I
230
231
A arrayref of user-ids.
232
233
=over
234
235
=item I
236
237
The user-id in common format. It can be parsed by L for example.
238
239
=item I, I, I, I, I, I
240
241
This fields have the same meaning as described above. The information is taken from the self-signature, if any. I and I may be C if not available (e.g. empty string).
242
243
=back
244
245
=back
246
247
=head4 Available options
248
249
=over
250
251
=item I
252
253
Set the I parameter to C<1> (or any expression that evaluates to true), if you want an exact match of your search expression.
254
255
=item I
256
257
Set the I parameter to C<1> (or any expression that evaluates to true), if you want only valid results. All keys or user IDs having I-parameter of C<0> are ignored.
258
259
$hkp->query(index => 'foo@bar.baz', filter_ok => 1);
260
261
=back
262
263
=cut
264
265
0
when ('index') {
266
0
my @options = qw(mr);
267
0
0
push @options => 'exact' if $options{exact};
268
0
my $message = $self->_get(op => 'index', options => join(',' => @options), search => $search);
269
0
0
return undef unless defined $message;
270
0
0
return $self->_parse_mr($message, $options{filter_ok} ? 1 : 0);
271
}
272
273
=head3 I operation
274
275
$hkp->query(get => 'DEADBEEF');
276
277
The operation returns the public key of specified key-id or undef, if not found. Any error messages can be fetched with C<< $hkp->error() >>.
278
279
=cut
280
281
0
when ('get') {
282
0
0
if ($search !~ /^0x/) {
283
0
$search = '0x'.$search;
284
}
285
0
my $message = $self->_get(op => 'get', options => 'exact', search => $search);
286
0
0
return undef unless defined $message;
287
0
return $message;
288
}
289
290
=head3 unimplemented operations
291
292
A HKP server may implement various other operations. Unimplemented operation cause the module to die with a stack trace.
293
294
=cut
295
296
0
default {
297
0
confess "unknown query type '$type'";
298
}
299
}
300
}
301
302
=head2 submit
303
304
Submit one or more ASCII-armored version of public keys to the server.
305
306
$pubkey = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...";
307
308
$hkp->submit($pubkey);
309
310
@pubkeys = ($pubkey1, $pubkey2, ...);
311
312
$hkp->submit(@pubkeys);
313
314
In case of success, C<1> is returned. Otherweise C<0> and an error message can be fetched from C<$hkp->error>.
315
316
=cut
317
318
sub submit($@) {
319
0
0
1
my ($self, @keys) = @_;
320
0
my $status = $self->_post(map {( keytext => $_ )} @keys);
0
321
0
0
return (defined $status and $status ? 1 : 0);
322
}
323
324
=head2 error
325
326
Returns last error message, if any.
327
328
$hkp->error; # "404 Not found", for example.
329
330
=cut
331
332
0
0
1
sub error($) { shift->{error} }
333
334
=head1 AUTHOR
335
336
David Zurborg, C<< >>
337
338
=head1 BUGS
339
340
Please report any bugs or feature requests trough my project management tool at L. I will be notified, and then you'll
341
automatically be notified of progress on your bug as I make changes.
342
343
=head1 SUPPORT
344
345
You can find documentation for this module with the perldoc command.
346
347
perldoc WWW::HKP
348
349
You can also look for information at:
350
351
=over 4
352
353
=item * Redmine: Homepage of this module
354
355
L
356
357
=item * RT: CPAN's request tracker
358
359
L
360
361
=item * AnnoCPAN: Annotated CPAN documentation
362
363
L
364
365
=item * CPAN Ratings
366
367
L
368
369
=item * Search CPAN
370
371
L
372
373
=back
374
375
=head1 COPYRIGHT & LICENSE
376
377
Copyright 2014 David Zurborg, all rights reserved.
378
379
This program is not really free software; you can redistribute it and/or modify it under certain circumstances. See file F for details.
380
381
=cut
382
383
1; # End of WWW::HKP