diff options
Diffstat (limited to 'Master/tlpkg/tlperl/lib/Crypt/OpenPGP/Certificate.pm')
-rwxr-xr-x | Master/tlpkg/tlperl/lib/Crypt/OpenPGP/Certificate.pm | 562 |
1 files changed, 562 insertions, 0 deletions
diff --git a/Master/tlpkg/tlperl/lib/Crypt/OpenPGP/Certificate.pm b/Master/tlpkg/tlperl/lib/Crypt/OpenPGP/Certificate.pm new file mode 100755 index 00000000000..72d344b9118 --- /dev/null +++ b/Master/tlpkg/tlperl/lib/Crypt/OpenPGP/Certificate.pm @@ -0,0 +1,562 @@ +package Crypt::OpenPGP::Certificate; +use strict; + +use Crypt::OpenPGP::S2k; +use Crypt::OpenPGP::Key::Public; +use Crypt::OpenPGP::Key::Secret; +use Crypt::OpenPGP::Buffer; +use Crypt::OpenPGP::Util qw( mp2bin bin2mp bitsize ); +use Crypt::OpenPGP::Constants qw( DEFAULT_CIPHER + PGP_PKT_PUBLIC_KEY + PGP_PKT_PUBLIC_SUBKEY + PGP_PKT_SECRET_KEY + PGP_PKT_SECRET_SUBKEY ); +use Crypt::OpenPGP::Cipher; +use Crypt::OpenPGP::ErrorHandler; +use base qw( Crypt::OpenPGP::ErrorHandler ); + +{ + my @PKT_TYPES = ( + PGP_PKT_PUBLIC_KEY, + PGP_PKT_PUBLIC_SUBKEY, + PGP_PKT_SECRET_KEY, + PGP_PKT_SECRET_SUBKEY + ); + sub pkt_type { + my $cert = shift; + $PKT_TYPES[ ($cert->{is_secret} << 1) | $cert->{is_subkey} ]; + } +} + +sub new { + my $class = shift; + my $cert = bless { }, $class; + $cert->init(@_); +} + +sub init { + my $cert = shift; + my %param = @_; + if (my $key = $param{Key}) { + $cert->{version} = $param{Version} || 4; + $cert->{key} = $key; + $cert->{is_secret} = $key->is_secret; + $cert->{is_subkey} = $param{Subkey} || 0; + $cert->{timestamp} = time; + $cert->{pk_alg} = $key->alg_id; + if ($cert->{version} < 4) { + $cert->{validity} = $param{Validity} || 0; + $key->alg eq 'RSA' or + return (ref $cert)->error("Version 3 keys must be RSA"); + } + $cert->{s2k} = Crypt::OpenPGP::S2k->new('Salt_Iter'); + + if ($cert->{is_secret}) { + $param{Passphrase} or + return (ref $cert)->error("Need a Passphrase to lock key"); + $cert->{cipher} = $param{Cipher} || DEFAULT_CIPHER; + $cert->lock($param{Passphrase}); + } + } + $cert; +} + +sub type { $_[0]->{type} } +sub version { $_[0]->{version} } +sub timestamp { $_[0]->{timestamp} } +sub validity { $_[0]->{validity} } +sub pk_alg { $_[0]->{pk_alg} } +sub key { $_[0]->{key} } +sub is_secret { $_[0]->{key}->is_secret } +sub is_subkey { $_[0]->{is_subkey} } +sub is_protected { $_[0]->{is_protected} } +sub can_encrypt { $_[0]->{key}->can_encrypt } +sub can_sign { $_[0]->{key}->can_sign } +sub uid { + my $cert = shift; + $cert->{_uid} = shift if @_; + $cert->{_uid}; +} + +sub public_cert { + my $cert = shift; + return $cert unless $cert->is_secret; + my $pub = (ref $cert)->new; + for my $f (qw( version timestamp pk_alg is_subkey )) { + $pub->{$f} = $cert->{$f}; + } + $pub->{validity} = $cert->{validity} if $cert->{version} < 4; + $pub->{key} = $cert->{key}->public_key; + $pub; +} + +sub key_id { + my $cert = shift; + unless ($cert->{key_id}) { + if ($cert->{version} < 4) { + $cert->{key_id} = substr(mp2bin($cert->{key}->n), -8); + } + else { + $cert->{key_id} = substr($cert->fingerprint, -8); + } + } + $cert->{key_id}; +} + +sub key_id_hex { uc unpack 'H*', $_[0]->key_id } + +sub fingerprint { + my $cert = shift; + unless ($cert->{fingerprint}) { + if ($cert->{version} < 4) { + my $dgst = Crypt::OpenPGP::Digest->new('MD5'); + $cert->{fingerprint} = + $dgst->hash(mp2bin($cert->{key}->n) . mp2bin($cert->{key}->e)); + } + else { + my $data = $cert->public_cert->save; + $cert->{fingerprint} = _gen_v4_fingerprint($data); + } + } + $cert->{fingerprint}; +} + +sub fingerprint_hex { uc unpack 'H*', $_[0]->fingerprint } + +sub fingerprint_words { + require Crypt::OpenPGP::Words; + Crypt::OpenPGP::Words->encode($_[0]->fingerprint); +} + +sub _gen_v4_fingerprint { + my($data) = @_; + my $buf = Crypt::OpenPGP::Buffer->new; + $buf->put_int8(0x99); + $buf->put_int16(length $data); + $buf->put_bytes($data); + my $dgst = Crypt::OpenPGP::Digest->new('SHA1'); + $dgst->hash($buf->bytes); +} + +sub parse { + my $class = shift; + my($buf, $secret, $subkey) = @_; + my $cert = $class->new; + $cert->{is_secret} = $secret; + $cert->{is_subkey} = $subkey; + + $cert->{version} = $buf->get_int8; + $cert->{timestamp} = $buf->get_int32; + if ($cert->{version} < 4) { + $cert->{validity} = $buf->get_int16; + } + $cert->{pk_alg} = $buf->get_int8; + + my $key_class = 'Crypt::OpenPGP::Key::' . ($secret ? 'Secret' : 'Public'); + my $key = $cert->{key} = $key_class->new($cert->{pk_alg}) or + return $class->error("Key creation failed: " . $key_class->errstr); + + my @pub = $key->public_props; + for my $e (@pub) { + $key->$e($buf->get_mp_int); + } + + if ($cert->{version} >= 4) { + my $data = $buf->bytes(0, $buf->offset); + $cert->{fingerprint} = _gen_v4_fingerprint($data); + } + + if ($secret) { + $cert->{cipher} = $buf->get_int8; + if ($cert->{cipher}) { + $cert->{is_protected} = 1; + if ($cert->{cipher} == 255 || $cert->{cipher} == 254) { + $cert->{sha1check} = $cert->{cipher} == 254; + $cert->{cipher} = $buf->get_int8; + $cert->{s2k} = Crypt::OpenPGP::S2k->parse($buf); + } + else { + $cert->{s2k} = Crypt::OpenPGP::S2k->new('Simple'); + $cert->{s2k}->set_hash('MD5'); + } + + $cert->{iv} = $buf->get_bytes(8); + } + + if ($cert->{is_protected}) { + if ($cert->{version} < 4) { + $cert->{encrypted} = {}; + my @sec = $key->secret_props; + for my $e (@sec) { + my $h = $cert->{encrypted}{"${e}h"} = $buf->get_bytes(2); + $cert->{encrypted}{"${e}b"} = + $buf->get_bytes(int((unpack('n', $h)+7)/8)); + } + $cert->{csum} = $buf->get_int16; + } + else { + $cert->{encrypted} = + $buf->get_bytes($buf->length - $buf->offset); + } + } + else { + my @sec = $key->secret_props; + for my $e (@sec) { + $key->$e($buf->get_mp_int); + } + } + } + + $cert; +} + +sub save { + my $cert = shift; + my $buf = Crypt::OpenPGP::Buffer->new; + + $buf->put_int8($cert->{version}); + $buf->put_int32($cert->{timestamp}); + if ($cert->{version} < 4) { + $buf->put_int16($cert->{validity}); + } + $buf->put_int8($cert->{pk_alg}); + + my $key = $cert->{key}; + my @pub = $key->public_props; + for my $e (@pub) { + $buf->put_mp_int($key->$e()); + } + + if ($cert->{key}->is_secret) { + if ($cert->{cipher}) { + $buf->put_int8(255); + $buf->put_int8($cert->{cipher}); + $buf->append($cert->{s2k}->save); + $buf->put_bytes($cert->{iv}); + + if ($cert->{version} < 4) { + my @sec = $key->secret_props; + for my $e (@sec) { + $buf->put_bytes($cert->{encrypted}{"${e}h"}); + $buf->put_bytes($cert->{encrypted}{"${e}b"}); + } + $buf->put_int16($cert->{csum}); + } + else { + $buf->put_bytes($cert->{encrypted}); + } + } + else { + my @sec = $key->secret_props; + for my $e (@sec) { + $key->$e($buf->get_mp_int); + } + } + } + $buf->bytes; +} + +sub v3_checksum { + my $cert = shift; + my $k = $cert->{encrypted}; + my $sum = 0; + my @sec = $cert->{key}->secret_props; + for my $e (@sec) { + $sum += unpack '%16C*', $k->{"${e}h"}; + $sum += unpack '%16C*', $k->{"${e}b"}; + } + $sum & 0xFFFF; +} + +sub unlock { + my $cert = shift; + return 1 unless $cert->{is_secret} && $cert->{is_protected}; + my($passphrase) = @_; + my $cipher = Crypt::OpenPGP::Cipher->new($cert->{cipher}) or + return $cert->error( Crypt::OpenPGP::Cipher->errstr ); + my $key = $cert->{s2k}->generate($passphrase, $cipher->keysize); + $cipher->init($key, $cert->{iv}); + my @sec = $cert->{key}->secret_props; + if ($cert->{version} < 4) { + my $k = $cert->{encrypted}; + my $r = {}; + for my $e (@sec) { + $r->{$e} = $k->{"${e}b"}; + $k->{"${e}b"} = $cipher->decrypt($r->{$e}); + } + unless ($cert->{csum} == $cert->v3_checksum) { + $k->{"${_}b"} = $r->{$_} for @sec; + return $cert->error("Bad checksum"); + } + for my $e (@sec) { + $cert->{key}->$e(bin2mp($k->{"${e}b"})); + } + unless ($cert->{key}->check) { + $k->{"${_}b"} = $r->{$_} for @sec; + return $cert->error("p*q != n"); + } + } + else { + my $decrypted = $cipher->decrypt($cert->{encrypted}); + if ($cert->{sha1check}) { + my $dgst = Crypt::OpenPGP::Digest->new('SHA1'); + my $csum = substr $decrypted, -20, 20, ''; + unless ($dgst->hash($decrypted) eq $csum) { + return $cert->error("Bad SHA-1 hash"); + } + } else { + my $csum = unpack "n", substr $decrypted, -2, 2, ''; + my $gen_csum = unpack '%16C*', $decrypted; + unless ($csum == $gen_csum) { + return $cert->error("Bad simple checksum"); + } + } + my $buf = Crypt::OpenPGP::Buffer->new; + $buf->append($decrypted); + for my $e (@sec) { + $cert->{key}->$e( $buf->get_mp_int ); + } + } + + $cert->{is_protected} = 0; + + 1; +} + +sub lock { + my $cert = shift; + return if !$cert->{is_secret} || $cert->{is_protected}; + my($passphrase) = @_; + my $cipher = Crypt::OpenPGP::Cipher->new($cert->{cipher}); + my $sym_key = $cert->{s2k}->generate($passphrase, $cipher->keysize); + require Crypt::Random; + $cert->{iv} = Crypt::Random::makerandom_octet( Length => 8 ); + $cipher->init($sym_key, $cert->{iv}); + my @sec = $cert->{key}->secret_props; + if ($cert->{version} < 4) { + my $k = $cert->{encrypted} = {}; + my $key = $cert->key; + for my $e (@sec) { + $k->{"${e}b"} = mp2bin($key->$e()); + $k->{"${e}h"} = pack 'n', bitsize($key->$e()); + } + $cert->{csum} = $cert->v3_checksum; + for my $e (@sec) { + $k->{"${e}b"} = $cipher->encrypt( $k->{"${e}b"} ); + } + } + else { + my $buf = Crypt::OpenPGP::Buffer->new; + for my $e (@sec) { + $buf->put_mp_int($cert->{key}->$e()); + } + my $cnt = $buf->bytes; + $cnt .= pack 'n', unpack '%16C*', $cnt; + $cert->{encrypted} = $cipher->encrypt($cnt); + } + + $cert->{is_protected} = 1; + 1; +} + +1; +__END__ + +=head1 NAME + +Crypt::OpenPGP::Certificate - PGP Key certificate + +=head1 SYNOPSIS + + use Crypt::OpenPGP::Certificate; + + my $dsa_secret_key = Crypt::OpenPGP::Key::Secret->new( 'DSA' ); + my $cert = Crypt::OpenPGP::Certificate->new( + Key => $dsa_secret_key, + Version => 4, + Passphrase => 'foobar', + ); + my $serialized = $cert->save; + + # Unlock the locked certificate (using the passphrase from above) + $cert->unlock( 'foobar' ); + +=head1 DESCRIPTION + +I<Crypt::OpenPGP::Certificate> encapsulates a PGP key certificate +for any underlying public-key algorithm, for public and secret keys, +and for master keys and subkeys. All of these scenarios are handled +by the same I<Certificate> class. + +A I<Crypt::OpenPGP::Certificate> object wraps around a +I<Crypt::OpenPGP::Key> object; the latter implements all public-key +algorithm-specific functionality, while the certificate layer +manages some meta-data about the key, as well as the mechanisms +for locking and unlocking a secret key (using a passphrase). + +=head1 USAGE + +=head2 Crypt::OpenPGP::Certificate->new( %arg ) + +Constructs a new PGP key certificate object and returns that object. +If no arguments are provided in I<%arg>, the certificate is empty; +this is used in I<parse>, for example, to construct an empty object, +then fill it with the data in the buffer. + +I<%arg> can contain: + +=over 4 + +=item * Key + +The public/secret key object, an object of type I<Crypt::OpenPGP::Key>. + +This argument is required (for a non-empty certificate). + +=item * Version + +The certificate packet version, as defined in the OpenPGP RFC. The +two valid values are C<3> and C<4>. + +This argument is optional; if not provided the default is to produce +version C<4> certificates. You may wish to override this for +compatibility with older versions of PGP. + +=item * Subkey + +A boolean flag: if true, indicates that this certificate is a subkey, +not a master key. + +This argument is optional; the default value is C<0>. + +=item * Validity + +The number of days that this certificate is valid. This argument only +applies when creating a version 3 certificate; version 4 certificates +hold this information in a signature. + +This argument is optional; the default value is C<0>, which means that +the certificate never expires. + +=item * Passphrase + +If you are creating a certificate for a secret key--indicated by whether +or not the I<Key> (above) is a secret key--you will need to lock it +(that is, encrypt the secret part of the key). The string provided in +I<Passphrase> is used as the passphrase to lock the key. + +This argument is required if the certificate holds a secret key. + +=item * Cipher + +Specifies the symmetric cipher to use when locking (encrypting) the +secret part of a secret key. Valid values are any supported symmetric +cipher names, which can be found in I<Crypt::OpenPGP::Cipher>. + +This argument is optional; if not specified, C<DES3> is used. + +=back + +=head2 $cert->save + +Serializes the I<Crypt::OpenPGP::Certificate> object I<$cert> into a +string of octets, suitable for saving in a keyring file. + +=head2 Crypt::OpenPGP::Certificate->parse($buffer) + +Given I<$buffer>, a I<Crypt::OpenPGP::Buffer> object holding (or with +offset point to) a certificate packet, returns a new object of type +I<Crypt::OpenPGP::Certificate>, initialized with the data from the +buffer. + +=head2 $cert->lock($passphrase) + +Locks the secret key data by encrypting that data with I<$passphrase>. + +Returns true on success, C<undef> on failure; in the case of failure +call I<errstr> to get the error message. + +=head2 $cert->unlock($passphrase) + +Uses the passphrase I<$passphrase> to unlock (decrypt) the secret +part of the key. + +Returns true on success, C<undef> on failure; in the case of failure +call I<errstr> to get the error message. + +=head2 $cert->fingerprint + +Returns the key fingerprint as an octet string. + +=head2 $cert->fingerprint_hex + +Returns the key fingerprint as a hex string. + +=head2 $cert->fingerprint_words + +Returns the key fingerprint as a list of English words, where each word +represents one octet from the fingerprint. See I<Crypt::OpenPGP::Words> +for more details about the encoding. + +=head2 $cert->key_id + +Returns the key ID. + +=head2 $cert->key_id_hex + +Returns the key ID as a hex string. + +=head2 $cert->key + +Returns the algorithm-specific portion of the certificate, the public +or secret key object (an object of type I<Crypt::OpenPGP::Key>). + +=head2 $cert->public_cert + +Returns a public version of the certificate, with a public key. If +the certificate was already public, the same certificate is returned; +if it was a secret certificate, a new I<Crypt::OpenPGP::Certificate> +object is created, and the secret key is made into a public version +of the key. + +=head2 $cert->version + +Returns the version of the certificate (C<3> or C<4>). + +=head2 $cert->timestamp + +Returns the creation date and time (in epoch time). + +=head2 $cert->validity + +Returns the number of days that the certificate is valid for version +3 keys. + +=head2 $cert->is_secret + +Returns true if the certificate holds a secret key, false otherwise. + +=head2 $cert->is_protected + +Returns true if the certificate is locked, false otherwise. + +=head2 $cert->is_subkey + +Returns true if the certificate is a subkey, false otherwise. + +=head2 $cert->can_encrypt + +Returns true if the public key algorithm for the certificate I<$cert> +can perform encryption/decryption, false otherwise. + +=head2 $cert->can_sign + +Returns true if the public key algorithm for the certificate I<$cert> +can perform signing/verification, false otherwise. + +=head1 AUTHOR & COPYRIGHTS + +Please see the Crypt::OpenPGP manpage for author, copyright, and +license information. + +=cut |