File Coverage

blib/lib/Crypt/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             package Crypt::OpenPGP;
2 6     6   5033 use strict;
  6         12  
  6         228  
3 6     6   156 use 5.008_001;
  6         23  
  6         235  
4              
5 6     6   35 use vars qw( $VERSION );
  6         15  
  6         660  
6             $VERSION = '1.07';
7              
8 6     6   4844 use Crypt::OpenPGP::Constants qw( DEFAULT_CIPHER );
  6         16  
  6         45  
9 6     6   4152 use Crypt::OpenPGP::KeyRing;
  0            
  0            
10             use Crypt::OpenPGP::Plaintext;
11             use Crypt::OpenPGP::Message;
12             use Crypt::OpenPGP::PacketFactory;
13             use Crypt::OpenPGP::Config;
14              
15             use Crypt::OpenPGP::ErrorHandler;
16             use base qw( Crypt::OpenPGP::ErrorHandler );
17              
18             use File::HomeDir;
19             use File::Spec;
20              
21             use vars qw( %COMPAT );
22              
23             ## pgp2 and pgp5 do not trim trailing whitespace from "canonical text"
24             ## signatures, only from cleartext signatures.
25             ## See:
26             ## http://cert.uni-stuttgart.de/archive/ietf-openpgp/2000/01/msg00033.html
27             $Crypt::OpenPGP::Globals::Trim_trailing_ws = 1;
28              
29             {
30             my $env = sub {
31             my $dir = shift; my @paths;
32             if (exists $ENV{$dir}) { for (@_) { push @paths, "$ENV{$dir}/$_" } }
33             return @paths ? @paths : ();
34             };
35              
36             my $home = sub {
37             my( @path ) = @_;
38             my $home_dir = File::HomeDir->my_home or return;
39             return File::Spec->catfile( $home_dir, @path );
40             };
41              
42             %COMPAT = (
43             PGP2 => {
44             'sign' => { Digest => 'MD5', Version => 3 },
45             'encrypt' => { Cipher => 'IDEA', Compress => 'ZIP' },
46             'keygen' => { Type => 'RSA', Cipher => 'IDEA',
47             Version => 3, Digest => 'MD5' },
48             'PubRing' => [
49             $env->('PGPPATH','pubring.pgp'),
50             $home->( '.pgp', 'pubring.pgp' ),
51             ],
52             'SecRing' => [
53             $env->('PGPPATH','secring.pgp'),
54             $home->( '.pgp', 'secring.pgp' ),
55             ],
56             'Config' => [
57             $env->('PGPPATH', 'config.txt'),
58             $home->( '.pgp', 'config.txt' ),
59             ],
60             },
61              
62             PGP5 => {
63             'sign' => { Digest => 'SHA1', Version => 3 },
64             'encrypt' => { Cipher => 'DES3', Compress => 'ZIP' },
65             'keygen' => { Type => 'DSA', Cipher => 'DES3',
66             Version => 4, Digest => 'SHA1' },
67             'PubRing' => [
68             $env->('PGPPATH','pubring.pkr'),
69             $home->( '.pgp', 'pubring.pkr' ),
70             ],
71             'SecRing' => [
72             $env->('PGPPATH','secring.skr'),
73             $home->( '.pgp', 'secring.skr' ),
74             ],
75             'Config' => [
76             $env->('PGPPATH', 'pgp.cfg'),
77             $home->( '.pgp', 'pgp.cfg' ),
78             ],
79             },
80              
81             GnuPG => {
82             'sign' => { Digest => 'RIPEMD160', Version => 4 },
83             'encrypt' => { Cipher => 'Rijndael', Compress => 'Zlib',
84             MDC => 1 },
85             'keygen' => { Type => 'DSA', Cipher => 'Rijndael',
86             Version => 4, Digest => 'RIPEMD160' },
87             'Config' => [
88             $env->('GNUPGHOME', 'options'),
89             $home->( '.gnupg', 'options' ),
90             ],
91             'PubRing' => [
92             $env->('GNUPGHOME', 'pubring.gpg'),
93             $home->( '.gnupg', 'pubring.gpg' ),
94             ],
95             'SecRing' => [
96             $env->('GNUPGHOME', 'secring.gpg'),
97             $home->( '.gnupg', 'secring.gpg' ),
98             ],
99             },
100             );
101             }
102              
103             sub version_string { __PACKAGE__ . ' ' . $VERSION }
104              
105             sub pubrings { $_[0]->{pubrings} }
106             sub secrings { $_[0]->{secrings} }
107              
108             use constant PUBLIC => 1;
109             use constant SECRET => 2;
110              
111             sub add_ring {
112             my $pgp = shift;
113             my($type, $ring) = @_;
114             unless (ref($ring) eq 'Crypt::OpenPGP::KeyRing') {
115             $ring = Crypt::OpenPGP::KeyRing->new( Filename => $ring )
116             or return Crypt::OpenPGP::KeyRing->errstr;
117             }
118             if ($type == SECRET) {
119             push @{ $pgp->{secrings} }, $ring;
120             } else {
121             push @{ $pgp->{pubrings} }, $ring;
122             }
123             $ring;
124             }
125              
126             sub new {
127             my $class = shift;
128             my $pgp = bless { }, $class;
129             $pgp->init(@_);
130             }
131              
132             sub _first_exists {
133             my($list) = @_;
134             for my $f (@$list) {
135             next unless $f;
136             return $f if -e $f;
137             }
138             }
139              
140             sub init {
141             my $pgp = shift;
142             $pgp->{pubrings} = [];
143             $pgp->{secrings} = [];
144             my %param = @_;
145             my $cfg_file = delete $param{ConfigFile};
146             my $cfg = $pgp->{cfg} = Crypt::OpenPGP::Config->new(%param) or
147             return Crypt::OpenPGP::Config->errstr;
148             if (!$cfg_file && (my $compat = $cfg->get('Compat'))) {
149             $cfg_file = _first_exists($COMPAT{$compat}{Config});
150             }
151             if ($cfg_file) {
152             $cfg->read_config($param{Compat}, $cfg_file);
153             }
154             ## Load public and secret keyrings.
155             for my $s (qw( PubRing SecRing )) {
156             unless (defined $cfg->get($s)) {
157             my @compats = $param{Compat} ? ($param{Compat}) : keys %COMPAT;
158             for my $compat (@compats) {
159             my $ring = _first_exists($COMPAT{$compat}{$s});
160             $cfg->set($s, $ring), last if $ring;
161             }
162             }
163             if (my $ring = $cfg->get($s)) {
164             $pgp->add_ring($s eq 'PubRing' ? PUBLIC : SECRET, $ring);
165             }
166             }
167             $pgp;
168             }
169              
170             sub handle {
171             my $pgp = shift;
172             my %param = @_;
173             my($data);
174             unless ($data = $param{Data}) {
175             my $file = $param{Filename} or
176             return $pgp->error("Need either 'Data' or 'Filename' to decrypt");
177             $data = $pgp->_read_files($file) or return $pgp->error($pgp->errstr);
178             }
179             my $msg = Crypt::OpenPGP::Message->new( Data => $data ) or
180             return $pgp->error("Reading data packets failed: " .
181             Crypt::OpenPGP::Message->errstr);
182             my @pieces = $msg->pieces;
183             return $pgp->error("No packets found in message") unless @pieces;
184             while (ref($pieces[0]) eq 'Crypt::OpenPGP::Marker') {
185             shift @pieces;
186             }
187             if (ref($pieces[0]) eq 'Crypt::OpenPGP::Compressed') {
188             $data = $pieces[0]->decompress or
189             return $pgp->error("Decompression error: " . $pieces[0]->errstr);
190             $msg = Crypt::OpenPGP::Message->new( Data => $data ) or
191             return $pgp->error("Reading decompressed data failed: " .
192             Crypt::OpenPGP::Message->errstr);
193             @pieces = $msg->pieces;
194             }
195             my $class = ref($pieces[0]);
196             my(%res);
197             if ($class eq 'Crypt::OpenPGP::OnePassSig' ||
198             $class eq 'Crypt::OpenPGP::Signature') {
199             my($valid, $sig) = $pgp->verify( Signature => $data );
200             return $pgp->error("Error verifying signature: " . $pgp->errstr)
201             if !defined $valid;
202             $res{Validity} = $valid;
203             $res{Signature} = $sig;
204             } else {
205             my $cb = $param{PassphraseCallback} || \&_default_passphrase_cb;
206             my($pt, $valid, $sig) = $pgp->decrypt(
207             Data => $data,
208             PassphraseCallback => $cb,
209             );
210             return $pgp->error("Decryption failed: " . $pgp->errstr)
211             unless defined $pt;
212             return $pgp->error("Error verifying signature: " . $pgp->errstr)
213             if !defined($valid) && $pgp->errstr !~ /^No Signature/;
214             $res{Plaintext} = $pt;
215             $res{Validity} = $valid if defined $valid;
216             $res{Signature} = $sig if defined $sig;
217             }
218             \%res;
219             }
220              
221             sub _default_passphrase_cb {
222             my($cert) = @_;
223             my $prompt;
224             if ($cert) {
225             $prompt = sprintf qq(
226             You need a passphrase to unlock the secret key for
227             user "%s".
228             %d-bit %s key, ID %s
229              
230             Enter passphrase: ), $cert->uid,
231             $cert->key->size,
232             $cert->key->alg,
233             substr($cert->key_id_hex, -8, 8);
234             } else {
235             $prompt = "Enter passphrase: ";
236             }
237             _prompt($prompt, '', 1);
238             }
239              
240             sub _prompt {
241             my($prompt, $def, $noecho) = @_;
242             require Term::ReadKey;
243             Term::ReadKey->import;
244             print STDERR $prompt . ($def ? "[$def] " : "");
245             if ($noecho) {
246             ReadMode('noecho');
247             }
248             chomp(my $ans = ReadLine(0));
249             ReadMode('restore');
250             print STDERR "\n";
251             $ans ? $ans : $def;
252             }
253              
254             sub sign {
255             my $pgp = shift;
256             my %param = @_;
257             $pgp->_merge_compat(\%param, 'sign') or
258             return $pgp->error( $pgp->errstr );
259             my($cert, $data);
260             require Crypt::OpenPGP::Signature;
261             unless ($data = $param{Data}) {
262             my $file = $param{Filename} or
263             return $pgp->error("Need either 'Data' or 'Filename' to sign");
264             $data = $pgp->_read_files($file) or return $pgp->error($pgp->errstr);
265             }
266             unless ($cert = $param{Key}) {
267             my $kid = $param{KeyID} or return $pgp->error("No KeyID specified");
268             my $ring = $pgp->secrings->[0]
269             or return $pgp->error("No secret keyrings");
270             my $kb = $ring->find_keyblock_by_keyid(pack 'H*', $kid) or
271             return $pgp->error("Could not find secret key with KeyID $kid");
272             $cert = $kb->signing_key;
273             $cert->uid($kb->primary_uid);
274             }
275             if ($cert->is_protected) {
276             my $pass = $param{Passphrase};
277             if (!defined $pass && (my $cb = $param{PassphraseCallback})) {
278             $pass = $cb->($cert);
279             }
280             return $pgp->error("Need passphrase to unlock secret key")
281             unless $pass;
282             $cert->unlock($pass) or
283             return $pgp->error("Secret key unlock failed: " . $cert->errstr);
284             }
285             my @ptarg;
286             push @ptarg, ( Filename => $param{Filename} ) if $param{Filename};
287             if ($param{Clearsign}) {
288             push @ptarg, ( Mode => 't' );
289             ## In clear-signed messages, the line ending before the signature
290             ## is not considered part of the signed text.
291             (my $tmp = $data) =~ s!\r?\n$!!;
292             push @ptarg, ( Data => $tmp );
293             } else {
294             push @ptarg, ( Data => $data );
295             }
296             my $pt = Crypt::OpenPGP::Plaintext->new(@ptarg);
297             my @sigarg;
298             if (my $hash_alg = $param{Digest}) {
299             my $dgst = Crypt::OpenPGP::Digest->new($hash_alg) or
300             return $pgp->error( Crypt::OpenPGP::Digest->errstr );
301             @sigarg = ( Digest => $dgst->alg_id );
302             }
303             push @sigarg, (Type => 0x01) if $param{Clearsign};
304             my $sig = Crypt::OpenPGP::Signature->new(
305             Data => $pt,
306             Key => $cert,
307             Version => $param{Version},
308             @sigarg,
309             );
310             if ($param{Clearsign}) {
311             $param{Armour} = $param{Detach} = 1;
312             }
313             my $sig_data = Crypt::OpenPGP::PacketFactory->save($sig,
314             $param{Detach} ? () : ($pt));
315             if ($param{Armour}) {
316             require Crypt::OpenPGP::Armour;
317             $sig_data = Crypt::OpenPGP::Armour->armour(
318             Data => $sig_data,
319             Object => ($param{Detach} ? 'SIGNATURE' : 'MESSAGE'),
320             ) or return $pgp->error( Crypt::OpenPGP::Armour->errstr );
321             }
322             if ($param{Clearsign}) {
323             require Crypt::OpenPGP::Util;
324             my $hash = Crypt::OpenPGP::Digest->alg($sig->{hash_alg});
325             my $data = Crypt::OpenPGP::Util::dash_escape($data);
326             $data .= "\n" unless $data =~ /\n$/;
327             $sig_data = "-----BEGIN PGP SIGNED MESSAGE-----\n" .
328             ($hash eq 'MD5' ? '' : "Hash: $hash\n") .
329             "\n" .
330             $data .
331             $sig_data;
332             }
333             $sig_data;
334             }
335              
336             sub verify {
337             my $pgp = shift;
338             my %param = @_;
339             my $wants_object = wantarray;
340             my($data, $sig);
341             require Crypt::OpenPGP::Signature;
342             $param{Signature} or $param{SigFile} or
343             return $pgp->error("Need Signature or SigFile to verify");
344             my %arg = $param{Signature} ? (Data => $param{Signature}) :
345             (Filename => $param{SigFile});
346             $arg{IsPacketStream} = 1 if $param{IsPacketStream};
347             my $msg = Crypt::OpenPGP::Message->new( %arg ) or
348             return $pgp->error("Reading signature failed: " .
349             Crypt::OpenPGP::Message->errstr);
350             my @pieces = $msg->pieces;
351             if (ref($pieces[0]) eq 'Crypt::OpenPGP::Compressed') {
352             $data = $pieces[0]->decompress or
353             return $pgp->error("Decompression error: " . $pieces[0]->errstr);
354             $msg = Crypt::OpenPGP::Message->new( Data => $data ) or
355             return $pgp->error("Reading decompressed data failed: " .
356             Crypt::OpenPGP::Message->errstr);
357             @pieces = $msg->pieces;
358             }
359             if (ref($pieces[0]) eq 'Crypt::OpenPGP::OnePassSig') {
360             ($data, $sig) = @pieces[1,2];
361             } elsif (ref($pieces[0]) eq 'Crypt::OpenPGP::Signature') {
362             ($sig, $data) = @pieces[0,1];
363             } else {
364             return $pgp->error("SigFile contents are strange");
365             }
366             unless ($data) {
367             if ($param{Data}) {
368             $data = Crypt::OpenPGP::Plaintext->new( Data => $param{Data} );
369             }
370             else {
371             ## if no Signature or detached sig in SigFile
372             my @files = ref($param{Files}) eq 'ARRAY' ? @{ $param{Files} } :
373             $param{Files};
374             my $fdata = $pgp->_read_files(@files);
375             return $pgp->error("Reading data files failed: " . $pgp->errstr)
376             unless defined $fdata;
377             $data = Crypt::OpenPGP::Plaintext->new( Data => $fdata );
378             }
379             }
380             my($cert, $kb);
381             unless ($cert = $param{Key}) {
382             my $key_id = $sig->key_id;
383             my $ring = $pgp->pubrings->[0];
384             unless ($ring && ($kb = $ring->find_keyblock_by_keyid($key_id))) {
385             my $cfg = $pgp->{cfg};
386             if ($cfg->get('AutoKeyRetrieve') && $cfg->get('KeyServer')) {
387             require Crypt::OpenPGP::KeyServer;
388             my $server = Crypt::OpenPGP::KeyServer->new(
389             Server => $cfg->get('KeyServer'),
390             );
391             $kb = $server->find_keyblock_by_keyid($key_id);
392             }
393             return $pgp->error("Could not find public key with KeyID " .
394             unpack('H*', $key_id))
395             unless $kb;
396             }
397             $cert = $kb->signing_key;
398             }
399              
400             ## pgp2 and pgp5 do not trim trailing whitespace from "canonical text"
401             ## signatures, only from cleartext signatures. So we first try to verify
402             ## the signature using proper RFC4880 canonical text, then if that fails,
403             ## retry without trimming trailing whitespace.
404             ## See:
405             ## http://cert.uni-stuttgart.de/archive/ietf-openpgp/2000/01/msg00033.html
406             my($dgst, $found);
407             for (1, 0) {
408             local $Crypt::OpenPGP::Globals::Trim_trailing_ws = $_;
409             $dgst = $sig->hash_data($data) or
410             return $pgp->error( $sig->errstr );
411             $found++, last if substr($dgst, 0, 2) eq $sig->{chk};
412             }
413             return $pgp->error("Message hash does not match signature checkbytes")
414             unless $found;
415             my $valid = $cert->key->public_key->verify($sig, $dgst) ?
416             ($kb && $kb->primary_uid ? $kb->primary_uid : 1) : 0;
417              
418             $wants_object ? ($valid, $sig) : $valid;
419             }
420              
421             sub encrypt {
422             my $pgp = shift;
423             my %param = @_;
424             $pgp->_merge_compat(\%param, 'encrypt') or
425             return $pgp->error( $pgp->errstr );
426             my($data);
427             require Crypt::OpenPGP::Cipher;
428             require Crypt::OpenPGP::Ciphertext;
429             unless ($data = $param{Data}) {
430             my $file = $param{Filename} or
431             return $pgp->error("Need either 'Data' or 'Filename' to encrypt");
432             $data = $pgp->_read_files($file) or return $pgp->error($pgp->errstr);
433             }
434             my $ptdata;
435             if ($param{SignKeyID}) {
436             $ptdata = $pgp->sign(
437             Data => $data,
438             KeyID => $param{SignKeyID},
439             Compat => $param{Compat},
440             Armour => 0,
441             Passphrase => $param{SignPassphrase},
442             PassphraseCallback => $param{SignPassphraseCallback},
443             )
444             or return;
445             } else {
446             my $pt = Crypt::OpenPGP::Plaintext->new( Data => $data,
447             $param{Filename} ? (Filename => $param{Filename}) : () );
448             $ptdata = Crypt::OpenPGP::PacketFactory->save($pt);
449             }
450             if (my $alg = $param{Compress}) {
451             require Crypt::OpenPGP::Compressed;
452             $alg = Crypt::OpenPGP::Compressed->alg_id($alg);
453             my $cdata = Crypt::OpenPGP::Compressed->new( Data => $ptdata,
454             Alg => $alg ) or return $pgp->error("Compression error: " .
455             Crypt::OpenPGP::Compressed->errstr);
456             $ptdata = Crypt::OpenPGP::PacketFactory->save($cdata);
457             }
458             require Crypt::Random;
459             my $key_data = Crypt::Random::makerandom_octet( Length => 32 );
460             my $sym_alg = $param{Cipher} ?
461             Crypt::OpenPGP::Cipher->alg_id($param{Cipher}) : DEFAULT_CIPHER;
462             my(@sym_keys);
463             if ($param{Recipients} && !ref($param{Recipients})) {
464             $param{Recipients} = [ $param{Recipients} ];
465             }
466             if (my $kid = delete $param{KeyID}) {
467             my @kid = ref $kid eq 'ARRAY' ? @$kid : $kid;
468             push @{ $param{Recipients} }, @kid;
469             }
470             if ($param{Key} || $param{Recipients}) {
471             require Crypt::OpenPGP::SessionKey;
472             my @keys;
473             if (my $recips = $param{Recipients}) {
474             my @recips = ref $recips eq 'ARRAY' ? @$recips : $recips;
475             my $ring = $pgp->pubrings->[0];
476             my %seen;
477             my $server;
478             my $cfg = $pgp->{cfg};
479             if ($cfg->get('AutoKeyRetrieve') && $cfg->get('KeyServer')) {
480             require Crypt::OpenPGP::KeyServer;
481             $server = Crypt::OpenPGP::KeyServer->new(
482             Server => $cfg->get('KeyServer'),
483             );
484             }
485             for my $r (@recips) {
486             my($lr, @kb) = (length($r));
487             if (($lr == 8 || $lr == 16) && $r !~ /[^\da-fA-F]/) {
488             my $id = pack 'H*', $r;
489             @kb = $ring->find_keyblock_by_keyid($id) if $ring;
490             @kb = $server->find_keyblock_by_keyid($id)
491             if !@kb && $server;
492             } else {
493             @kb = $ring->find_keyblock_by_uid($r) if $ring;
494             @kb = $server->find_keyblock_by_uid($r)
495             if !@kb && $server;
496             }
497             for my $kb (@kb) {
498             next unless my $cert = $kb->encrypting_key;
499             next if $seen{ $cert->key_id }++;
500             $cert->uid($kb->primary_uid);
501             push @keys, $cert;
502             }
503             }
504             if (my $cb = $param{RecipientsCallback}) {
505             @keys = @{ $cb->(\@keys) };
506             }
507             }
508             if ($param{Key}) {
509             push @keys, ref $param{Key} eq 'ARRAY' ? @{$param{Key}} :
510             $param{Key};
511             }
512             return $pgp->error("No known recipients for encryption")
513             unless @keys;
514             for my $key (@keys) {
515             push @sym_keys, Crypt::OpenPGP::SessionKey->new(
516             Key => $key,
517             SymKey => $key_data,
518             Cipher => $sym_alg,
519             ) or
520             return $pgp->error( Crypt::OpenPGP::SessionKey->errstr );
521             }
522             }
523             elsif (my $pass = $param{Passphrase}) {
524             require Crypt::OpenPGP::SKSessionKey;
525             require Crypt::OpenPGP::S2k;
526             my $s2k;
527             if ($param{Compat} && $param{Compat} eq 'PGP2') {
528             $s2k = Crypt::OpenPGP::S2k->new('Simple');
529             $s2k->{hash} = Crypt::OpenPGP::Digest->new('MD5');
530             } else {
531             $s2k = Crypt::OpenPGP::S2k->new('Salt_Iter');
532             }
533             my $keysize = Crypt::OpenPGP::Cipher->new($sym_alg)->keysize;
534             $key_data = $s2k->generate($pass, $keysize);
535             push @sym_keys, Crypt::OpenPGP::SKSessionKey->new(
536             Passphrase => $pass,
537             SymKey => $key_data,
538             Cipher => $sym_alg,
539             S2k => $s2k,
540             ) or
541             return $pgp->error( Crypt::OpenPGP::SKSessionKey->errstr );
542             } else {
543             return $pgp->error("Need something to encrypt with");
544             }
545             my $enc = Crypt::OpenPGP::Ciphertext->new(
546             MDC => $param{MDC},
547             SymKey => $key_data,
548             Data => $ptdata,
549             Cipher => $sym_alg,
550             );
551             my $enc_data = Crypt::OpenPGP::PacketFactory->save(
552             $param{Passphrase} && $param{Compat} && $param{Compat} eq 'PGP2' ?
553             $enc : (@sym_keys, $enc)
554             );
555             if ($param{Armour}) {
556             require Crypt::OpenPGP::Armour;
557             $enc_data = Crypt::OpenPGP::Armour->armour(
558             Data => $enc_data,
559             Object => 'MESSAGE',
560             ) or return $pgp->error( Crypt::OpenPGP::Armour->errstr );
561             }
562             $enc_data;
563             }
564              
565             sub decrypt {
566             my $pgp = shift;
567             my %param = @_;
568             my $wants_verify = wantarray;
569             my($data);
570             unless ($data = $param{Data}) {
571             my $file = $param{Filename} or
572             return $pgp->error("Need either 'Data' or 'Filename' to decrypt");
573             $data = $pgp->_read_files($file) or return $pgp->error($pgp->errstr);
574             }
575             my $msg = Crypt::OpenPGP::Message->new( Data => $data ) or
576             return $pgp->error("Reading data packets failed: " .
577             Crypt::OpenPGP::Message->errstr);
578             my @pieces = $msg->pieces;
579             return $pgp->error("No packets found in message") unless @pieces;
580             while (ref($pieces[0]) eq 'Crypt::OpenPGP::Marker') {
581             shift @pieces;
582             }
583             my($key, $alg);
584             if (ref($pieces[0]) eq 'Crypt::OpenPGP::SessionKey') {
585             my($sym_key, $cert, $ring) = (shift @pieces);
586             unless ($cert = $param{Key}) {
587             $ring = $pgp->secrings->[0]
588             or return $pgp->error("No secret keyrings");
589             }
590             my($kb);
591             while (ref($sym_key) eq 'Crypt::OpenPGP::SessionKey') {
592             if ($cert) {
593             if ($cert->key_id eq $sym_key->key_id) {
594             shift @pieces
595             while ref($pieces[0]) eq 'Crypt::OpenPGP::SessionKey';
596             last;
597             }
598             } else {
599             if ($kb = $ring->find_keyblock_by_keyid($sym_key->key_id)) {
600             shift @pieces
601             while ref($pieces[0]) eq 'Crypt::OpenPGP::SessionKey';
602             last;
603             }
604             }
605             $sym_key = shift @pieces;
606             }
607             return $pgp->error("Can't find a secret key to decrypt message")
608             unless $kb || $cert;
609             if ($kb) {
610             $cert = $kb->encrypting_key;
611             $cert->uid($kb->primary_uid);
612             }
613             if ($cert->is_protected) {
614             my $pass = $param{Passphrase};
615             if (!defined $pass && (my $cb = $param{PassphraseCallback})) {
616             $pass = $cb->($cert);
617             }
618             return $pgp->error("Need passphrase to unlock secret key")
619             unless $pass;
620             $cert->unlock($pass) or
621             return $pgp->error("Seckey unlock failed: " . $cert->errstr);
622             }
623             ($key, $alg) = $sym_key->decrypt($cert) or
624             return $pgp->error("Symkey decrypt failed: " . $sym_key->errstr);
625             }
626             elsif (ref($pieces[0]) eq 'Crypt::OpenPGP::SKSessionKey') {
627             my $sym_key = shift @pieces;
628             my $pass = $param{Passphrase};
629             if (!defined $pass && (my $cb = $param{PassphraseCallback})) {
630             $pass = $cb->();
631             }
632             return $pgp->error("Need passphrase to decrypt session key")
633             unless $pass;
634             ($key, $alg) = $sym_key->decrypt($pass) or
635             return $pgp->error("Symkey decrypt failed: " . $sym_key->errstr);
636             }
637             my $enc = $pieces[0];
638              
639             ## If there is still no symkey and symmetric algorithm, *and* the
640             ## first packet is a Crypt::OpenPGP::Ciphertext packet, assume that
641             ## the packet is encrypted using a symmetric key, using a 'Simple' s2k.
642             if (!$key && !$alg && ref($enc) eq 'Crypt::OpenPGP::Ciphertext') {
643             my $pass = $param{Passphrase} or
644             return $pgp->error("Need passphrase to decrypt session key");
645             require Crypt::OpenPGP::Cipher;
646             require Crypt::OpenPGP::S2k;
647             my $ciph = Crypt::OpenPGP::Cipher->new('IDEA');
648             my $s2k = Crypt::OpenPGP::S2k->new('Simple');
649             $s2k->{hash} = Crypt::OpenPGP::Digest->new('MD5');
650             $key = $s2k->generate($pass, $ciph->keysize);
651             $alg = $ciph->alg_id;
652             }
653              
654             $data = $enc->decrypt($key, $alg) or
655             return $pgp->error("Ciphertext decrypt failed: " . $enc->errstr);
656              
657             ## This is a special hack: if decrypt gets a signed, encrypted message,
658             ## it needs to be able to pass back the decrypted text *and* a flag
659             ## saying whether the signature is valid or not. But in some cases,
660             ## you don't know ahead of time if there is a signature at all--and if
661             ## there isn't, there is no way of knowing whether the signature is valid,
662             ## or if there isn't a signature at all. So this prepopulates the internal
663             ## errstr with the string "No Signature\n"--if there is a signature, and
664             ## there is an error during verification, the second return value will be
665             ## undef, and the errstr will contain the error that occurred. If there is
666             ## *not* a signature, the second return value will still be undef, but
667             ## the errstr is guaranteed to be "No Signature\n".
668             $pgp->error("No Signature");
669              
670             my($valid, $sig);
671             $msg = Crypt::OpenPGP::Message->new( Data => $data,
672             IsPacketStream => 1 );
673             @pieces = $msg->pieces;
674              
675             ## If the first packet in the decrypted data is compressed,
676             ## decompress it and set the list of packets to the result.
677             if (ref($pieces[0]) eq 'Crypt::OpenPGP::Compressed') {
678             $data = $pieces[0]->decompress or
679             return $pgp->error("Decompression error: " . $pieces[0]->errstr);
680             $msg = Crypt::OpenPGP::Message->new( Data => $data,
681             IsPacketStream => 1 );
682             @pieces = $msg->pieces;
683             }
684              
685             my($pt);
686             if (ref($pieces[0]) eq 'Crypt::OpenPGP::OnePassSig' ||
687             ref($pieces[0]) eq 'Crypt::OpenPGP::Signature') {
688             $pt = $pieces[1];
689             if ($wants_verify) {
690             ($valid, $sig) =
691             $pgp->verify( Signature => $data, IsPacketStream => 1 );
692             }
693             } else {
694             $pt = $pieces[0];
695             }
696              
697             $wants_verify ? ($pt->data, $valid, $sig) : $pt->data;
698             }
699              
700             sub keygen {
701             my $pgp = shift;
702             my %param = @_;
703             require Crypt::OpenPGP::Certificate;
704             require Crypt::OpenPGP::Key;
705             require Crypt::OpenPGP::KeyBlock;
706             require Crypt::OpenPGP::Signature;
707             require Crypt::OpenPGP::UserID;
708              
709             $param{Type} or
710             return $pgp->error("Need a Type of key to generate");
711             $param{Size} ||= 1024;
712             $param{Version} ||= 4;
713             $param{Version} = 3 if $param{Type} eq 'RSA';
714              
715             my $kb_pub = Crypt::OpenPGP::KeyBlock->new;
716             my $kb_sec = Crypt::OpenPGP::KeyBlock->new;
717              
718             my($pub, $sec) = Crypt::OpenPGP::Key->keygen($param{Type}, %param);
719             die Crypt::OpenPGP::Key->errstr unless $pub && $sec;
720             my $pubcert = Crypt::OpenPGP::Certificate->new(
721             Key => $pub,
722             Version => $param{Version}
723             ) or
724             die Crypt::OpenPGP::Certificate->errstr;
725             my $seccert = Crypt::OpenPGP::Certificate->new(
726             Key => $sec,
727             Passphrase => $param{Passphrase},
728             Version => $param{Version}
729             ) or
730             die Crypt::OpenPGP::Certificate->errstr;
731             $kb_pub->add($pubcert);
732             $kb_sec->add($seccert);
733              
734             my $id = Crypt::OpenPGP::UserID->new( Identity => $param{Identity} );
735             $kb_pub->add($id);
736             $kb_sec->add($id);
737              
738             my $sig = Crypt::OpenPGP::Signature->new(
739             Data => [ $pubcert, $id ],
740             Key => $seccert,
741             Version => $param{Version},
742             Type => 0x13,
743             );
744             $kb_pub->add($sig);
745             $kb_sec->add($sig);
746              
747             ($kb_pub, $kb_sec);
748             }
749              
750             sub _read_files {
751             my $pgp = shift;
752             return $pgp->error("No files specified") unless @_;
753             my @files = @_;
754             my $data = '';
755             for my $file (@files) {
756             $file ||= '';
757             local *FH;
758             open FH, $file or return $pgp->error("Error opening $file: $!");
759             binmode FH;
760             { local $/; $data .= }
761             close FH or warn "Warning: Got error closing $file: $!";
762             }
763             $data;
764             }
765              
766             {
767             my @MERGE_CONFIG = qw( Cipher Armour Digest );
768             sub _merge_compat {
769             my $pgp = shift;
770             my($param, $meth) = @_;
771             my $compat = $param->{Compat} || $pgp->{cfg}->get('Compat') || return 1;
772             my $ref = $COMPAT{$compat}{$meth} or
773             return $pgp->error("No settings for Compat class '$compat'");
774             for my $arg (keys %$ref) {
775             $param->{$arg} = $ref->{$arg} unless exists $param->{$arg};
776             }
777             for my $key (@MERGE_CONFIG) {
778             $param->{$key} = $pgp->{cfg}->get($key)
779             unless exists $param->{$key};
780             }
781             1;
782             }
783             }
784              
785             1;
786              
787             __END__