Crypt::Monkeysphere::OpenPGP : bring in pieces of keytrans for fingerprint computation
authorDaniel Kahn Gillmor <dkg@fifthhorseman.net>
Fri, 11 Mar 2011 07:34:36 +0000 (02:34 -0500)
committerDaniel Kahn Gillmor <dkg@fifthhorseman.net>
Fri, 11 Mar 2011 07:35:11 +0000 (02:35 -0500)
Changelog
Crypt/Monkeysphere/OpenPGP.pm [new file with mode: 0644]
unit-tests/30.fingerprints/fpr.t [new file with mode: 0644]

index 5eef10a86a05a9ffe44c1294f12de3bf03ea33b4..ba076bad2b2f8ac328e64031f2d078319da1ddf7 100644 (file)
--- a/Changelog
+++ b/Changelog
@@ -13,7 +13,9 @@ msva-perl (0.9~pre) upstream;
    - unit tests and unit test harness
 
   [ Daniel Kahn Gillmor ]
-  * now depending on Crypt::X509 0.50 for pubkey components directly.
+  * Now depending on Crypt::X509 0.50 for pubkey components directly.
+  * Crypt::Monkeysphere::OpenPGP for helper functions in
+    packet generation and parsing.
 
  -- Daniel Kahn Gillmor <dkg@fifthhorseman.net>  Fri, 11 Mar 2011 01:24:55 -0500
 
