X-Git-Url: https://codewiz.org/gitweb?a=blobdiff_plain;f=src%2Fkeytrans%2Fpem2openpgp;h=40188c7ab64dbb7b9224ffd4e50de376dd97bc7d;hb=b62cb24951ccb9026fa9c2d660398be094a8b62f;hp=3492361ad37994655c53c5c7dee2cbc33d5863af;hpb=e83267c80493b9279bd35e8adf91963d0ec6f0b6;p=monkeysphere.git diff --git a/src/keytrans/pem2openpgp b/src/keytrans/pem2openpgp index 3492361..40188c7 100755 --- a/src/keytrans/pem2openpgp +++ b/src/keytrans/pem2openpgp @@ -23,6 +23,7 @@ use strict; use warnings; +use File::Basename; use Crypt::OpenSSL::RSA; use Crypt::OpenSSL::Bignum; use Crypt::OpenSSL::Bignum::CTX; @@ -287,6 +288,51 @@ sub mpi_pack { return pack('n', $mpilen).$val; } +# takes a Crypt::OpenSSL::Bignum, returns an MPI packed in preparation +# for an OpenSSH-style public key format. see: +# http://marc.info/?l=openssh-unix-dev&m=121866301718839&w=2 +sub openssh_mpi_pack { + my $num = shift; + + my $val = $num->to_bin(); + my $mpilen = length($val); + + my $ret = pack('N', $mpilen); + + # if the first bit of the leading byte is high, we should include a + # 0 byte: + if (ord($val) & 0x80) { + $ret = pack('NC', $mpilen+1, 0); + } + + return $ret.$val; +} + +sub openssh_pubkey_pack { + my $key = shift; + + my ($modulus, $exponent) = $key->get_key_parameters(); + + return openssh_mpi_pack(Crypt::OpenSSL::Bignum->new_from_bin("ssh-rsa")). + openssh_mpi_pack($exponent). + openssh_mpi_pack($modulus); + } + +# pull an OpenPGP-specified MPI off of a given stream, returning it as +# a Crypt::OpenSSL::Bignum. +sub read_mpi { + my $instr = shift; + + my $bitlen; + read($instr, $bitlen, 2) or die "could not read MPI length.\n"; + $bitlen = unpack('n', $bitlen); + + my $ret; + read($instr, $ret, ($bitlen + 7)/8) or die "could not read MPI body.\n"; + return Crypt::OpenSSL::Bignum->new_from_bin($ret); +} + + # FIXME: genericize these to accept either RSA or DSA keys: sub make_rsa_pub_key_body { my $key = shift; @@ -508,27 +554,166 @@ sub pem2openpgp { } -my $rsa; -if (defined $ENV{PEM2OPENPGP_NEWKEY}) { - $rsa = Crypt::OpenSSL::RSA->generate_key($ENV{PEM2OPENPGP_NEWKEY}); -} else { - # slurp in the entire stdin: - undef $/; - my $stdin = ; +sub openpgp2ssh { + my $instr = shift; + my $fpr = shift; + + if (defined $fpr) { + if (length($fpr) < 8) { + die "We need at least 8 hex digits of fingerprint.\n"; + } + } + + my $packettag; + my $dummy; + my $tag; - $rsa = Crypt::OpenSSL::RSA->new_private_key($stdin); + my $key; + + while (! eof($instr)) { + read($instr, $packettag, 1); + $packettag = ord($packettag); + + my $packetlen; + if ( ! (0x80 & $packettag)) { + die "This is not an OpenPGP packet\n"; + } + if (0x40 & $packettag) { + $tag = (0x3f & $packettag); + my $nextlen = 0; + read($instr, $nextlen, 1); + $nextlen = ord($nextlen); + if ($nextlen < 192) { + $packetlen = $nextlen; + } elsif ($nextlen < 224) { + my $newoct; + read($instr, $newoct, 1); + $newoct = ord($newoct); + $packetlen = (($nextlen - 192) << 8) + ($newoct) + 192; + } elsif ($nextlen == 255) { + read($instr, $nextlen, 4); + $packetlen = unpack('N', $nextlen); + } else { + # packet length is undefined. + } + } else { + my $lentype; + $lentype = 0x03 & $packettag; + $tag = ( 0x3c & $packettag ) >> 2; + if ($lentype == 0) { + read($instr, $packetlen, 1) or die "could not read packet length\n"; + $packetlen = unpack('C', $packetlen); + } elsif ($lentype == 1) { + read($instr, $packetlen, 2) or die "could not read packet length\n"; + $packetlen = unpack('n', $packetlen); + } elsif ($lentype == 2) { + read($instr, $packetlen, 4) or die "could not read packet length\n"; + $packetlen = unpack('N', $packetlen); + } else { + # packet length is undefined. + } + } + + if (! defined($packetlen)) { + die "Undefined packet lengths are not supported.\n"; + } + + if ($tag == $packet_types->{pubkey} || + $tag == $packet_types->{pub_subkey} || + $tag == $packet_types->{seckey} || + $tag == $packet_types->{sec_subkey}) { + my $ver; + read($instr, $ver, 1) or die "could not read key version\n"; + $ver = ord($ver); + if ($ver != 4) { + printf(STDERR "We only work with version 4 keys. This key appears to be version $ver.\n"); + read($instr, $dummy, $packetlen - 1) or die "Could not skip past this packet.\n"; + } else { + + my $timestamp; + read($instr, $timestamp, 4) or die "could not read key timestamp.\n"; + $timestamp = unpack('N', $timestamp); + + my $algo; + read($instr, $algo, 1) or die "could not read key algorithm.\n"; + $algo = ord($algo); + if ($algo != $asym_algos->{rsa}) { + printf(STDERR "We only support RSA keys (this key used algorithm %d).\n", $algo); + read($instr, $dummy, $packetlen - 6) or die "Could not skip past this packet.\n"; + } else { + ## we have an RSA key. + my $modulus = read_mpi($instr); + my $exponent = read_mpi($instr); + + my $pubkey = Crypt::OpenSSL::RSA->new_key_from_parameters($modulus, $exponent); + my $foundfpr = fingerprint($pubkey, $timestamp); + + my $foundfprstr = Crypt::OpenSSL::Bignum->new_from_bin($foundfpr)->to_hex(); + + # is this a match? + if ((!defined($fpr)) || + (substr($foundfprstr, -1 * length($fpr)) eq $fpr)) { + if (defined($key)) { + die "Found two matching keys.\n"; + } + $key = $pubkey; + } + + if ($tag == $packet_types->{seckey} || + $tag == $packet_types->{sec_subkey}) { + die "Cannot deal with secret keys yet!\n"; + } + + } + } + } else { + read($instr, $dummy, $packetlen) or die "Could not skip past this packet!\n"; + } + } + + if (defined($key)) { + return "ssh-rsa ".encode_base64(openssh_pubkey_pack($key), ''); + } } -my $uid = shift; -# FIXME: fail if there is no given user ID; or should we default to -# hostname_long() from Sys::Hostname::Long ? +for (basename($0)) { + if (/^pem2openpgp$/) { + my $rsa; + my $stdin; + if (defined $ENV{PEM2OPENPGP_NEWKEY}) { + $rsa = Crypt::OpenSSL::RSA->generate_key($ENV{PEM2OPENPGP_NEWKEY}); + } else { + $stdin = do { + local $/; # slurp! + ; + }; + + $rsa = Crypt::OpenSSL::RSA->new_private_key($stdin); + } + + my $uid = shift; -print pem2openpgp($rsa, - $uid, - { timestamp => $ENV{PEM2OPENPGP_TIMESTAMP}, - expiration => $ENV{PEM2OPENPGP_EXPIRATION}, - usage_flags => $ENV{PEM2OPENPGP_USAGE_FLAGS}, - } - ); + # FIXME: fail if there is no given user ID; or should we default to + # hostname_long() from Sys::Hostname::Long ? + + print pem2openpgp($rsa, + $uid, + { timestamp => $ENV{PEM2OPENPGP_TIMESTAMP}, + expiration => $ENV{PEM2OPENPGP_EXPIRATION}, + usage_flags => $ENV{PEM2OPENPGP_USAGE_FLAGS}, + } + ); + } + elsif (/^openpgp2ssh$/) { + my $fpr = shift; + my $instream; + open($instream,'-'); + binmode($instream, ":bytes"); + print openpgp2ssh($instream, $fpr); + } + else { + die "Unrecognized keytrans call.\n"; + } +}