outputting secret key material now with perl-only openpgp2ssh.
[monkeysphere.git] / src / keytrans / pem2openpgp
index 4dda6caea0076a84656491d11afb1c1ebebfbb5f..37b372a5e1ad5afba65cf76a2867737d587f60e3 100755 (executable)
@@ -288,6 +288,55 @@ 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 $readtally = shift;
+
+  my $bitlen;
+  read($instr, $bitlen, 2) or die "could not read MPI length.\n";
+  $bitlen = unpack('n', $bitlen);
+  $$readtally += 2;
+
+  my $bytestoread = ($bitlen + 7)/8;
+  my $ret;
+  read($instr, $ret, $bytestoread) or die "could not read MPI body.\n";
+  $$readtally += $bytestoread;
+  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;
@@ -509,9 +558,170 @@ sub pem2openpgp {
 }
 
 
+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";
+    }
+    $fpr = uc($fpr);
+  }
+
+  my $packettag;
+  my $dummy;
+  my $tag;
+
+  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;
+      my $readbytes = 0;
+      read($instr, $ver, 1) or die "could not read key version\n";
+      $readbytes += 1;
+      $ver = ord($ver);
+
+      if ($ver != 4) {
+       printf(STDERR "We only work with version 4 keys.  This key appears to be version %s.\n", $ver);
+       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";
+       $readbytes += 4;
+       $timestamp = unpack('N', $timestamp);
+
+       my $algo;
+       read($instr, $algo, 1) or die "could not read key algorithm.\n";
+       $readbytes += 1;
+       $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 - $readbytes) or die "Could not skip past this packet.\n";
+       } else {
+         ## we have an RSA key.
+         my $modulus = read_mpi($instr, \$readbytes);
+         my $exponent = read_mpi($instr, \$readbytes);
+
+         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}) {
+           if (!defined($key)) { # we don't think the public part of
+                                  # this key matches
+             read($instr, $dummy, $packetlen - $readbytes) or die "Could not skip past this packet.\n";
+           } else {
+             my $s2k;
+             read($instr, $s2k, 1) or die "Could not read S2K octet.\n";
+             $readbytes += 1;
+             $s2k = ord($s2k);
+             if ($s2k == 0) {
+               # secret material is unencrypted
+               # see http://tools.ietf.org/html/rfc4880#section-5.5.3
+               my $d = read_mpi($instr, \$readbytes);
+               my $p = read_mpi($instr, \$readbytes);
+               my $q = read_mpi($instr, \$readbytes);
+               my $u = read_mpi($instr, \$readbytes);
+
+               my $checksum;
+               read($instr, $checksum, 2) or die "Could not read checksum of secret key material.\n";
+               $readbytes += 2;
+               $checksum = unpack('n', $checksum);
+
+               # FIXME: compare with the checksum!  how?  the data is
+               # gone into the Crypt::OpenSSL::Bignum
+
+               $key = Crypt::OpenSSL::RSA->new_key_from_parameters($modulus,
+                                                                   $exponent,
+                                                                   $d,
+                                                                   $p,
+                                                                   $q);
+
+               $key->check_key() or die "Secret key is not a valid RSA key.\n";
+             } else {
+               print(STDERR "We cannot handle encrypted secret keys.  Skipping!\n") ;
+               read($instr, $dummy, $packetlen - $readbytes) or die "Could not skip past this packet.\n";
+             }
+           }
+         }
+
+       }
+      }
+    } else {
+      read($instr, $dummy, $packetlen) or die "Could not skip past this packet!\n";
+    }
+  }
+
+  return $key;
+}
+
+
 for (basename($0)) {
   if (/^pem2openpgp$/) {
-
     my $rsa;
     my $stdin;
     if (defined $ENV{PEM2OPENPGP_NEWKEY}) {
@@ -539,11 +749,23 @@ for (basename($0)) {
                     );
   }
   elsif (/^openpgp2ssh$/) {
-    print STDERR "woo\n";
+      my $fpr = shift;
+      my $instream;
+      open($instream,'-');
+      binmode($instream, ":bytes");
+      my $key = openpgp2ssh($instream, $fpr);
+      if (defined($key)) {
+       if ($key->is_private()) {
+         print $key->get_private_key_string();
+       } else {
+         print "ssh-rsa ".encode_base64(openssh_pubkey_pack($key), '')."\n";
+       }
+      } else {
+       die "No matching key found.\n";
+      }
   }
   else {
-    print STDERR "Unrecognized keytrans call.\n";
-    die 1;
+    die "Unrecognized keytrans call.\n";
   }
 }