diff --git a/Changes b/Changes index 1995b0e..5e579ef 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,22 @@ -$Id: Changes,v 1.55 2001/08/16 01:04:56 btrott Exp $ +$Id: Changes,v 1.60 2001/09/16 04:48:53 btrott Exp $ Revision history for Crypt::OpenPGP +0.17 2001.09.15 + - Added Crypt::OpenPGP::Config to hold per-instance configuration + information, either specified through the constructor, or set + in a config file. Reads from existing PGP/GnuPG config files. + - Added --compat arg to bin/pgplet for demonstration of usage of + Compat flag to constructor, which then gets propagated down to + methods, selects keyrings, etc. + - Made keyring selection based on compatibility mode, if given. + - Automatically read in appropriate config file if Compat flag + given. + - Added tests for encrypting/decrypting block of text to t/06-cipher.t. + - Added --sdk option to Makefile.Pl to bypass interactive prompts. + - Removed Crypt::DES_EDE3 from this distribution, moved into its own + distribution. + 0.16 2001.08.15 - Took stupid extraneous files out of lib. Ick. diff --git a/MANIFEST b/MANIFEST index 068b119..6567d1b 100644 --- a/MANIFEST +++ b/MANIFEST @@ -5,7 +5,6 @@ Makefile.PL README ToDo bin/pgplet -lib/Crypt/DES_EDE3.pm lib/Crypt/OpenPGP.pm lib/Crypt/OpenPGP/Armour.pm lib/Crypt/OpenPGP/Buffer.pm @@ -14,6 +13,7 @@ lib/Crypt/OpenPGP/Certificate.pm lib/Crypt/OpenPGP/Cipher.pm lib/Crypt/OpenPGP/Ciphertext.pm lib/Crypt/OpenPGP/Compressed.pm +lib/Crypt/OpenPGP/Config.pm lib/Crypt/OpenPGP/Constants.pm lib/Crypt/OpenPGP/Digest.pm lib/Crypt/OpenPGP/ErrorHandler.pm @@ -52,10 +52,13 @@ t/05-packets.t t/06-cipher.t t/07-digest.t t/08-compress.t +t/09-config.t t/10-keyring.t t/11-encrypt.t t/12-sign.t t/13-keygen.t +t/samples/cfg.gnupg +t/samples/cfg.pgp2 t/samples/gpg/ring.pub t/samples/gpg/ring.sec t/test-common.pl diff --git a/Makefile.PL b/Makefile.PL index d188776..8b2c294 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -1,8 +1,13 @@ -# $Id: Makefile.PL,v 1.9 2001/07/31 07:03:52 btrott Exp $ +# $Id: Makefile.PL,v 1.11 2001/09/16 04:48:53 btrott Exp $ use ExtUtils::MakeMaker qw( prompt WriteMakefile ); use strict; +my $is_sdk = 0; +for my $arg (@ARGV) { + $is_sdk = 1, last if $arg eq '--sdk'; +} + my %BASE_PREREQS = ( 'Data::Buffer' => '0.04', 'MIME::Base64' => 0, @@ -16,7 +21,7 @@ my %BASE_PREREQS = ( my %prereq = %BASE_PREREQS; my @cryptmod = ( - [ '3DES' => 'Crypt::DES' ], + [ '3DES' => 'Crypt::DES_EDE3' ], [ 'IDEA' => 'Crypt::IDEA' ], [ 'Blowfish' => 'Crypt::Blowfish' ], [ 'Twofish' => 'Crypt::Twofish', '2.00' ], @@ -35,7 +40,16 @@ my @pkmod = ( [ 'RSA' => 'Crypt::RSA' ], ); -print<[1]) { + $prereq{$ref->[1]} = $ref->[2] ? $ref->[2] : 0; + } + } + } +} else { + print<[0]; -} -my $c = prompt("\nEnter your choices, separated by spaces:", 1); -print "\n"; + my $i = 1; + for my $ciph (@cryptmod) { + printf " [%d] %s\n", $i++, $ciph->[0]; + } + my $c = prompt("\nEnter your choices, separated by spaces:", 1); + print "\n"; -for my $id (split /\s+/, $c) { - next unless $cryptmod[$id-1]->[1]; - $prereq{ $cryptmod[$id-1]->[1] } = $cryptmod[$id-1]->[2] || '0'; -} + for my $id (split /\s+/, $c) { + next unless $cryptmod[$id-1]->[1]; + $prereq{ $cryptmod[$id-1]->[1] } = $cryptmod[$id-1]->[2] || '0'; + } -print<[0]; -} -my $c = prompt("\nEnter your choices, separated by spaces:", 2); -print "\n"; + my $i = 1; + for my $ciph (@dgstmod) { + printf " [%d] %s\n", $i++, $ciph->[0]; + } + my $c = prompt("\nEnter your choices, separated by spaces:", 2); + print "\n"; -for my $id (split /\s+/, $c) { - next unless $dgstmod[$id-1]->[1]; - $prereq{ $dgstmod[$id-1]->[1] } = '0'; -} + for my $id (split /\s+/, $c) { + next unless $dgstmod[$id-1]->[1]; + $prereq{ $dgstmod[$id-1]->[1] } = '0'; + } =pod -print<[0]; -} -my $c = prompt("\nEnter your choices, separated by spaces:", '1 2'); -print "\n"; + my $i = 1; + for my $pk (@pkmod) { + printf " [%d] %s\n", $i++, $pk->[0]; + } + my $c = prompt("\nEnter your choices, separated by spaces:", '1 2'); + print "\n"; -for my $id (split /\s+/, $c) { - next unless $pkmod[$id-1]->[1]; - $prereq{ $pkmod[$id-1]->[1] } = '0'; -} + for my $id (split /\s+/, $c) { + next unless $pkmod[$id-1]->[1]; + $prereq{ $pkmod[$id-1]->[1] } = '0'; + } =cut -print "\nChecking for required modules\n\n"; -my(%todo, $missing); -while (my($k, $v) = each %prereq) { - unless (check_module($k, $v, \$missing)) { - $todo{$k} = $v; + print "\nChecking for required modules\n\n"; + my(%todo, $missing); + while (my($k, $v) = each %prereq) { + unless (check_module($k, $v, \$missing)) { + $todo{$k} = $v; + } } -} -use Cwd; -my $cwd = cwd(); + use Cwd; + my $cwd = cwd(); -if (%todo) { - print <install($k); - delete $prereq{$k}; - chdir $cwd or die "Can't chdir back to $cwd: $!"; + while (my($k, $v) = each %todo) { + if (prompt(sprintf("%s%s not installed/out of date. Install (y/n)?", + $k, $v ? " (ver. $v)" : ""), "y") =~ /^y/) { + require CPAN; CPAN::Shell->install($k); + delete $prereq{$k}; + chdir $cwd or die "Can't chdir back to $cwd: $!"; + } } } + print "\n"; } -print "\n"; WriteMakefile( NAME => 'Crypt::OpenPGP', DISTNAME => 'Crypt-OpenPGP', diff --git a/README b/README index f04bfc5..fd92d73 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -$Id: README,v 1.6 2001/08/15 22:11:14 btrott Exp $ +$Id: README,v 1.7 2001/09/16 04:48:53 btrott Exp $ This is Crypt::OpenPGP. It provides a pure-Perl implementation of the OpenPGP standard, including support for all versions of PGP and @@ -16,7 +16,7 @@ Crypt::OpenPGP requires the following Perl modules: Depending on which symmetric ciphers you use, you will require at least one of the following modules: - * Crypt::DES (for 3DES) + * Crypt::DES_EDE3 (for 3DES) * Crypt::IDEA (for IDEA) * Crypt::Blowfish (for Blowfish) * Crypt::Twofish (for Twofish) diff --git a/ToDo b/ToDo index 2bb73f1..8239ba1 100644 --- a/ToDo +++ b/ToDo @@ -1,4 +1,4 @@ -$Id: ToDo,v 1.31 2001/08/12 23:33:07 btrott Exp $ +$Id: ToDo,v 1.32 2001/09/16 04:48:53 btrott Exp $ Crypt::OpenPGP todo -------- @@ -26,7 +26,7 @@ xx * KeyRing::find_keyblock_by_keyid should no longer return certificate if rings. xx * compatibility param to encrypt and sign (and keygen, maybe), eg. Compat => 'PGP2' turns on Version => 3 and Cipher => 'IDEA' -* read existing PGPx/GnuPG config files into Config object +xx * read existing PGPx/GnuPG config files into Config object * smart detection and description of arbitrary files, eg. pass in some data and give back 'RSA signed message, Version 3, probably PGP2' xx * compressed packets diff --git a/bin/pgplet b/bin/pgplet index 92a61c8..d9a8ecc 100755 --- a/bin/pgplet +++ b/bin/pgplet @@ -1,5 +1,5 @@ #!/usr/bin/perl -w -# $Id: pgplet,v 1.5 2001/08/15 22:21:27 btrott Exp $ +# $Id: pgplet,v 1.8 2001/08/30 03:40:24 btrott Exp $ use lib 'lib'; @@ -16,10 +16,15 @@ GetOptions(\%opts, "sign|s", "encrypt|e", "detach-sign|b", "list-public-keys", "list-secret-keys", "fingerprint", "keyring=s", "secret-keyring=s", "recipient|r=s@", "armour|a", "clearsign", - "enarmour", "dearmour"); + "enarmour", "dearmour", "compat=s"); -my $pgp = Crypt::OpenPGP->new( PubRing => $opts{keyring}, - SecRing => $opts{'secret-keyring'} ); +my %arg; +$arg{PubRing} = $opts{keyring} if $opts{keyring}; +$arg{SecRing} = $opts{'secret-keyring'} if $opts{'secret-keyring'}; +$arg{Compat} = $opts{compat} if $opts{compat}; + +my $pgp = Crypt::OpenPGP->new( %arg ) or + die Crypt::OpenPGP->errstr; my @args = ($pgp, \%opts, \@ARGV); @@ -48,7 +53,7 @@ if ($opts{'list-keys'} || $opts{'list-public-keys'}) { sub do_list_keys { my($pgp, $opts, $args, $secret) = @_; my $fp = $opts->{fingerprint}; - my $ring_file = $pgp->{ $secret ? 'SecRing' : 'PubRing' }; + my $ring_file = $pgp->{cfg}->get( $secret ? 'SecRing' : 'PubRing' ); my $ring = Crypt::OpenPGP::KeyRing->new( Filename => $ring_file ); $ring->read; my @blocks = $ring->blocks; @@ -60,7 +65,7 @@ sub do_list_keys { ($cert->is_secret ? 'sec' : 'pub'), $cert->key->size, $cert->key->public_key->abbrev, - uc unpack('H*', substr $cert->key_id, -4, 4), + substr($cert->key_id_hex, -8, 8), ($kb->primary_uid || ''); if ($fp) { my $f = $cert->fingerprint; @@ -72,7 +77,7 @@ sub do_list_keys { ($sub->is_secret ? 'ssb' : 'sub'), $sub->key->size, $sub->key->public_key->abbrev, - uc unpack('H*', substr $sub->key_id, -4, 4), + substr($cert->key_id_hex, -8, 8), } print "\n"; } @@ -92,7 +97,7 @@ Message is being encrypted to: for my $cert (@$keys) { $prompt .= sprintf " [%d] %s (ID %s)\n", $i++, $cert->uid, - uc unpack('H*', substr $cert->key_id, -4, 4); + substr($cert->key_id_hex, -8, 8); } $prompt .= " If these are the intended recipients, press . Otherwise, @@ -114,9 +119,8 @@ Enter numeric indices, separated by spaces: "; my $ct = $pgp->encrypt( Recipients => $recips, RecipientsCallback => $cb, - Compat => 'GnuPG', Filename => $file, - Armour => $opts->{armour}, + $opts->{armour} ? (Armour => $opts->{armour}) : (), %sign_args, ) or die $pgp->errstr; print $ct; @@ -155,13 +159,12 @@ sub do_sign { my $cert = find_default_seckey($pgp); until ($sig) { $sig = $pgp->sign( - Compat => 'GnuPG', Filename => $file, Detach => $detach, Clearsign => $clear, Key => $cert, PassphraseCallback => \&passphrase_cb, - Armour => $opts->{armour}, + $opts->{armour} ? (Armour => $opts->{armour}) : (), ); unless ($sig) { if ($pgp->errstr =~ /Bad checksum/) { @@ -210,14 +213,15 @@ user "%s". Enter passphrase: ), $cert->uid, $cert->key->size, $cert->key->alg, - uc unpack('H*', substr $cert->key_id, -4, 4); + substr($cert->key_id_hex, -8, 8); prompt($prompt, '', 1); } sub find_default_seckey { my($pgp) = @_; - my $ring = Crypt::OpenPGP::KeyRing->new( Filename => $pgp->{SecRing} ) or - return $pgp->error(Crypt::OpenPGP::KeyRing->errstr); + my $ring = Crypt::OpenPGP::KeyRing->new( Filename => + $pgp->{cfg}->get('SecRing') ) or + return $pgp->error(Crypt::OpenPGP::KeyRing->errstr); my $kb = $ring->find_keyblock_by_index(-1) or return $pgp->error("Can't find last keyblock: " . $ring->errstr); my $cert = $kb->signing_key; diff --git a/lib/Crypt/DES_EDE3.pm b/lib/Crypt/DES_EDE3.pm deleted file mode 100644 index 0a17291..0000000 --- a/lib/Crypt/DES_EDE3.pm +++ /dev/null @@ -1,116 +0,0 @@ -# $Id: DES_EDE3.pm,v 1.2 2001/07/28 05:21:24 btrott Exp $ - -package Crypt::DES_EDE3; -use strict; - -use Crypt::DES; - -sub new { - my $class = shift; - my $ede3 = bless {}, $class; - $ede3->init(@_); -} - -sub keysize { 24 } -sub blocksize { 8 } - -sub init { - my $ede3 = shift; - my($key) = @_; - for my $i (1..3) { - $ede3->{"des$i"} = Crypt::DES->new(substr $key, 8*($i-1), 8); - } - $ede3; -} - -sub encrypt { - my($ede3, $block) = @_; - $ede3->{des3}->encrypt( - $ede3->{des2}->decrypt( - $ede3->{des1}->encrypt($block) - ) - ); -} - -sub decrypt { - my($ede3, $block) = @_; - $ede3->{des1}->decrypt( - $ede3->{des2}->encrypt( - $ede3->{des3}->decrypt($block) - ) - ); -} - -1; -__END__ - -=head1 NAME - -Crypt::DES_EDE3 - DES-EDE3 encryption/decryption - -=head1 SYNOPSIS - - use Crypt::DES_EDE3; - my $ede3 = Crypt::DES_EDE3->new($key); - $ede3->encrypt($block); - -=head1 DESCRIPTION - -I implements DES-EDE3 encryption. This is triple-DES -encryption where an encrypt operation is encrypt-decrypt-encrypt, and -decrypt is decrypt-encrypt-decrypt. This implementation uses I -to do its dirty DES work, and simply provides a wrapper around that -module: setting up the individual DES ciphers, initializing the keys, -and performing the encryption/decryption steps. - -DES-EDE3 encryption requires a key size of 24 bytes. - -You're probably best off not using this module directly, as the -I and I methods expect 8-byte blocks. You might -want to use the module in conjunction with I, for example. -This would be DES-EDE3-CBC, or triple-DES in outer CBC mode. - -=head1 USAGE - -=head2 $ede3 = Crypt::DES_EDE3->new($key) - -Creates a new I object (really, a collection of three -DES ciphers), and initializes each cipher with part of I<$key>, which -should be at least 24 bytes. If it's longer than 24 bytes, the extra -bytes will be ignored. - -Returns the new object. - -=head2 $ede3->encrypt($block) - -Encrypts an 8-byte block of data I<$block> using the three DES ciphers -in an encrypt-decrypt-encrypt operation. - -Returns the encrypted block. - -=head2 $ede3->decrypt($block) - -Decrypts an 8-byte block of data I<$block> using the three DES ciphers -in a decrypt-encrypt-decrypt operation. - -Returns the decrypted block. - -=head2 $ede3->blocksize - -Returns the block size (8). - -=head2 $ede3->keysize - -Returns the key size (24). - -=head1 LICENSE - -Crypt::DES_EDE3 is free software; you may redistribute it and/or modify -it under the same terms as Perl itself. - -=head1 AUTHOR & COPYRIGHTS - -Except where otherwise noted, Crypt::DES_EDE3 is Copyright 2001 -Benjamin Trott, ben@rhumba.pair.com. All rights reserved. - -=cut diff --git a/lib/Crypt/OpenPGP.pm b/lib/Crypt/OpenPGP.pm index 45ae7fc..5545866 100644 --- a/lib/Crypt/OpenPGP.pm +++ b/lib/Crypt/OpenPGP.pm @@ -1,67 +1,90 @@ -# $Id: OpenPGP.pm,v 1.69 2001/08/16 01:04:57 btrott Exp $ +# $Id: OpenPGP.pm,v 1.73 2001/09/16 04:48:20 btrott Exp $ package Crypt::OpenPGP; use strict; use vars qw( $VERSION ); -$VERSION = '0.16'; +$VERSION = '0.17'; use Crypt::OpenPGP::Constants qw( DEFAULT_CIPHER ); use Crypt::OpenPGP::KeyRing; use Crypt::OpenPGP::Plaintext; use Crypt::OpenPGP::Message; use Crypt::OpenPGP::PacketFactory; +use Crypt::OpenPGP::Config; use Crypt::OpenPGP::ErrorHandler; use base qw( Crypt::OpenPGP::ErrorHandler ); -use vars qw( %DEFAULTS %COMPAT ); +use vars qw( %COMPAT ); { my $env = sub { my $dir = shift; my @paths; if (exists $ENV{$dir}) { for (@_) { push @paths, "$ENV{$dir}/$_" } } - return @paths ? @paths : ""; + return @paths ? @paths : (); }; - %DEFAULTS = ( - PubRing => [ $env->('PGPPATH','pubring.pgp', 'pubring.pkr'), - $env->('HOME', '.pgp/pubring.pgp', '.pgp/pubring.pkr'), - $env->('GNUPGHOME', 'pubring.gpg'), - $env->('HOME', '.gnupg/pubring.gpg'), - ], - - SecRing => [ $env->('PGPPATH','secring.pgp', 'secring.skr'), - $env->('HOME', '.pgp/secring.pgp', '.pgp/secring.skr'), - $env->('GNUPGHOME', 'secring.gpg'), - $env->('HOME', '.gnupg/secring.gpg'), - ], - ); -} - -%COMPAT = ( - PGP2 => { + %COMPAT = ( + PGP2 => { 'sign' => { Digest => 'MD5', Version => 3 }, 'encrypt' => { Cipher => 'IDEA', Compress => 'ZIP' }, 'keygen' => { Type => 'RSA', Cipher => 'IDEA', Version => 3, Digest => 'MD5' }, - }, - - PGP5 => { + 'PubRing' => [ + $env->('PGPPATH','pubring.pgp'), + $env->('HOME', '.pgp/pubring.pgp'), + ], + 'SecRing' => [ + $env->('PGPPATH','secring.pgp'), + $env->('HOME', '.pgp/secring.pgp'), + ], + 'Config' => [ + $env->('PGPPATH', 'config.txt'), + $env->('HOME', '.pgp/config.txt'), + ], + }, + + PGP5 => { 'sign' => { Digest => 'SHA1', Version => 3 }, 'encrypt' => { Cipher => 'DES3', Compress => 'ZIP' }, 'keygen' => { Type => 'DSA', Cipher => 'DES3', Version => 4, Digest => 'SHA1' }, - }, - - GnuPG => { + 'PubRing' => [ + $env->('PGPPATH','pubring.pkr'), + $env->('HOME', '.pgp/pubring.pkr'), + ], + 'SecRing' => [ + $env->('PGPPATH','secring.skr'), + $env->('HOME', '.pgp/secring.skr'), + ], + 'Config' => [ + $env->('PGPPATH', 'pgp.cfg'), + $env->('HOME', '.pgp/pgp.cfg'), + ], + }, + + GnuPG => { 'sign' => { Digest => 'RIPEMD160', Version => 4 }, 'encrypt' => { Cipher => 'Rijndael', Compress => 'Zlib', MDC => 1 }, 'keygen' => { Type => 'DSA', Cipher => 'Rijndael', Version => 4, Digest => 'RIPEMD160' }, - }, -); + 'Config' => [ + $env->('GNUPGHOME', 'options'), + $env->('HOME', '.gnupg/options'), + ], + 'PubRing' => [ + $env->('GNUPGHOME', 'pubring.gpg'), + $env->('HOME', '.gnupg/pubring.gpg'), + ], + 'SecRing' => [ + $env->('GNUPGHOME', 'secring.gpg'), + $env->('HOME', '.gnupg/secring.gpg'), + ], + }, + ); +} sub version_string { __PACKAGE__ . ' ' . $VERSION } @@ -71,16 +94,32 @@ sub new { $pgp->init(@_); } +sub _first_exists { + my($list) = @_; + for my $f (@$list) { + next unless $f; + return $f if -e $f; + } +} + sub init { my $pgp = shift; my %param = @_; - ## XXX fix, read options from GnuPG options file, etc.? - $pgp->{$_} = $param{$_} for keys %param; + my $cfg_file = delete $param{ConfigFile}; + my $cfg = $pgp->{cfg} = Crypt::OpenPGP::Config->new(%param) or + return Crypt::OpenPGP::Config->errstr; + if (!$cfg_file && (my $compat = $cfg->get('Compat'))) { + $cfg_file = _first_exists($COMPAT{$compat}{Config}); + } + if ($cfg_file) { + $cfg->read_config($param{Compat}, $cfg_file); + } for my $s (qw( PubRing SecRing )) { - unless (defined $pgp->{$s}) { - for my $ring (@{ $DEFAULTS{$s} }) { - next unless $ring; - $pgp->{$s} = $ring, last if -e $ring; + unless (defined $cfg->get($s)) { + my @compats = $param{Compat} ? ($param{Compat}) : keys %COMPAT; + for my $compat (@compats) { + my $ring = _first_exists($COMPAT{$compat}{$s}); + $cfg->set($s, $ring), last if $ring; } } } @@ -101,7 +140,8 @@ sub sign { } unless ($cert = $param{Key}) { my $kid = $param{KeyID} or return $pgp->error("No KeyID specified"); - my $ring = Crypt::OpenPGP::KeyRing->new( Filename => $pgp->{SecRing} ); + my $ring = Crypt::OpenPGP::KeyRing->new( Filename => + $pgp->{cfg}->get('SecRing') ); my $kb = $ring->find_keyblock_by_keyid(pack 'H*', $kid) or return $pgp->error("Could not find secret key with KeyID $kid"); $cert = $kb->signing_key; @@ -205,7 +245,8 @@ sub verify { my($cert, $kb); unless ($cert = $param{Key}) { my $key_id = $sig->key_id; - my $ring = Crypt::OpenPGP::KeyRing->new( Filename => $pgp->{PubRing} ); + my $ring = Crypt::OpenPGP::KeyRing->new( Filename => + $pgp->{cfg}->get('PubRing') ); $kb = $ring->find_keyblock_by_keyid($key_id) or return $pgp->error("Could not find public key with KeyID " . unpack('H*', $key_id)); @@ -270,7 +311,7 @@ sub encrypt { if (my $recips = $param{Recipients}) { my @recips = ref $recips eq 'ARRAY' ? @$recips : $recips; my $ring = Crypt::OpenPGP::KeyRing->new( Filename => - $pgp->{PubRing} ); + $pgp->{cfg}->get('PubRing') ); my %seen; for my $r (@recips) { my($lr, @kb) = (length($r)); @@ -361,7 +402,7 @@ sub decrypt { my($sym_key, $cert) = (shift @pieces); unless ($cert = $param{Key}) { my $ring = Crypt::OpenPGP::KeyRing->new(Filename => - $pgp->{SecRing}); + $pgp->{cfg}->get('SecRing')); my($kb); while (ref($sym_key) eq 'Crypt::OpenPGP::SessionKey') { if ($kb = $ring->find_keyblock_by_keyid($sym_key->key_id)) { @@ -487,16 +528,23 @@ sub _read_files { $data; } -sub _merge_compat { - my $pgp = shift; - my($param, $meth) = @_; - my $compat = $param->{Compat} or return 1; - my $ref = $COMPAT{$compat}{$meth} or - return $pgp->error("No settings for Compat class '$compat'"); - for my $arg (keys %$ref) { - $param->{$arg} = $ref->{$arg} unless exists $param->{$arg}; +{ + my @MERGE_CONFIG = qw( Cipher Armour Digest ); + sub _merge_compat { + my $pgp = shift; + my($param, $meth) = @_; + my $compat = $param->{Compat} || $pgp->{cfg}->get('Compat') || return 1; + my $ref = $COMPAT{$compat}{$meth} or + return $pgp->error("No settings for Compat class '$compat'"); + for my $arg (keys %$ref) { + $param->{$arg} = $ref->{$arg} unless exists $param->{$arg}; + } + for my $key (@MERGE_CONFIG) { + $param->{$key} = $pgp->{cfg}->get($key) + unless exists $param->{$key}; + } + 1; } - 1; } 1; @@ -525,7 +573,7 @@ Crypt::OpenPGP - Pure-Perl OpenPGP implementation my $ciphertext = $pgp->encrypt( Filename => $file, - KeyID => $key_id, + Recipients => $key_id, Armour => 1, ); @@ -579,9 +627,9 @@ I parameter, giving it one of the values from the list below. For example: my $ct = $pgp->encrypt( - Compat => 'PGP2', - Filename => 'foo.pl', - KeyID => $key_id, + Compat => 'PGP2', + Filename => 'foo.pl', + Recipients => $key_id, ); Because I was specified, the data will automatically be encrypted @@ -634,6 +682,24 @@ I<%args> can contain: =over 4 +=item * Compat + +The compatibility mode for this I object. This value will +propagate down into method calls upon this object, meaning that it will be +applied for all method calls invoked on this object. For example, if you set +I here, you do not have to set it again when calling I +or I (below), unless, of course, you want to set I to a +different value for those methods. + +I influences several factors upon object creation, unless otherwise +overridden in the constructor arguments: if you have a configuration file +for this compatibility mode (eg. F<~/.gnupg/options> for GnuPG), it will +be automatically read in, and I will set any options +relevant to its execution (symmetric cipher algorithm, etc.); I +and I (below) are set according to the default values for this +compatibility mode (eg. F<~/.gnupg/pubring.gpg for the GnuPG public +keyring). + =item * SecRing Path to your secret keyring. If unspecified, I will look @@ -644,6 +710,23 @@ for your keyring in a number of default places. Path to your public keyring. If unspecified, I will look for your keyring in a number of default places. +=item * ConfigFile + +Path to a PGP/GnuPG config file. If specified, you must also pass in a +value for the I parameter, stating what format config file you are +passing in. For example, if you are passing in the path to a GnuPG config +file, you should give a value of C for the I flag. + +If you leave I unspecified, but you have specified a value for +I, I will try to find your config file, based on +the value of I that you pass in (eg. F<~/.gnupg/options> if +I is C). + +NOTE: if you do not specify a I flag, I cannot read +any configuration files, even if you I specified a value for the +I parameter, because it will not be able to determine the proper +config file format. + =back =head2 $pgp->encrypt( %args ) @@ -730,8 +813,8 @@ cryptography; this can be useful in certain circumstances (for example, when encrypting data locally on disk). This argument is optional; if not provided you should provide the -I option (above) to perform public-key encryption when encrypting -the session key. +I option (above) to perform public-key encryption when +encrypting the session key. =item * RecipientsCallback diff --git a/lib/Crypt/OpenPGP/Config.pm b/lib/Crypt/OpenPGP/Config.pm new file mode 100644 index 0000000..3611cc4 --- /dev/null +++ b/lib/Crypt/OpenPGP/Config.pm @@ -0,0 +1,110 @@ +# $Id: Config.pm,v 1.5 2001/08/30 04:10:19 btrott Exp $ + +package Crypt::OpenPGP::Config; +use strict; + +use Crypt::OpenPGP::ErrorHandler; +use base qw( Crypt::OpenPGP::ErrorHandler ); + +sub new { + my $class = shift; + my $cfg = bless { o => { @_ } }, $class; + $cfg; +} + +sub get { $_[0]->{o}{ $_[1] } } +sub set { + my $cfg = shift; + my($key, $val) = @_; + $cfg->{o}{$key} = $val; +} + +{ + my %STANDARD = ( + str => \&_set_str, + bool => \&_set_bool, + ); + + sub read_config { + my $cfg = shift; + my($compat, $cfg_file) = @_; + my $class = join '::', __PACKAGE__, $compat; + my $directives = $class->directives; + local(*FH, $_, $/); + $/ = "\n"; + open FH, $cfg_file or + return $cfg->error("Error opening file '$cfg_file': $!"); + while () { + chomp; + next if !/\S/ || /^#/; + if (/^\s*([^\s=]+)(?:(?:(?:\s*=\s*)|\s+)(.*))?/) { + my($key, $val) = ($1, $2); + my $ref = $directives->{lc $key}; + next unless $ref; + my $code = ref($ref->[0]) eq 'CODE' ? $ref->[0] : + $STANDARD{$ref->[0]}; + $code->($cfg, $ref->[1], $val); + } + } + close FH; + } +} + +sub _set_str { $_[0]->{o}{$_[1]} = $_[2] } +{ + my %BOOL = ( off => 0, on => 1 ); + sub _set_bool { + my($cfg, $key, $val) = @_; + $val = 1 unless defined $val; + $val = $BOOL{$val} || $val; + $cfg->{o}{$key} = $val; + } +} + +package Crypt::OpenPGP::Config::GnuPG; +sub directives { + { + armor => [ 'bool', 'Armour' ], + 'default-key' => [ 'str', 'DefaultKey' ], + recipient => [ 'str', 'Recipient' ], + 'default-recipient' => [ 'str', 'DefaultRecipient' ], + 'default-recipient-self' => [ 'bool', 'DefaultSelfRecipient' ], + 'encrypt-to' => [ 'str', 'Recipient' ], + verbose => [ 'bool', 'Verbose' ], + textmode => [ 'bool', 'TextMode' ], + keyring => [ 'str', 'PubRing' ], + 'secret-keyring' => [ 'str', 'SecRing' ], + 'cipher-algo' => [ \&_set_cipher ], + 'digest-algo' => [ 'str', 'Digest' ], + 'compress-algo' => [ \&_set_compress ], + } +} + +{ + my %Ciphers = ( + '3DES' => 'DES3', BLOWFISH => 'Blowfish', + RIJNDAEL => 'Rijndael', RIJNDAEL192 => 'Rijndael192', + RIJNDAEL256 => 'Rijndael256', TWOFISH => 'Twofish', + ); + sub _set_cipher { $_[0]->{o}{Cipher} = $Ciphers{$_[2]} } + + my %Compress = ( 1 => 'ZIP', 2 => 'Zlib' ); + sub _set_compress { $_[0]->{o}{Compress} = $Compress{$_[2]} } +} + +package Crypt::OpenPGP::Config::PGP2; +sub directives { + { + armor => [ 'bool', 'Armour' ], + compress => [ 'bool', 'Compress' ], + encrypttoself => [ 'bool', 'EncryptToSelf' ], + myname => [ 'str', 'DefaultSelfRecipient' ], + pubring => [ 'str', 'PubRing' ], + secring => [ 'str', 'SecRing' ], + } +} + +package Crypt::OpenPGP::Config::PGP5; +*directives = \&Crypt::OpenPGP::Config::PGP2::directives; + +1; diff --git a/t/06-cipher.t b/t/06-cipher.t index 500d94a..7ae20aa 100644 --- a/t/06-cipher.t +++ b/t/06-cipher.t @@ -1,4 +1,4 @@ -# $Id: 06-cipher.t,v 1.3 2001/07/29 12:48:37 btrott Exp $ +# $Id: 06-cipher.t,v 1.4 2001/08/29 18:10:11 btrott Exp $ use Test; use Crypt::OpenPGP::Cipher; @@ -7,6 +7,15 @@ use strict; my $KEY = pack "H64", ("0123456789ABCDEF" x 8); my $PASS = pack "H16", ("0123456789ABCDEF"); +my $data = <<'TEXT'; +I 'T' them, 24:7, all year long +purgatory's circle, drowning here, someone will always say yes +funny place for the social, for the insects to start caring +just an ambulance at the bottom of a cliff +in these plagued streets of pity you can buy anything +for $200 anyone can conceive a God on video +TEXT + my %TESTS; BEGIN { %TESTS = %Crypt::OpenPGP::Cipher::ALG; @@ -15,7 +24,7 @@ BEGIN { for my $cid (keys %TESTS) { my $cipher = Crypt::OpenPGP::Cipher->new($cid); if ($cipher) { - $num_tests += 7; + $num_tests += 9; } else { delete $TESTS{$cid}; } @@ -37,6 +46,10 @@ for my $cid (keys %TESTS) { $dec = $ciph2->decrypt($enc); ok(vec($dec, 0, 8) == vec($dec, 2, 8)); ok(vec($dec, 1, 8) == vec($dec, 3, 8)); + + $enc = $ciph1->encrypt($data); + ok($enc); + ok($ciph2->decrypt($enc), $data); } sub _checkbytes { diff --git a/t/09-config.t b/t/09-config.t new file mode 100644 index 0000000..918ad81 --- /dev/null +++ b/t/09-config.t @@ -0,0 +1,69 @@ +# $Id: 09-config.t,v 1.1 2001/08/30 04:19:27 btrott Exp $ + +use Test; +use Crypt::OpenPGP; +use Crypt::OpenPGP::Config; +use strict; + +BEGIN { plan tests => 20 }; + +use vars qw( $SAMPLES ); +unshift @INC, 't/'; +require 'test-common.pl'; +use File::Spec; + +my($cfg_file, $cfg, $pgp); + +### TEST GNUPG CONFIG +$cfg_file = File::Spec->catfile($SAMPLES, 'cfg.gnupg'); + +$cfg = Crypt::OpenPGP::Config->new; +ok($cfg); +ok( $cfg->read_config('GnuPG', $cfg_file) ); + +## Test standard str directive +ok($cfg->get('Digest'), 'MD5'); +$cfg->set('Digest', 'SHA1'); +ok($cfg->get('Digest'), 'SHA1'); + +## Test standard bool directive, no arg (eg. 'armor') +ok($cfg->get('Armour'), 1); +$cfg->set('Armour', 0); +ok($cfg->get('Armour'), 0); + +## Test special Cipher directive (eg. 'cipher-alg TWOFISH') +ok($cfg->get('Cipher'), 'Twofish'); + +## Test special Compress directive +ok($cfg->get('Compress'), 'Zlib'); + +## Test that config file gets read correctly when passed to +## constructor. +$pgp = Crypt::OpenPGP->new( ConfigFile => $cfg_file, Compat => 'GnuPG' ); +ok($pgp); +ok($pgp->{cfg}); +ok($pgp->{cfg}->get('Armour'), 1); + +### TEST PGP2 CONFIG +$cfg_file = File::Spec->catfile($SAMPLES, 'cfg.pgp2'); + +$cfg = Crypt::OpenPGP::Config->new; +ok($cfg); +ok( $cfg->read_config('PGP2', $cfg_file) ); + +## Test standard str directive +ok($cfg->get('PubRing'), 'foo.pubring'); +$cfg->set('PubRing', 'bar.pubring'); +ok($cfg->get('PubRing'), 'bar.pubring'); + +## Test standard bool directive, with arg (eg. 'Armor on') +ok($cfg->get('Armour'), 1); +$cfg->set('Armour', 0); +ok($cfg->get('Armour'), 0); + +## Test that config file gets read correctly when passed to +## constructor. +$pgp = Crypt::OpenPGP->new( ConfigFile => $cfg_file, Compat => 'PGP2' ); +ok($pgp); +ok($pgp->{cfg}); +ok($pgp->{cfg}->get('Armour'), 1); diff --git a/t/11-encrypt.t b/t/11-encrypt.t index 15602af..bc0a5ad 100644 --- a/t/11-encrypt.t +++ b/t/11-encrypt.t @@ -1,4 +1,4 @@ -# $Id: 11-encrypt.t,v 1.10 2001/08/11 07:38:50 btrott Exp $ +# $Id: 11-encrypt.t,v 1.11 2001/08/29 21:31:01 btrott Exp $ use Test; use Crypt::OpenPGP; @@ -148,7 +148,8 @@ ok($pt eq $text); ## Test giving encrypt and decrypt the Key parameter to ## bypass looking up key in keyring. -my $ring = Crypt::OpenPGP::KeyRing->new( Filename => $pgp->{SecRing} ); +my $ring = Crypt::OpenPGP::KeyRing->new( Filename => + $pgp->{cfg}->get('SecRing') ); my $kb = $ring->find_keyblock_by_keyid(pack 'H*', $key_id); my $cert = $kb->encrypting_key; $cert->unlock($pass); diff --git a/t/12-sign.t b/t/12-sign.t index c7ac87a..dfcd841 100644 --- a/t/12-sign.t +++ b/t/12-sign.t @@ -1,4 +1,4 @@ -# $Id: 12-sign.t,v 1.5 2001/08/11 07:46:46 btrott Exp $ +# $Id: 12-sign.t,v 1.6 2001/08/29 21:31:01 btrott Exp $ use Test; use Crypt::OpenPGP; @@ -84,7 +84,8 @@ $signer = $pgp->verify( Signature => $sig ); ok($signer, $uid); ## Test using Key param to sign and verify. -my $ring = Crypt::OpenPGP::KeyRing->new( Filename => $pgp->{SecRing} ); +my $ring = Crypt::OpenPGP::KeyRing->new( Filename => + $pgp->{cfg}->get('SecRing') ); my $kb = $ring->find_keyblock_by_keyid(pack 'H*', $key_id); my $cert = $kb->signing_key; $cert->unlock($pass); diff --git a/t/samples/cfg.gnupg b/t/samples/cfg.gnupg new file mode 100644 index 0000000..f867760 --- /dev/null +++ b/t/samples/cfg.gnupg @@ -0,0 +1,4 @@ +digest-algo MD5 +armor +cipher-algo TWOFISH +compress-algo 2 diff --git a/t/samples/cfg.pgp2 b/t/samples/cfg.pgp2 new file mode 100644 index 0000000..0c866ad --- /dev/null +++ b/t/samples/cfg.pgp2 @@ -0,0 +1,2 @@ +Pubring=foo.pubring +Armor on