diff --git a/Crypt/Monkeysphere/OpenPGP.pm b/Crypt/Monkeysphere/OpenPGP.pm
new file mode 100644 (file)
index 0000000..59dbcc9
--- /dev/null
@@ -0,0 +1,186 @@
+package Crypt::Monkeysphere::OpenPGP;
+
+use strict;
+use warnings;
+
+use Math::BigInt;
+use Digest::SHA;
+
+## WARNING!  This entire module has an unstable API at the moment.
+## Please do not rely on it, as it may change in the near future.
+
+
+my $tables = {
+              # see RFC 4880 section 9.1 (ignoring deprecated algorithms for now)
+              asym_algos => { rsa => 1,
+                              elgamal => 16,
+                              dsa => 17,
+                            },
+
+              # see RFC 4880 section 9.2
+              ciphers => { plaintext => 0,
+                           idea => 1,
+                           tripledes => 2,
+                           cast5 => 3,
+                           blowfish => 4,
+                           aes128 => 7,
+                           aes192 => 8,
+                           aes256 => 9,
+                           twofish => 10,
+                         },
+
+              # see RFC 4880 section 9.3
+              compression => { uncompressed => 0,
+                               zip => 1,
+                               zlib => 2,
+                               bzip2 => 3,
+                             },
+
+              # see RFC 4880 section 9.4
+              digests => { md5 => 1,
+                           sha1 => 2,
+                           ripemd160 => 3,
+                           sha256 => 8,
+                           sha384 => 9,
+                           sha512 => 10,
+                           sha224 => 11,
+                         },
+
+              # see RFC 4880 section 5.2.3.21
+              usage_flags => { certify => 0x01,
+                               sign => 0x02,
+                               encrypt_comms => 0x04,
+                               encrypt_storage => 0x08,
+                               encrypt => 0x0c, ## both comms and storage
+                               split => 0x10, # the private key is split via secret sharing
+                               authenticate => 0x20,
+                               shared => 0x80, # more than one person holds the entire private key
+                             },
+
+              # see RFC 4880 section 4.3
+              packet_types => { pubkey_enc_session => 1,
+                                sig => 2,
+                                symkey_enc_session => 3,
+                                onepass_sig => 4,
+                                seckey => 5,
+                                pubkey => 6,
+                                sec_subkey => 7,
+                                compressed_data => 8,
+                                symenc_data => 9,
+                                marker => 10,
+                                literal => 11,
+                                trust => 12,
+                                uid => 13,
+                                pub_subkey => 14,
+                                uat => 17,
+                                symenc_w_integrity => 18,
+                                mdc => 19,
+                              },
+
+              # see RFC 4880 section 5.2.1
+              sig_types => { binary_doc => 0x00,
+                             text_doc => 0x01,
+                             standalone => 0x02,
+                             generic_certification => 0x10,
+                             persona_certification => 0x11,
+                             casual_certification => 0x12,
+                             positive_certification => 0x13,
+                             subkey_binding => 0x18,
+                             primary_key_binding => 0x19,
+                             key_signature => 0x1f,
+                             key_revocation => 0x20,
+                             subkey_revocation => 0x28,
+                             certification_revocation => 0x30,
+                             timestamp => 0x40,
+                             thirdparty => 0x50,
+                           },
+
+              # see RFC 4880 section 5.2.3.23
+              revocation_reasons => { no_reason_specified => 0,
+                                      key_superseded => 1,
+                                      key_compromised => 2,
+                                      key_retired => 3,
+                                      user_id_no_longer_valid => 32,
+                                    },
+
+              # see RFC 4880 section 5.2.3.1
+              subpacket_types => { sig_creation_time => 2,
+                                   sig_expiration_time => 3,
+                                   exportable => 4,
+                                   trust_sig => 5,
+                                   regex => 6,
+                                   revocable => 7,
+                                   key_expiration_time => 9,
+                                   preferred_cipher => 11,
+                                   revocation_key => 12,
+                                   issuer => 16,
+                                   notation => 20,
+                                   preferred_digest => 21,
+                                   preferred_compression => 22,
+                                   keyserver_prefs => 23,
+                                   preferred_keyserver => 24,
+                                   primary_uid => 25,
+                                   policy_uri => 26,
+                                   usage_flags => 27,
+                                   signers_uid => 28,
+                                   revocation_reason => 29,
+                                   features => 30,
+                                   signature_target => 31,
+                                   embedded_signature => 32,
+                                 },
+
+              # bitstring (see RFC 4880 section 5.2.3.24)
+              features => { mdc => 0x01
+                          },
+
+              # bitstring (see RFC 4880 5.2.3.17)
+              keyserver_prefs => { nomodify => 0x80
+                                 },
+             };
+
+
+# takes a Math::BigInt, returns it formatted as OpenPGP MPI
+# (RFC 4880 section 3.2)
+sub mpi_pack {
+  my $num = shift;
+
+  my $hex = $num->as_hex();
+  $hex =~ s/^0x//;
+  # ensure we've got an even multiple of 2 nybbles here.
+  $hex = '0'.$hex
+    if (length($hex) % 2);
+
+  my $val = pack('H*', $hex);
+  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;
+}
+
+sub make_rsa_pub_key_body {
+  my $key = shift;
+  my $key_timestamp = shift;
+
+  return
+    pack('CN', 4, $key_timestamp).
+      pack('C', $tables->{asym_algos}->{rsa}).
+       mpi_pack($key->{modulus}).
+         mpi_pack($key->{exponent});
+}
+
+sub fingerprint {
+  my $key = shift;
+  my $key_timestamp = shift;
+
+  my $rsabody = make_rsa_pub_key_body($key, $key_timestamp);
+
+  return Digest::SHA::sha1(pack('Cn', 0x99, length($rsabody)).$rsabody);
+}
+
+
+1;
diff --git a/unit-tests/30.fingerprints/fpr.t b/unit-tests/30.fingerprints/fpr.t
new file mode 100644 (file)
index 0000000..7218536
--- /dev/null
@@ -0,0 +1,18 @@
+# -*- perl -*-
+use Test::More;
+
+use Crypt::Monkeysphere::OpenPGP;
+use Data::Dumper;
+
+use strict;
+
+my $timestamp = 1299825212;
+my $key = { modulus => Math::BigInt->new('0xcceb95c3c00b8a12c9de4829a803302f76549a50ee9b7ee58ee3a75ed1839d77d2f57b766e9954581d64eb5599ae98326a028831fbadad8065d63bc5a7b8d831e06d363fd9954f271fda1d746674b0ad6e8dff9fc5ddd4608bdf95760372f50897637a379079f3eb2544099a4511fc8af8e5992e15df8eac619b58a9970a3bdb'),
+            exponent => Math::BigInt->new('0x10001'),
+          };
+plan tests =>1;
+
+is(unpack('H*', Crypt::Monkeysphere::OpenPGP::fingerprint($key, $timestamp)),"10cc971bbbb37b9152e8e759a2882699b47c6497");
+
+
+