Crypt::OpenPGP::Certificate - PGP Key certificate


Crypt-OpenPGP documentation Contained in the Crypt-OpenPGP distribution.

Index


Code Index:

NAME

Top

Crypt::OpenPGP::Certificate - PGP Key certificate

SYNOPSIS

Top

    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' );

DESCRIPTION

Top

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 Certificate class.

A Crypt::OpenPGP::Certificate object wraps around a 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).

USAGE

Top

Crypt::OpenPGP::Certificate->new( %arg )

Constructs a new PGP key certificate object and returns that object. If no arguments are provided in %arg, the certificate is empty; this is used in parse, for example, to construct an empty object, then fill it with the data in the buffer.

%arg can contain:

* Key

The public/secret key object, an object of type Crypt::OpenPGP::Key.

This argument is required (for a non-empty certificate).

* Version

The certificate packet version, as defined in the OpenPGP RFC. The two valid values are 3 and 4.

This argument is optional; if not provided the default is to produce version 4 certificates. You may wish to override this for compatibility with older versions of PGP.

* 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 0.

* 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 0, which means that the certificate never expires.

* Passphrase

If you are creating a certificate for a secret key--indicated by whether or not the 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 Passphrase is used as the passphrase to lock the key.

This argument is required if the certificate holds a secret key.

* 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 Crypt::OpenPGP::Cipher.

This argument is optional; if not specified, DES3 is used.

$cert->save

Serializes the Crypt::OpenPGP::Certificate object $cert into a string of octets, suitable for saving in a keyring file.

Crypt::OpenPGP::Certificate->parse($buffer)

Given $buffer, a Crypt::OpenPGP::Buffer object holding (or with offset point to) a certificate packet, returns a new object of type Crypt::OpenPGP::Certificate, initialized with the data from the buffer.

$cert->lock($passphrase)

Locks the secret key data by encrypting that data with $passphrase.

Returns true on success, undef on failure; in the case of failure call errstr to get the error message.

$cert->unlock($passphrase)

Uses the passphrase $passphrase to unlock (decrypt) the secret part of the key.

Returns true on success, undef on failure; in the case of failure call errstr to get the error message.

$cert->fingerprint

Returns the key fingerprint as an octet string.

$cert->fingerprint_hex

Returns the key fingerprint as a hex string.

$cert->fingerprint_words

Returns the key fingerprint as a list of English words, where each word represents one octet from the fingerprint. See Crypt::OpenPGP::Words for more details about the encoding.

$cert->key_id

Returns the key ID.

$cert->key_id_hex

Returns the key ID as a hex string.

$cert->key

Returns the algorithm-specific portion of the certificate, the public or secret key object (an object of type Crypt::OpenPGP::Key).

$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 Crypt::OpenPGP::Certificate object is created, and the secret key is made into a public version of the key.

$cert->version

Returns the version of the certificate (3 or 4).

$cert->timestamp

Returns the creation date and time (in epoch time).

$cert->validity

Returns the number of days that the certificate is valid for version 3 keys.

$cert->is_secret

Returns true if the certificate holds a secret key, false otherwise.

$cert->is_protected

Returns true if the certificate is locked, false otherwise.

$cert->is_subkey

Returns true if the certificate is a subkey, false otherwise.

$cert->can_encrypt

Returns true if the public key algorithm for the certificate $cert can perform encryption/decryption, false otherwise.

$cert->can_sign

Returns true if the public key algorithm for the certificate $cert can perform signing/verification, false otherwise.

AUTHOR & COPYRIGHTS

Top

Please see the Crypt::OpenPGP manpage for author, copyright, and license information.


Crypt-OpenPGP documentation Contained in the Crypt-OpenPGP distribution.

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__