further perl-only openpgp2ssh work. public keys are now translated.
[monkeysphere.git] / src / keytrans / pem2openpgp
index 94fd3c89de16fdba5bb427b861b89b54ac9d36a9..40188c7ab64dbb7b9224ffd4e50de376dd97bc7d 100755 (executable)
@@ -288,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;
@@ -509,16 +554,22 @@ 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";
+    }
+  }
+
   my $packettag;
   my $dummy;
   my $tag;
 
+  my $key;
+
   while (! eof($instr)) {
     read($instr, $packettag, 1);
     $packettag = ord($packettag);
@@ -528,7 +579,6 @@ sub openpgp2ssh {
       die "This is not an OpenPGP packet\n";
     }
     if (0x40 & $packettag) {
-      print STDERR "This is a new-style packet header\n";
       $tag = (0x3f & $packettag);
       my $nextlen = 0;
       read($instr, $nextlen, 1);
@@ -542,13 +592,12 @@ sub openpgp2ssh {
        $packetlen = (($nextlen - 192) << 8) + ($newoct) + 192;
       } elsif ($nextlen == 255) {
        read($instr, $nextlen, 4);
-       $packetlen = unpack('%L', $nextlen);
+       $packetlen = unpack('N', $nextlen);
       } else {
        # packet length is undefined.
       }
     } else {
       my $lentype;
-      print STDERR "This is an old-style packet header\n";
       $lentype = 0x03 & $packettag;
       $tag = ( 0x3c & $packettag ) >> 2;
       if ($lentype == 0) {
@@ -568,21 +617,63 @@ sub openpgp2ssh {
     if (! defined($packetlen)) {
       die "Undefined packet lengths are not supported.\n";
     }
-    printf(STDERR "Packet is %d long\n", $packetlen);
 
     if ($tag == $packet_types->{pubkey} ||
        $tag == $packet_types->{pub_subkey} ||
        $tag == $packet_types->{seckey} ||
        $tag == $packet_types->{sec_subkey}) {
-      printf(STDERR "Packet type %d\n", $tag);
-      read($instr, $dummy, $packetlen) or die "Could not seek!\n";
+      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 {
-      printf(STDERR "We do not care about this packet.\n");
-      read($instr, $dummy, $packetlen) or die "Could not seek!\n";
+      read($instr, $dummy, $packetlen) or die "Could not skip past this packet!\n";
     }
   }
 
-  print $tag;
+  if (defined($key)) {
+    return "ssh-rsa ".encode_base64(openssh_pubkey_pack($key), '');
+  }
 }
 
 
@@ -619,7 +710,7 @@ for (basename($0)) {
       my $instream;
       open($instream,'-');
       binmode($instream, ":bytes");
-      openpgp2ssh($instream, $fpr);
+      print openpgp2ssh($instream, $fpr);
   }
   else {
     die "Unrecognized keytrans call.\n";