use bytes in pem2openpgp to ensure that length calculations are done by octet and...
[monkeysphere.git] / src / keytrans / pem2openpgp
index 59f9bb0db7f5f5bb14cff55aa56b05345b571383..f6e2d4f33d3f79f2cbc7d59fa61dba06936a8dbb 100755 (executable)
@@ -20,6 +20,84 @@ use Crypt::OpenSSL::Bignum;
 use Digest::SHA1;
 use MIME::Base64;
 
+## make sure all length() and substr() calls use bytes only:
+use bytes;
+
+# make an old-style packet out of the given packet type and body.
+# old-style  (see RFC 4880 section 4.2)
+sub make_packet {
+  my $type = shift;
+  my $body = shift;
+
+  my $len = length($body);
+
+  my $lenbytes;
+  my $lencode;
+
+  if ($len < 2**8) {
+    $lenbytes = 0;
+    $lencode = 'C';
+  } elsif ($len < 2**16) {
+    $lenbytes = 1;
+    $lencode = 'n';
+  } elsif ($len < 2**31) {
+    ## not testing against full 32 bits because i don't want to deal
+    ## with potential overflow.
+    $lenbytes = 2;
+    $lencode = 'N';
+  } else {
+    ## what the hell do we do here?
+    $lenbytes = 3;
+    $lencode = '';
+  }
+
+  return pack('C'.$lencode, 0x80 + ($type * 4) + $lenbytes, $len).
+    $body;
+}
+
+
+# takes a Crypt::OpenSSL::Bignum, returns it formatted as OpenPGP MPI
+# (RFC 4880 section 3.2)
+sub mpi_pack {
+  my $num = shift;
+
+  my $val = $num->to_bin();
+  my $mpilen = length($val)*8;
+
+# this is a kludgy way to get the number of significant bits in the
+# first byte:
+  my $bitsinfirstbyte = length(sprintf("%b", ord($val)));
+
+  $mpilen -= (8 - $bitsinfirstbyte);
+
+  return pack('n', $mpilen).$val;
+}
+
+# FIXME: genericize this to accept either RSA or DSA keys:
+sub make_rsa_key_body {
+  my $key = shift;
+  my $timestamp = shift;
+
+  my ($n, $e) = $key->get_key_parameters();
+
+  return
+    pack('CN', 4, $timestamp).
+      pack('C', 1). # RSA
+       mpi_pack($n).
+         mpi_pack($e);
+
+}
+
+# expects an RSA key (public or private) and a timestamp
+sub fingerprint {
+  my $key = shift;
+  my $timestamp = shift;
+
+  my $rsabody = make_rsa_key_body($key, $timestamp);
+
+  return Digest::SHA1::sha1_hex(pack('Cn', 0x99, length($rsabody)).$rsabody);
+}
+
 my $holdTerminator = $/;
 undef $/;
 my $buf = <STDIN>;
@@ -78,7 +156,7 @@ my $features = pack('CCC', 2, 30, 1);
 # keyserver preference: only owner modify (???):
 my $keyserver_pref = pack('CCC', 2, 23, 0x80);
 
-my $subpackets_to_be_hashed = 
+my $subpackets_to_be_hashed =
   $creation_time_packet.
   $usage_packet.
   $expiration_packet.
@@ -88,7 +166,6 @@ my $subpackets_to_be_hashed =
   $features.
   $keyserver_pref;
 
-#FIXME: what's the right way to get length()?
 my $subpacket_octets = pack('n', length($subpackets_to_be_hashed));
 
 my $sig_data_to_be_hashed =
@@ -99,16 +176,13 @@ my $sig_data_to_be_hashed =
   $subpacket_octets.
   $subpackets_to_be_hashed;
 
+my $pubkey = make_rsa_key_body($rsa, $timestamp);
 
-my ($n, $e, $d, $p, $q) = $rsa->get_key_parameters();
-
+#open(KEYFILE, "</home/wt215/gpg-test/key-data");
+my $key_data = make_packet(6, $pubkey);
 
-open(KEYFILE, "</home/wt215/gpg-test/key-data");
-my $key_data = <KEYFILE>;
-
-# FIXME: $keyid should be generated from the public key instead of
-# hardcoded:
-my $keyid = '5616d7cb02e69446';
+# take the last 16 characters of the fingerprint as the keyid:
+my $keyid = substr(fingerprint($rsa, $timestamp), 40 - 16, 16);
 
 # the v4 signature trailer is:
 
@@ -116,8 +190,6 @@ my $keyid = '5616d7cb02e69446';
 # signature data itself.
 my $trailer = pack('CCN', 4, 0xff, length($sig_data_to_be_hashed));
 
-# FIXME: length() is probably not right here either in the event that
-# the uid uses unicode.
 my $uid_data =
   pack('CN', 0xb4, length($uid)).
   $uid;
@@ -133,48 +205,17 @@ my $data_hash = Digest::SHA1::sha1_hex($datatosign);
 
 my $issuer_packet = pack('CCH16', 9, 16, $keyid);
 
-my $sig = $rsa->sign($datatosign);
-
-my $bigsig = Crypt::OpenSSL::Bignum->new_from_bin($sig);
-
-
-my $hex = $bigsig->to_hex();
-
-my $mpilen = length($hex)*4;
-
-# this is a kludgy way to get the number of bits in the first byte:
-my $bitsinfirstbyte = length(sprintf("%b", hex(substr $hex, 0, 2)));
-
-$mpilen -= (8 - $bitsinfirstbyte);
-
-# emit two octets representing $mpilen, followed by the signature itself:
-
+my $sig = Crypt::OpenSSL::Bignum->new_from_bin($rsa->sign($datatosign));
 
 my $sig_body =
   $sig_data_to_be_hashed.
-# FIXME: another dubious length() call.
   pack('n', length($issuer_packet)).
   $issuer_packet.
   pack('n', hex(substr($data_hash, 0, 4))).
-  pack("n" , $mpilen).
-  $sig;
-
-# FIXME: yet another length():
-my $len = length($sig_body);
-
-my $header;
-
-if ($len < 2**8) {
-  $header = pack('CC', 0x88, $len);
-} elsif ($len < 2**16) {
-  $header = pack('Cn', 0x89, $len);
-} elsif ($len < 2**31) {
-  $header = pack('CN', 0x8a, $len);
-} else {
-  # what the hell do we do here?
-  $header = pack('C', 0x8b);
-}
+  mpi_pack($sig);
 
-print $header.$sig_body;
+print make_packet(6, $pubkey);
+print make_packet(13, $uid);
+print make_packet(2, $sig_body);
 
 $/ = $holdTerminator;