X-Git-Url: https://codewiz.org/gitweb?a=blobdiff_plain;f=src%2Fshare%2Fkeytrans;h=3e6bdf6ba95ed8560e908e2ade313f03efb3090d;hb=028617f7160596fabfc5f9123a4cc9a6445aaa59;hp=8bf17fb2df6b2a2a212b1af218eac6eaad8d70d8;hpb=2c427b22f6a780cbf0d4e22fce26071727e985a1;p=monkeysphere.git diff --git a/src/share/keytrans b/src/share/keytrans index 8bf17fb..3e6bdf6 100755 --- a/src/share/keytrans +++ b/src/share/keytrans @@ -1,4 +1,14 @@ -#!/usr/bin/perl -w -T +#!/usr/bin/perl -T + +# keytrans: this is an RSA key translation utility; it is capable of +# transforming RSA keys (both public keys and secret keys) between +# several popular representations, including OpenPGP, PEM-encoded +# PKCS#1 DER, and OpenSSH-style public key lines. + +# How it behaves depends on the name under which it is invoked. The +# two implementations currently are: pem2openpgp and openpgp2ssh. + + # pem2openpgp: take a PEM-encoded RSA private-key on standard input, a # User ID as the first argument, and generate an OpenPGP secret key @@ -12,6 +22,23 @@ # pem2openpgp 'ssh://'$(hostname -f) < /etc/ssh/ssh_host_rsa_key | gpg --import + + + +# openpgp2ssh: take a stream of OpenPGP packets containing public or +# secret key material on standard input, and a Key ID (or fingerprint) +# as the first argument. Find the matching key in the input stream, +# and emit it on stdout in an OpenSSH-compatible format. If the input +# key is an OpenPGP public key (either primary or subkey), the output +# will be an OpenSSH single-line public key. If the input key is an +# OpenPGP secret key, the output will be a PEM-encoded RSA key. + +# Example usage: + +# gpg --export-secret-subkeys --export-options export-reset-subkey-passwd $KEYID | \ +# openpgp2ssh $KEYID | ssh-add /dev/stdin + + # Authors: # Jameson Rollins # Daniel Kahn Gillmor @@ -27,7 +54,7 @@ use File::Basename; use Crypt::OpenSSL::RSA; use Crypt::OpenSSL::Bignum; use Crypt::OpenSSL::Bignum::CTX; -use Digest::SHA1; +use Digest::SHA; use MIME::Base64; use POSIX; @@ -168,11 +195,11 @@ my $keyserver_prefs = { nomodify => 0x80 ########### Math/Utility Functions ############## -# see the bottom of page 43 of RFC 4880 +# see the bottom of page 44 of RFC 4880 (http://tools.ietf.org/html/rfc4880#page-44) sub simple_checksum { my $bytes = shift; - return unpack("%32W*",$bytes) % 65536; + return unpack("%16C*",$bytes); } # calculate the multiplicative inverse of a mod b this is euclid's @@ -341,12 +368,12 @@ sub read_mpi { # FIXME: genericize these to accept either RSA or DSA keys: sub make_rsa_pub_key_body { my $key = shift; - my $timestamp = shift; + my $key_timestamp = shift; my ($n, $e) = $key->get_key_parameters(); return - pack('CN', 4, $timestamp). + pack('CN', 4, $key_timestamp). pack('C', $asym_algos->{rsa}). mpi_pack($n). mpi_pack($e); @@ -354,7 +381,7 @@ sub make_rsa_pub_key_body { sub make_rsa_sec_key_body { my $key = shift; - my $timestamp = shift; + my $key_timestamp = shift; # we're not using $a and $b, but we need them to get to $c. my ($n, $e, $d, $p, $q) = $key->get_key_parameters(); @@ -373,7 +400,7 @@ sub make_rsa_sec_key_body { # with modular_multi_inverse. return - pack('CN', 4, $timestamp). + pack('CN', 4, $key_timestamp). pack('C', $asym_algos->{rsa}). mpi_pack($n). mpi_pack($e). @@ -385,11 +412,11 @@ sub make_rsa_sec_key_body { # expects an RSA key (public or private) and a timestamp sub fingerprint { my $key = shift; - my $timestamp = shift; + my $key_timestamp = shift; - my $rsabody = make_rsa_pub_key_body($key, $timestamp); + my $rsabody = make_rsa_pub_key_body($key, $key_timestamp); - return Digest::SHA1::sha1(pack('Cn', 0x99, length($rsabody)).$rsabody); + return Digest::SHA::sha1(pack('Cn', 0x99, length($rsabody)).$rsabody); } @@ -399,7 +426,7 @@ sub pem2openpgp { my $uid = shift; my $args = shift; - $rsa->use_sha1_hash(); + $rsa->use_sha256_hash(); # see page 22 of RFC 4880 for why i think this is the right padding # choice to use: @@ -409,30 +436,42 @@ sub pem2openpgp { die "key does not check"; } + # strong assertion of identity is the default (for a self-sig): + my $certtype = $sig_types->{positive_certification}; + if (defined $args->{certification_type}) { + $certtype = $args->{certification_type} + 0; + } + my $version = pack('C', 4); - # strong assertion of identity: - my $sigtype = pack('C', $sig_types->{positive_certification}); + my $sigtype = pack('C', $certtype); # RSA my $pubkey_algo = pack('C', $asym_algos->{rsa}); - # SHA1 - my $hash_algo = pack('C', $digests->{sha1}); + # SHA256 + my $hash_algo = pack('C', $digests->{sha256}); # FIXME: i'm worried about generating a bazillion new OpenPGP # certificates from the same key, which could easily happen if you run # this script more than once against the same key (because the # timestamps will differ). How can we prevent this? - # this environment variable (if set) overrides the current time, to - # be able to create a standard key? If we read the key from a file + # this argument (if set) overrides the current time, to + # be able to create a standard key. If we read the key from a file # instead of stdin, should we use the creation time on the file? - my $timestamp = 0; - if (defined $args->{timestamp}) { - $timestamp = ($args->{timestamp} + 0); + my $sig_timestamp = 0; + if (defined $args->{sig_timestamp}) { + $sig_timestamp = ($args->{sig_timestamp} + 0); } else { - $timestamp = time(); + $sig_timestamp = time(); + } + my $key_timestamp = $sig_timestamp; + if (defined $args->{key_timestamp}) { + $key_timestamp = ($args->{key_timestamp} + 0); + } + if ($key_timestamp > $sig_timestamp) { + die "key timestamp must not be later than signature timestamp"; } - my $creation_time_packet = pack('CCN', 5, $subpacket_types->{sig_creation_time}, $timestamp); + my $creation_time_packet = pack('CCN', 5, $subpacket_types->{sig_creation_time}, $sig_timestamp); my $flags = 0; @@ -470,11 +509,14 @@ sub pem2openpgp { $ciphers->{tripledes} ); - # prefer SHA-1, SHA-256, RIPE-MD/160 - my $pref_hash_algos = pack('CCCCC', 4, $subpacket_types->{preferred_digest}, - $digests->{sha1}, + # prefer SHA-512, SHA-384, SHA-256, SHA-224, RIPE-MD/160, SHA-1 + my $pref_hash_algos = pack('CCCCCCCC', 7, $subpacket_types->{preferred_digest}, + $digests->{sha512}, + $digests->{sha384}, $digests->{sha256}, - $digests->{ripemd160} + $digests->{sha224}, + $digests->{ripemd160}, + $digests->{sha1} ); # prefer ZLIB, BZip2, ZIP @@ -512,8 +554,8 @@ sub pem2openpgp { $subpacket_octets. $subpackets_to_be_hashed; - my $pubkey = make_rsa_pub_key_body($rsa, $timestamp); - my $seckey = make_rsa_sec_key_body($rsa, $timestamp); + my $pubkey = make_rsa_pub_key_body($rsa, $key_timestamp); + my $seckey = make_rsa_sec_key_body($rsa, $key_timestamp); # this is for signing. it needs to be an old-style header with a # 2-packet octet count. @@ -521,7 +563,7 @@ sub pem2openpgp { my $key_data = make_packet($packet_types->{pubkey}, $pubkey, {'packet_length'=>2}); # take the last 8 bytes of the fingerprint as the keyid: - my $keyid = substr(fingerprint($rsa, $timestamp), 20 - 8, 8); + my $keyid = substr(fingerprint($rsa, $key_timestamp), 20 - 8, 8); # the v4 signature trailer is: @@ -539,7 +581,7 @@ sub pem2openpgp { $sig_data_to_be_hashed. $trailer; - my $data_hash = Digest::SHA1::sha1_hex($datatosign); + my $data_hash = Digest::SHA::sha256_hex($datatosign); my $issuer_packet = pack('CCa8', 9, $subpacket_types->{issuer}, $keyid); @@ -585,6 +627,7 @@ sub openpgp2ssh { die "This is not an OpenPGP packet\n"; } if (0x40 & $packettag) { + # this is a new-format packet. $tag = (0x3f & $packettag); my $nextlen = 0; read($instr, $nextlen, 1); @@ -603,6 +646,7 @@ sub openpgp2ssh { # packet length is undefined. } } else { + # this is an old-format packet. my $lentype; $lentype = 0x03 & $packettag; $tag = ( 0x3c & $packettag ) >> 2; @@ -639,10 +683,10 @@ sub openpgp2ssh { read($instr, $dummy, $packetlen - $readbytes) or die "Could not skip past this packet.\n"; } else { - my $timestamp; - read($instr, $timestamp, 4) or die "could not read key timestamp.\n"; + my $key_timestamp; + read($instr, $key_timestamp, 4) or die "could not read key timestamp.\n"; $readbytes += 4; - $timestamp = unpack('N', $timestamp); + $key_timestamp = unpack('N', $key_timestamp); my $algo; read($instr, $algo, 1) or die "could not read key algorithm.\n"; @@ -657,9 +701,11 @@ sub openpgp2ssh { my $exponent = read_mpi($instr, \$readbytes); my $pubkey = Crypt::OpenSSL::RSA->new_key_from_parameters($modulus, $exponent); - my $foundfpr = fingerprint($pubkey, $timestamp); + my $foundfpr = fingerprint($pubkey, $key_timestamp); my $foundfprstr = Crypt::OpenSSL::Bignum->new_from_bin($foundfpr)->to_hex(); + # left-pad with 0's to bring up to full 40-char (160-bit) fingerprint: + $foundfprstr = sprintf("%040s", $foundfprstr); # is this a match? if ((!defined($fpr)) || @@ -732,7 +778,6 @@ for (basename($0)) { # FIXME: fail if there is no given user ID; or should we default to # hostname_long() from Sys::Hostname::Long ? - if (defined $ENV{PEM2OPENPGP_NEWKEY}) { $rsa = Crypt::OpenSSL::RSA->generate_key($ENV{PEM2OPENPGP_NEWKEY}); } else { @@ -746,7 +791,8 @@ for (basename($0)) { print pem2openpgp($rsa, $uid, - { timestamp => $ENV{PEM2OPENPGP_TIMESTAMP}, + { sig_timestamp => $ENV{PEM2OPENPGP_TIMESTAMP}, + key_timestamp => $ENV{PEM2OPENPGP_KEY_TIMESTAMP}, expiration => $ENV{PEM2OPENPGP_EXPIRATION}, usage_flags => $ENV{PEM2OPENPGP_USAGE_FLAGS}, }