File Coverage

blib/lib/SRS/EPP/OpenPGP.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1              
2             package SRS::EPP::OpenPGP;
3             {
4             $SRS::EPP::OpenPGP::VERSION = '0.22';
5             }
6              
7 2     2   128902 use 5.010;
  2         8  
  2         82  
8 2     2   847 use Moose;
  2         560710  
  2         19  
9 2     2   16786 use MooseX::Params::Validate;
  2         13710  
  2         21  
10 2     2   836 use Moose::Util::TypeConstraints;
  2         5  
  2         21  
11 2     2   11840 use Crypt::OpenPGP;
  0            
  0            
12             use Crypt::OpenPGP::KeyRing;
13             use Carp;
14              
15             with 'MooseX::Log::Log4perl';
16              
17             BEGIN {
18             class_type "Crypt::OpenPGP::KeyRing";
19             class_type "Crypt::OpenPGP::KeyBlock";
20             class_type "Crypt::OpenPGP::Certificate";
21             }
22              
23             # Crypt::OpenPGP setup.
24             has 'pgp' =>
25             is => "ro",
26             isa => "Crypt::OpenPGP",
27             lazy => 1,
28             default => sub {
29             my $self = shift;
30             Crypt::OpenPGP->new(
31             (
32             $self->_has_secret_keyring
33             ? (SecRing => $self->secret_keyring)
34             : ()
35             ),
36             (
37             $self->_has_public_keyring
38             ? (PubRing => $self->public_keyring)
39             : ()
40             ),
41             );
42             },
43             ;
44              
45             coerce "Crypt::OpenPGP::KeyRing"
46             => from "Str"
47             => via {
48             Crypt::OpenPGP::KeyRing->new(
49             Filename => $_,
50             );
51             };
52              
53             has 'secret_keyring' =>
54             is => "ro",
55             isa => "Crypt::OpenPGP::KeyRing",
56             lazy => 1,
57             predicate => "_has_secret_keyring",
58             coerce => 1,
59             default => sub {
60             my $self = shift;
61             $self->pgp->{cfg}->get("SecRing");
62             },
63             ;
64             has 'public_keyring' =>
65             is => "ro",
66             isa => "Crypt::OpenPGP::KeyRing",
67             lazy => 1,
68             predicate => "_has_public_keyring",
69             coerce => 1,
70             default => sub {
71             my $self = shift;
72             $self->pgp->{cfg}->get("PubRing");
73             },
74             ;
75              
76              
77             # specifying the default signing/encryption key
78              
79             BEGIN {
80             subtype "SRS::EPP::OpenPGP::key_id"
81             => as "Str",
82             => where {
83             m{^(?:0x)?(?:(?:[0-9a-f]{4}\s?){2}){1,2}$}i;
84             };
85             }
86              
87             has 'uid' =>
88             is => "rw",
89             isa => "SRS::EPP::OpenPGP::key_id",
90             trigger => sub {
91             my $self = shift;
92             my $uid = shift;
93             $self->default_signing_key(
94             $self->find_signing_key($uid)
95             );
96             $self->default_encrypting_key(
97             $self->find_signing_key($uid)
98             );
99             }
100             ;
101              
102             has 'passphrase' =>
103             is => "rw",
104             isa => "Str",
105             ;
106              
107             sub unlock_cert {
108             my $self = shift;
109            
110             my ( $cert ) = pos_validated_list(
111             \@_,
112             { isa => 'Crypt::OpenPGP::Certificate' },
113             );
114            
115             return unless $cert->is_protected;
116              
117             return if $self->passphrase and
118             $cert->unlock($self->passphrase);
119              
120             my $key_id = $cert->fingerprint_hex;
121             require Scriptalicious;
122              
123             unless (-t STDIN) {
124             $self->logger->fatal("no terminal");
125             die "no terminal";
126             }
127              
128             $self->passphrase(
129             Scriptalicious::prompt_passwd(
130             "Enter passphrase for PGP cert $key_id:"
131             ),
132             );
133             print "\n"; # workaround bug in Scriptalicious..
134              
135             return $self->unlock_cert($cert);
136             }
137              
138             has 'default_signing_key' => (
139             is => "rw",
140             lazy => 1,
141             default => sub {
142             my $self = shift;
143             my $sec_ring = $self->secret_keyring;
144             my $kb = $self->get_sec_key_block
145             or die "no secret key block";
146             my $cert = $kb->signing_key
147             or croak "Invalid default secret key; specify pgp_keyid in config";
148             $self->unlock_cert($cert);
149             $cert->uid($kb->primary_uid);
150             $cert;
151             },
152             );
153              
154             has 'default_encrypting_key' =>
155             is => "rw",
156             ;
157              
158             sub find_signing_key {
159             my $self = shift;
160            
161             my ( $key_id ) = pos_validated_list(
162             \@_,
163             { isa => 'SRS::EPP::OpenPGP::key_id' },
164             );
165            
166             my $kb = $self->get_sec_key_block($key_id) or return;
167             my $cert = $kb->signing_key
168             or croak "Invalid signing key $key_id";
169             $self->unlock_cert($cert);
170             $cert->uid($kb->primary_uid);
171             return $cert;
172             }
173              
174             sub find_encrypting_key {
175             my $self = shift;
176            
177             my ( $key_id ) = pos_validated_list(
178             \@_,
179             { isa => 'SRS::EPP::OpenPGP::key_id' },
180             );
181            
182             my $kb = $self->get_sec_key_block($key_id) or return;
183             my $cert = $kb->encrypting_key
184             or croak "Invalid encrypting key $key_id";
185             $self->unlock_cert($cert);
186             $cert->uid($kb->primary_uid);
187             return $cert;
188             }
189              
190             sub get_sec_key_block {
191             my $self = shift;
192            
193             my ( $key_id ) = pos_validated_list(
194             \@_,
195             { isa => 'SRS::EPP::OpenPGP::key_id', optional => 1 },
196             );
197            
198             my $sec_ring = $self->secret_keyring;
199              
200             my $func = sub{$sec_ring->find_keyblock_by_index(@_)};
201             my $param = -1;
202             my $label = "default";
203             if ($key_id) {
204             $key_id =~ s{^0x}{};
205             $func = sub{$sec_ring->find_keyblock_by_keyid(@_)};
206             $param = pack("H*", $key_id);
207             $label = $key_id;
208             }
209              
210             my $kb = $func->($param)
211             or croak "Can't find keyblock ($label): " . $sec_ring->errstr;
212             return $kb;
213             }
214              
215             sub get_pub_key_block {
216             my $self = shift;
217            
218             my ( $key_id ) = pos_validated_list(
219             \@_,
220             { isa => 'SRS::EPP::OpenPGP::key_id', optional => 1 },
221             );
222            
223             my $pub_ring = $self->public_keyring;
224             $key_id =~ s{^0x}{};
225             my $kb = $key_id
226             ? $pub_ring->find_keyblock_by_keyid( pack("H*", $key_id) )
227             : $pub_ring->find_keyblock_by_index(-1)
228             or croak "Can't find keyblock ("
229             .($key_id ? $key_id : "default")
230             ."): " . $pub_ring->errstr;
231             return $kb;
232             }
233              
234             sub get_cert_from_key_text{
235             my $self = shift;
236            
237             my ( $key_text ) = pos_validated_list(
238             \@_,
239             { isa => 'Str' },
240             );
241            
242             my $kr = new Crypt::OpenPGP::KeyRing(Data => $key_text)
243             or return;
244             my $kb = $kr->find_keyblock_by_index(-1)
245             or return;
246             my $cert = $kb->signing_key
247             or return;
248             $cert->uid($kb->primary_uid);
249             $cert;
250             }
251              
252             use Encode;
253             use utf8;
254              
255             sub byte_string {
256             if ( utf8::is_utf8($_[0]) ) {
257             encode("utf8", $_[0]);
258             }
259             else {
260             $_[0];
261             }
262             }
263              
264             sub verify_detached {
265             my $self = shift;
266            
267             my ( $data, $signature, $cert, $key_text ) = validated_list(
268             \@_,
269             data => { isa => 'Str' },
270             signature => { isa => 'Str' },
271             cert => { optional => 1 },
272             key_text => { optional => 1 },
273             );
274            
275             if ($key_text) {
276             $cert ||= $self->get_cert_from_key_text($key_text);
277             }
278             my $pgp = $self->pgp;
279             my $res = $pgp->verify(
280             Data => byte_string($data),
281             Signature => $signature,
282             ( $cert ? (Key => $cert) : () ),
283             );
284             if ($res) {
285             my $res_neg = $pgp->verify(
286             Data => "xx.$$.".rand(3),
287             Signature => $signature,
288             ( $cert ? (Key => $cert) : () ),
289             );
290             if ( $res and $res_neg ) {
291              
292             # a full doc was passed in as a signature...
293             $res = 0;
294             }
295             }
296             warn $pgp->errstr if !$res && $pgp->errstr;
297             return $res;
298             }
299              
300             sub detached_sign {
301             my $self = shift;
302            
303             my ( $data, $key, $passphrase ) = pos_validated_list(
304             \@_,
305             { isa => 'Str' },
306             { optional => 1 },
307             { optional => 1 },
308             );
309            
310            
311             $key ||= $self->default_signing_key;
312             my $pgp = $self->pgp;
313             my $signature = $pgp->sign(
314             Data => byte_string($data),
315             Detach => 1,
316             Armour => 1,
317             Digest => "SHA1",
318             Passphrase => $passphrase//"",
319             Key => $key,
320             );
321              
322             carp "Signing attempt failed: ", $pgp->errstr() unless $signature;
323             return $signature;
324             }
325              
326             1;