From efb99a4677ec05fb481e50bbb739f066c4025d25 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 7 Jan 2009 19:24:05 -0500 Subject: pem2openpgp: make lookup tables of relevant parameters. --- src/keytrans/pem2openpgp | 105 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 101 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/keytrans/pem2openpgp b/src/keytrans/pem2openpgp index 2fa221d..e76ba6f 100755 --- a/src/keytrans/pem2openpgp +++ b/src/keytrans/pem2openpgp @@ -107,10 +107,43 @@ sub fingerprint { return Digest::SHA1::sha1(pack('Cn', 0x99, length($rsabody)).$rsabody); } -# FIXME: make tables of relevant identifiers: digest algorithms, -# ciphers, asymmetric crypto, packet types, subpacket types, signature -# types. As these are created, replace the opaque numbers below with -# semantically-meaningful code. +# FIXME: replace the opaque numbers below with +# semantically-meaningful references based on these tables. + +# see RFC 4880 section 9.1 (ignoring deprecated algorithms for now) +my $asym_algos = { rsa => 1, + elgamal => 16, + dsa => 17, + } + +# see RFC 4880 section 9.2 +my $ciphers = { plaintext => 0, + idea => 1, + 3des => 2, + cast5 => 3, + blowfish => 4, + aes128 => 7, + aes192 => 8, + aes256 => 9 + twofish => 10, + }; + +# see RFC 4880 section 9.3 +my $zips = { uncompressed => 0, + zip => 1, + zlib => 2, + bzip2 => 3, + }; + +# see RFC 4880 section 9.4 +my $digests = { md5 => 1, + sha1 => 2, + ripemd160 => 3, + sha256 => 8, + sha384 => 9, + sha512 => 10, + sha224 => 11, + }; # see RFC 4880 section 5.2.3.21 my $usage_flags = { certify => 0x01, @@ -124,6 +157,70 @@ my $usage_flags = { certify => 0x01, }; +# see RFC 4880 section 4.3 +my $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 +my $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.1 +my $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, + 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, + }; + # we're just not dealing with newline business right now. slurp in # the whole file. undef $/; -- cgit v1.2.3 From 4a7350c9ae0b789210583af169071c43d2c43ab4 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 8 Jan 2009 13:36:49 -0500 Subject: fix stupid typos; switch padding during rsa signatures to that specified in RFC 4880 --- src/keytrans/pem2openpgp | 56 +++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/keytrans/pem2openpgp b/src/keytrans/pem2openpgp index e76ba6f..382e14f 100755 --- a/src/keytrans/pem2openpgp +++ b/src/keytrans/pem2openpgp @@ -114,17 +114,17 @@ sub fingerprint { my $asym_algos = { rsa => 1, elgamal => 16, dsa => 17, - } + }; # see RFC 4880 section 9.2 my $ciphers = { plaintext => 0, idea => 1, - 3des => 2, + tripledes => 2, cast5 => 3, blowfish => 4, aes128 => 7, aes192 => 8, - aes256 => 9 + aes256 => 9, twofish => 10, }; @@ -156,7 +156,6 @@ my $usage_flags = { certify => 0x01, shared => 0x80, # more than one person holds the entire private key }; - # see RFC 4880 section 4.3 my $packet_types = { pubkey_enc_session => 1, sig => 2, @@ -197,28 +196,28 @@ my $sig_types = { binary_doc => 0x00, # see RFC 4880 section 5.2.3.1 -my $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, - 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, +my $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, + 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, }; # we're just not dealing with newline business right now. slurp in @@ -230,7 +229,10 @@ my $buf = ; my $rsa = Crypt::OpenSSL::RSA->new_private_key($buf); $rsa->use_sha1_hash(); -$rsa->use_no_padding(); + +# see page 22 of RFC 4880 for why i think this is the right padding +# choice to use: +$rsa->use_pkcs1_padding(); if (! $rsa->check_key()) { die "key does not check"; -- cgit v1.2.3 From 3f5960cf4ba2f938c677c27e3296e6feae2f56aa Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sat, 10 Jan 2009 18:42:57 -0500 Subject: pem2openpgp: replace raw numbers with semantic labelling to make it more readable. --- src/keytrans/pem2openpgp | 61 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/keytrans/pem2openpgp b/src/keytrans/pem2openpgp index 382e14f..637eba2 100755 --- a/src/keytrans/pem2openpgp +++ b/src/keytrans/pem2openpgp @@ -208,6 +208,7 @@ my $subpacket_types = { sig_creation_time => 2, issuer => 16, notation => 20, preferred_digest => 21, + preferred_compression => 22, keyserver_prefs => 23, preferred_keyserver => 24, primary_uid => 25, @@ -220,6 +221,14 @@ my $subpacket_types = { sig_creation_time => 2, embedded_signature => 32, }; +# bitstring (see RFC 4880 section 5.2.3.24) +my $features = { mdc => 0x01 + }; + +# bitstring (see RFC 4880 5.2.3.17) +my $keyserver_prefs = { nomodify => 0x80 + }; + # we're just not dealing with newline business right now. slurp in # the whole file. undef $/; @@ -240,11 +249,11 @@ if (! $rsa->check_key()) { my $version = pack('C', 4); # strong assertion of identity: -my $sigtype = pack('C', 0x13); +my $sigtype = pack('C', $sig_types->{positive_certification}); # RSA -my $pubkey_algo = pack('C', 1); +my $pubkey_algo = pack('C', $asym_algos->{rsa}); # SHA1 -my $hash_algo = pack('C', 2); +my $hash_algo = pack('C', $digests->{sha1}); # FIXME: i'm worried about generating a bazillion new OpenPGP # certificates from the same key, which could easily happen if you run @@ -254,36 +263,51 @@ my $hash_algo = pack('C', 2); # could an environment variable (if set) override the current time? my $timestamp = time(); -my $creation_time_packet = pack('CCN', 5, 2, $timestamp); +my $creation_time_packet = pack('CCN', 5, $subpacket_types->{sig_creation_time}, $timestamp); # FIXME: HARDCODED: what if someone wants to select a different set of # usage flags? For now, we do only authentication. -my $flags = $usage_flags->{authenticate}; -my $usage_packet = pack('CCC', 2, 27, $flags); +my $usage_packet = pack('CCC', 2, $subpacket_types->{usage_flags}, $usage_flags->{authenticate}); # FIXME: HARDCODED: how should we determine how far off to set the # expiration date? default is to expire in 2 days, which is insanely # short (but good for testing). my $expires_in = 86400*2; -my $expiration_packet = pack('CCN', 5, 9, $expires_in); +my $expiration_packet = pack('CCN', 5, $subpacket_types->{key_expiration_time}, $expires_in); # prefer AES-256, AES-192, AES-128, CAST5, 3DES: -my $pref_sym_algos = pack('CCCCCCC', 6, 11, 9, 8, 7, 3, 2); +my $pref_sym_algos = pack('CCCCCCC', 6, $subpacket_types->{preferred_cipher}, + $ciphers->{aes256}, + $ciphers->{aes192}, + $ciphers->{aes128}, + $ciphers->{cast5}, + $ciphers->{tripledes} + ); # prefer SHA-1, SHA-256, RIPE-MD/160 -my $pref_hash_algos = pack('CCCCC', 4, 21, 2, 8, 3); +my $pref_hash_algos = pack('CCCCC', 4, $subpacket_types->{preferred_digest}, + $digests->{sha1}, + $digests->{sha256}, + $digests->{ripemd160} + ); # prefer ZLIB, BZip2, ZIP -my $pref_zip_algos = pack('CCCCC', 4, 22, 2, 3, 1); +my $pref_zip_algos = pack('CCCCC', 4, $subpacket_types->{preferred_compression}, + $zips->{zlib}, + $zips->{bzip2}, + $zips->{zip} + ); # we support the MDC feature: -my $features = pack('CCC', 2, 30, 1); +my $feature_subpacket = pack('CCC', 2, $subpacket_types->{features}, + $features->{mdc}); # keyserver preference: only owner modify (???): -my $keyserver_pref = pack('CCC', 2, 23, 0x80); +my $keyserver_pref = pack('CCC', 2, $subpacket_types->{keyserver_prefs}, + $keyserver_prefs->{nomodify}); my $subpackets_to_be_hashed = $creation_time_packet. @@ -292,7 +316,7 @@ my $subpackets_to_be_hashed = $pref_sym_algos. $pref_hash_algos. $pref_zip_algos. - $features. + $feature_subpacket. $keyserver_pref; my $subpacket_octets = pack('n', length($subpackets_to_be_hashed)); @@ -307,8 +331,7 @@ my $sig_data_to_be_hashed = my $pubkey = make_rsa_pub_key_body($rsa, $timestamp); -#open(KEYFILE, "{pubkey}, $pubkey); # take the last 8 bytes of the fingerprint as the keyid: my $keyid = substr(fingerprint($rsa, $timestamp), 20 - 8, 8); @@ -332,7 +355,7 @@ my $datatosign = my $data_hash = Digest::SHA1::sha1_hex($datatosign); -my $issuer_packet = pack('CCa8', 9, 16, $keyid); +my $issuer_packet = pack('CCa8', 9, $subpacket_types->{issuer}, $keyid); my $sig = Crypt::OpenSSL::Bignum->new_from_bin($rsa->sign($datatosign)); @@ -344,8 +367,8 @@ my $sig_body = mpi_pack($sig); print - make_packet(6, $pubkey). - make_packet(13, $uid). - make_packet(2, $sig_body); + make_packet($packet_types->{pubkey}, $pubkey). + make_packet($packet_types->{uid}, $uid). + make_packet($packet_types->{sig}, $sig_body); -- cgit v1.2.3 From 4af5666101d302692f76671c08188141289f13f3 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sun, 11 Jan 2009 20:10:34 -0500 Subject: pem2openpgp: reorganized some code, put in initial function to try to create secret keys. we seem to be a bit of modular arithmetic away from creating private keys in an OpenPGP-style format. --- src/keytrans/pem2openpgp | 191 ++++++++++++++++++++++++++++------------------- 1 file changed, 115 insertions(+), 76 deletions(-) (limited to 'src') diff --git a/src/keytrans/pem2openpgp b/src/keytrans/pem2openpgp index 637eba2..fa92297 100755 --- a/src/keytrans/pem2openpgp +++ b/src/keytrans/pem2openpgp @@ -32,83 +32,7 @@ my $uid = shift; # FIXME: fail if there is no given user ID; or should we default to # hostname_long() from Sys::Hostname::Long ? -# 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_pub_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_pub_key_body($key, $timestamp); - - return Digest::SHA1::sha1(pack('Cn', 0x99, length($rsabody)).$rsabody); -} - -# FIXME: replace the opaque numbers below with -# semantically-meaningful references based on these tables. # see RFC 4880 section 9.1 (ignoring deprecated algorithms for now) my $asym_algos = { rsa => 1, @@ -229,6 +153,120 @@ my $features = { mdc => 0x01 my $keyserver_prefs = { nomodify => 0x80 }; +###### end lookup tables ###### + +# FIXME: if we want to be able to interpret openpgp data as well as +# produce it, we need to produce key/value-swapped lookup tables as well. + + +# 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; +} + +# see the bottom of page 43 of RFC 4880 +sub simple_checksum { + my $bytes = shift; + + return unpack("%C*",$bytes) % 65536; +} + +# FIXME: genericize these to accept either RSA or DSA keys: +sub make_rsa_pub_key_body { + my $key = shift; + my $timestamp = shift; + + my ($n, $e) = $key->get_key_parameters(); + + return + pack('CN', 4, $timestamp). + pack('C', $asym_algos->{rsa}). + mpi_pack($n). + mpi_pack($e); +} +sub make_rsa_sec_key_body { + my $key = shift; + my $timestamp = shift; + + # we're not using $a and $b, but we need them to get to $c. + my ($n, $e, $d, $p, $q, $a, $b, $c) = $key->get_key_parameters(); + + my $secret_material = mpi_pack($d). + mpi_pack($p). + mpi_pack($q). + mpi_pack($c); + + # FIXME: according to Crypt::OpenSSL::RSA, $c is 1/q mod p; but + # according to sec 5.5.3 of RFC 4880, this last argument should + # instead be: u, the multiplicative inverse of p, mod q. i don't + # see a simple way to generate this number from the perl module + # directly yet. + + return + pack('CN', 4, $timestamp). + pack('C', $asym_algos->{rsa}). + mpi_pack($n). + mpi_pack($e). + pack('C', 0). # seckey material is not encrypted -- see RFC 4880 sec 5.5.3 + $secret_material. + simple_checksum($secret_material); +} + +# expects an RSA key (public or private) and a timestamp +sub fingerprint { + my $key = shift; + my $timestamp = shift; + + my $rsabody = make_rsa_pub_key_body($key, $timestamp); + + return Digest::SHA1::sha1(pack('Cn', 0x99, length($rsabody)).$rsabody); +} + # we're just not dealing with newline business right now. slurp in # the whole file. undef $/; @@ -330,6 +368,7 @@ my $sig_data_to_be_hashed = $subpackets_to_be_hashed; my $pubkey = make_rsa_pub_key_body($rsa, $timestamp); +my $seckey = make_rsa_sec_key_body($rsa, $timestamp); my $key_data = make_packet($packet_types->{pubkey}, $pubkey); -- cgit v1.2.3 From ae9a949163f6850c7ef6a260d6d7b086a622d787 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sun, 11 Jan 2009 23:01:10 -0500 Subject: pem2openpgp: implemented extended euclidean algorithm to find modular multiplicative inverse. this lets us compute the value we need for secret key material. --- src/keytrans/pem2openpgp | 49 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/keytrans/pem2openpgp b/src/keytrans/pem2openpgp index fa92297..c74ca25 100755 --- a/src/keytrans/pem2openpgp +++ b/src/keytrans/pem2openpgp @@ -21,6 +21,7 @@ use strict; use warnings; use Crypt::OpenSSL::RSA; use Crypt::OpenSSL::Bignum; +use Crypt::OpenSSL::Bignum::CTX; use Digest::SHA1; use MIME::Base64; @@ -213,7 +214,7 @@ sub mpi_pack { sub simple_checksum { my $bytes = shift; - return unpack("%C*",$bytes) % 65536; + return unpack("%32W*",$bytes) % 65536; } # FIXME: genericize these to accept either RSA or DSA keys: @@ -229,6 +230,46 @@ sub make_rsa_pub_key_body { mpi_pack($n). mpi_pack($e); } + +# calculate the multiplicative inverse of a mod b this is euclid's +# extended algorithm. For more information see: +# http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm the +# arguments here should be Crypt::OpenSSL::Bignum objects. $a should +# be the larger of the two values, and the two values should be +# coprime. + +sub modular_multi_inverse { + my $a = shift; + my $b = shift; + + my $ctx = Crypt::OpenSSL::Bignum::CTX->new(); + my $x = Crypt::OpenSSL::Bignum->zero(); + my $y = Crypt::OpenSSL::Bignum->one(); + my $lastx = Crypt::OpenSSL::Bignum->one(); + my $lasty = Crypt::OpenSSL::Bignum->zero(); + + while (! $b->is_zero()) { + my ($quotient, $remainder) = $a->div($b, $ctx); + + $a = $b; + $b = $remainder; + + my $temp = $x; + $x = $lastx->sub($quotient->mul($x, $ctx)); + $lastx = $temp; + + $temp = $y; + $y = $lasty->sub($quotient->mul($y, $ctx)); + $lasty = $temp; + } + + if (!$a->is_one()) { + die "did this math wrong.\n"; + } + + return $lastx; +} + sub make_rsa_sec_key_body { my $key = shift; my $timestamp = shift; @@ -239,7 +280,7 @@ sub make_rsa_sec_key_body { my $secret_material = mpi_pack($d). mpi_pack($p). mpi_pack($q). - mpi_pack($c); + mpi_pack(modular_multi_inverse($p, $q)); # FIXME: according to Crypt::OpenSSL::RSA, $c is 1/q mod p; but # according to sec 5.5.3 of RFC 4880, this last argument should @@ -254,7 +295,7 @@ sub make_rsa_sec_key_body { mpi_pack($e). pack('C', 0). # seckey material is not encrypted -- see RFC 4880 sec 5.5.3 $secret_material. - simple_checksum($secret_material); + pack('n', simple_checksum($secret_material)); } # expects an RSA key (public or private) and a timestamp @@ -406,7 +447,7 @@ my $sig_body = mpi_pack($sig); print - make_packet($packet_types->{pubkey}, $pubkey). + make_packet($packet_types->{seckey}, $seckey). make_packet($packet_types->{uid}, $uid). make_packet($packet_types->{sig}, $sig_body); -- cgit v1.2.3 From c2e9fab1b3e8f0c254a41e7875d4aaf9bb5b2419 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sun, 11 Jan 2009 23:05:44 -0500 Subject: pem2openpgp: cleaning up some comments, not fetching unnecessary parameters from OpenSSL. --- src/keytrans/pem2openpgp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/keytrans/pem2openpgp b/src/keytrans/pem2openpgp index c74ca25..4cc6f1d 100755 --- a/src/keytrans/pem2openpgp +++ b/src/keytrans/pem2openpgp @@ -275,18 +275,18 @@ sub make_rsa_sec_key_body { my $timestamp = shift; # we're not using $a and $b, but we need them to get to $c. - my ($n, $e, $d, $p, $q, $a, $b, $c) = $key->get_key_parameters(); + my ($n, $e, $d, $p, $q) = $key->get_key_parameters(); my $secret_material = mpi_pack($d). mpi_pack($p). mpi_pack($q). mpi_pack(modular_multi_inverse($p, $q)); - # FIXME: according to Crypt::OpenSSL::RSA, $c is 1/q mod p; but - # according to sec 5.5.3 of RFC 4880, this last argument should - # instead be: u, the multiplicative inverse of p, mod q. i don't - # see a simple way to generate this number from the perl module - # directly yet. + # according to Crypt::OpenSSL::RSA, the closest value we can get out + # of get_key_parameters is 1/q mod p; but according to sec 5.5.3 of + # RFC 4880, we're actually looking for u, the multiplicative inverse + # of p, mod q. This is why we're calculating the value directly + # with modular_multi_inverse. return pack('CN', 4, $timestamp). -- cgit v1.2.3 From 71afa5c8ef69365ee7db26d865f277f053198739 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sun, 11 Jan 2009 23:27:41 -0500 Subject: pem2openpgp: reorganization, cleanup of comments, adding a warning about secret material on stdout --- src/keytrans/pem2openpgp | 121 ++++++++++++++++++++++++++--------------------- 1 file changed, 68 insertions(+), 53 deletions(-) (limited to 'src') diff --git a/src/keytrans/pem2openpgp b/src/keytrans/pem2openpgp index 4cc6f1d..3d9f6f8 100755 --- a/src/keytrans/pem2openpgp +++ b/src/keytrans/pem2openpgp @@ -1,8 +1,12 @@ #!/usr/bin/perl -w -T # pem2openpgp: take a PEM-encoded RSA private-key on standard input, a -# User ID as the first argument, and generate an OpenPGP certificate -# from it. +# User ID as the first argument, and generate an OpenPGP secret key +# and certificate from it. + +# WARNING: the secret key material *will* appear on stdout (albeit in +# OpenPGP form) -- if you redirect stdout to a file, make sure the +# permissions on that file are appropriately locked down! # Usage: @@ -160,6 +164,58 @@ my $keyserver_prefs = { nomodify => 0x80 # produce it, we need to produce key/value-swapped lookup tables as well. +########### Math/Utility Functions ############## + + +# see the bottom of page 43 of RFC 4880 +sub simple_checksum { + my $bytes = shift; + + return unpack("%32W*",$bytes) % 65536; +} + +# calculate the multiplicative inverse of a mod b this is euclid's +# extended algorithm. For more information see: +# http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm the +# arguments here should be Crypt::OpenSSL::Bignum objects. $a should +# be the larger of the two values, and the two values should be +# coprime. + +sub modular_multi_inverse { + my $a = shift; + my $b = shift; + + my $ctx = Crypt::OpenSSL::Bignum::CTX->new(); + my $x = Crypt::OpenSSL::Bignum->zero(); + my $y = Crypt::OpenSSL::Bignum->one(); + my $lastx = Crypt::OpenSSL::Bignum->one(); + my $lasty = Crypt::OpenSSL::Bignum->zero(); + + while (! $b->is_zero()) { + my ($quotient, $remainder) = $a->div($b, $ctx); + + $a = $b; + $b = $remainder; + + my $temp = $x; + $x = $lastx->sub($quotient->mul($x, $ctx)); + $lastx = $temp; + + $temp = $y; + $y = $lasty->sub($quotient->mul($y, $ctx)); + $lasty = $temp; + } + + if (!$a->is_one()) { + die "did this math wrong.\n"; + } + + return $lastx; +} + + +############ OpenPGP formatting functions ############ + # make an old-style packet out of the given packet type and body. # old-style (see RFC 4880 section 4.2) sub make_packet { @@ -210,13 +266,6 @@ sub mpi_pack { return pack('n', $mpilen).$val; } -# see the bottom of page 43 of RFC 4880 -sub simple_checksum { - my $bytes = shift; - - return unpack("%32W*",$bytes) % 65536; -} - # FIXME: genericize these to accept either RSA or DSA keys: sub make_rsa_pub_key_body { my $key = shift; @@ -231,45 +280,6 @@ sub make_rsa_pub_key_body { mpi_pack($e); } -# calculate the multiplicative inverse of a mod b this is euclid's -# extended algorithm. For more information see: -# http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm the -# arguments here should be Crypt::OpenSSL::Bignum objects. $a should -# be the larger of the two values, and the two values should be -# coprime. - -sub modular_multi_inverse { - my $a = shift; - my $b = shift; - - my $ctx = Crypt::OpenSSL::Bignum::CTX->new(); - my $x = Crypt::OpenSSL::Bignum->zero(); - my $y = Crypt::OpenSSL::Bignum->one(); - my $lastx = Crypt::OpenSSL::Bignum->one(); - my $lasty = Crypt::OpenSSL::Bignum->zero(); - - while (! $b->is_zero()) { - my ($quotient, $remainder) = $a->div($b, $ctx); - - $a = $b; - $b = $remainder; - - my $temp = $x; - $x = $lastx->sub($quotient->mul($x, $ctx)); - $lastx = $temp; - - $temp = $y; - $y = $lasty->sub($quotient->mul($y, $ctx)); - $lasty = $temp; - } - - if (!$a->is_one()) { - die "did this math wrong.\n"; - } - - return $lastx; -} - sub make_rsa_sec_key_body { my $key = shift; my $timestamp = shift; @@ -336,23 +346,28 @@ my $hash_algo = pack('C', $digests->{sha1}); # FIXME: i'm worried about generating a bazillion new OpenPGP # certificates from the same key, which could easily happen if you run -# this script more than once against the same key. How can we prevent -# this? +# this script more than once against the same key (because the +# timestamps will differ). How can we prevent this? -# could an environment variable (if set) override the current time? +# could an environment variable (if set) override the current time, to +# be able to create a standard key? If we read the key from a file +# instead of stdin, should we use the creation time on the file? my $timestamp = time(); my $creation_time_packet = pack('CCN', 5, $subpacket_types->{sig_creation_time}, $timestamp); # FIXME: HARDCODED: what if someone wants to select a different set of -# usage flags? For now, we do only authentication. +# usage flags? For now, we do only authentication because that's what +# monkeysphere needs. my $usage_packet = pack('CCC', 2, $subpacket_types->{usage_flags}, $usage_flags->{authenticate}); # FIXME: HARDCODED: how should we determine how far off to set the # expiration date? default is to expire in 2 days, which is insanely -# short (but good for testing). +# short (but good for testing). The user ought to be able to decide +# this directly, rather than having to do "monkeysphere-server +# extend-key". my $expires_in = 86400*2; my $expiration_packet = pack('CCN', 5, $subpacket_types->{key_expiration_time}, $expires_in); -- cgit v1.2.3 From dbb58c04bf0a06071c740495636b55775023979d Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Thu, 15 Jan 2009 22:32:45 -0500 Subject: work on fleshing out the new functionality for the next release, including new functions: import-key, add-revoker, revoke-key, etc. --- src/monkeysphere | 52 ++++++++++++++-- src/monkeysphere-server | 154 +++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 180 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/monkeysphere b/src/monkeysphere index 5444cb0..463a1b1 100755 --- a/src/monkeysphere +++ b/src/monkeysphere @@ -41,6 +41,9 @@ Monkeysphere client tool. subcommands: update-known_hosts (k) [HOST]... update known_hosts file update-authorized_keys (a) update authorized_keys file + import-subkey (i) import existing ssh key as gpg subkey + --keyfile (-f) FILE key file to import + --expire (-e) EXPIRE date to expire gen-subkey (g) [KEYID] generate an authentication subkey --length (-l) BITS key length in bits (2048) --expire (-e) EXPIRE date to expire @@ -51,6 +54,47 @@ subcommands: EOF } +# import an existing ssh key as a gpg subkey +import_subkey() { + local keyFile="~/.ssh/id_rsa" + local keyExpire + local keyID + local gpgOut + local userID + + # get options + while true ; do + case "$1" in + -f|--keyfile) + keyFile="$2" + shift 2 + ;; + -e|--expire) + keyExpire="$2" + shift 2 + ;; + *) + if [ "$(echo "$1" | cut -c 1)" = '-' ] ; then + failure "Unknown option '$1'. +Type '$PGRM help' for usage." + fi + break + ;; + esac + done + + log verbose "importing ssh key..." + fifoDir=$(mktemp -d ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX) + (umask 077 && mkfifo "$fifoDir/pass") + ssh2openpgp | gpg --passphrase-fd 3 3< "$fifoDir/pass" --expert --command-fd 0 --import & + + passphrase_prompt "Please enter your passphrase for $keyID: " "$fifoDir/pass" + + rm -rf "$fifoDir" + wait + log verbose "done." +} + # generate a subkey with the 'a' usage flags set gen_subkey(){ local keyLength @@ -59,10 +103,6 @@ gen_subkey(){ local gpgOut local userID - # set default key parameter values - keyLength= - keyExpire= - # get options while true ; do case "$1" in @@ -376,6 +416,10 @@ case $COMMAND in RETURN="$?" ;; + 'import-subkey'|'i') + import_key "$@" + ;; + 'gen-subkey'|'g') gen_subkey "$@" ;; diff --git a/src/monkeysphere-server b/src/monkeysphere-server index ba3fa8d..96f5b56 100755 --- a/src/monkeysphere-server +++ b/src/monkeysphere-server @@ -46,13 +46,20 @@ Monkeysphere server admin tool. subcommands: update-users (u) [USER]... update user authorized_keys files - gen-key (g) [NAME[:PORT]] generate gpg key for the server + import-key (i) import existing ssh key to gpg + --hostname (-h) NAME[:PORT] hostname for key user ID + --keyfile (-f) FILE key file to import + --expire (-e) EXPIRE date to expire + gen-key (g) generate gpg key for the host + --hostname (-h) NAME[:PORT] hostname for key user ID --length (-l) BITS key length in bits (2048) --expire (-e) EXPIRE date to expire --revoker (-r) FINGERPRINT add a revoker - extend-key (e) EXPIRE extend expiration to EXPIRE - add-hostname (n+) NAME[:PORT] add hostname user ID to server key + extend-key (e) EXPIRE extend host key expiration + add-hostname (n+) NAME[:PORT] add hostname user ID to host key revoke-hostname (n-) NAME[:PORT] revoke hostname user ID + add-revoker (o) FINGERPRINT add a revoker to the host key + revoke-key (r) revoke host key show-key (s) output all server host key information publish-key (p) publish server host key to keyserver diagnostics (d) report on server monkeysphere status @@ -64,7 +71,8 @@ subcommands: remove-id-certifier (c-) KEYID remove a certification key list-id-certifiers (c) list certification keys - gpg-authentication-cmd CMD gnupg-authentication command + gpg-authentication-cmd CMD give a gpg command to the + authentication keyring version (v) show version number help (h,?) this help @@ -311,28 +319,107 @@ update_users() { done } +# import an existing ssh key to a gpg key +import_key() { + local hostName=$(hostname -f) + local keyFile="/etc/ssh/ssh_host_rsa_key" + local keyExpire + local userID + + # check for presense of secret key + # FIXME: is this the proper test to be doing here? + fingerprint_server_key >/dev/null \ + && failure "An OpenPGP host key already exists." + + # get options + while true ; do + case "$1" in + -h|--hostname) + hostName="$2" + shift 2 + ;; + -f|--keyfile) + keyFile="$2" + shift 2 + ;; + -e|--expire) + keyExpire="$2" + shift 2 + ;; + *) + if [ "$(echo "$1" | cut -c 1)" = '-' ] ; then + failure "Unknown option '$1'. +Type '$PGRM help' for usage." + fi + break + ;; + esac + done + + if [ ! -f "$keyFile" ] ; then + failure "SSH secret key file '$keyFile' not found." + fi + + userID="ssh://${hostName}" + + # prompt about key expiration if not specified + keyExpire=$(get_gpg_expiration "$keyExpire") + + echo "The following key parameters will be used for the host private key:" + echo "Import: $keyFile" + echo "Name-Real: $userID" + echo "Expire-Date: $keyExpire" + + read -p "Import key? (Y/n) " OK; OK=${OK:=Y} + if [ ${OK/y/Y} != 'Y' ] ; then + failure "aborting." + fi + + log verbose "importing ssh key..." + # translate ssh key to a private key + (umask 077 && \ + pem2openpgp "$userID" "$keyExpire" < "$sshKey" | gpg_host --import) + + # find the key fingerprint of the newly converted key + fingerprint=$(fingerprint_server_key) + + # export host ownertrust to authentication keyring + log verbose "setting ultimate owner trust for host key..." + echo "${fingerprint}:6:" | gpg_host "--import-ownertrust" + echo "${fingerprint}:6:" | gpg_authentication "--import-ownertrust" + + # export public key to file + gpg_authentication "--export-options export-minimal --armor --export 0x${fingerprint}\!" > "${SYSDATADIR}/ssh_host_rsa_key.pub.gpg" + log info "SSH host public key in OpenPGP form: ${SYSDATADIR}/ssh_host_rsa_key.pub.gpg" + + # show info about new key + show_server_key +} + # generate server gpg key gen_key() { - local keyType - local keyLength - local keyUsage + local keyType="RSA" + local keyLength="2048" + local keyUsage="auth" local keyExpire local revoker - local hostName + local hostName=$(hostname -f) local userID local keyParameters local fingerprint - # set default key parameter values - keyType="RSA" - keyLength="2048" - keyUsage="auth" - keyExpire= - revoker= + # check for presense of secret key + # FIXME: is this the proper test to be doing here? + fingerprint_server_key >/dev/null \ + && failure "An OpenPGP host key already exists." # get options while true ; do case "$1" in + -h|--hostname) + hostName="$2" + shift 2 + ;; -l|--length) keyLength="$2" shift 2 @@ -355,14 +442,8 @@ Type '$PGRM help' for usage." esac done - hostName=${1:-$(hostname -f)} userID="ssh://${hostName}" - # check for presense of secret key - # FIXME: is this the proper test to be doing here? - fingerprint_server_key >/dev/null \ - && failure "A key for this host already exists." - # prompt about key expiration if not specified keyExpire=$(get_gpg_expiration "$keyExpire") @@ -399,14 +480,14 @@ Revoker: 1:${revoker} sensitive" %commit %echo done" - log verbose "generating server key..." + log verbose "generating host key..." echo "$keyParameters" | gpg_host --batch --gen-key # find the key fingerprint of the newly generated key fingerprint=$(fingerprint_server_key) # export host ownertrust to authentication keyring - log verbose "setting ultimate owner trust for server key..." + log verbose "setting ultimate owner trust for host key..." echo "${fingerprint}:6:" | gpg_authentication "--import-ownertrust" # translate the private key to ssh format, and export to a file @@ -584,6 +665,18 @@ EOF fi } +# add a revoker to the host key +add_revoker() { + # FIXME: implement! + failure "not implemented yet!" +} + +# revoke the host key +revoke_key() { + # FIXME: implement! + failure "not implemented yet!" +} + # publish server key to keyserver publish_server_key() { read -p "Really publish host key to $KEYSERVER? (y/N) " OK; OK=${OK:=N} @@ -1006,6 +1099,11 @@ case $COMMAND in update_users "$@" ;; + 'import-key'|'i') + check_user + import_key "$@" + ;; + 'gen-key'|'g') check_user gen_key "$@" @@ -1029,6 +1127,18 @@ case $COMMAND in revoke_hostname "$@" ;; + 'add-revoker'|'o') + check_user + check_host_keyring + add_revoker "$@" + ;; + + 'revoke-key'|'r') + check_user + check_host_keyring + revoke_key "$@" + ;; + 'show-key'|'show'|'s') show_server_key ;; -- cgit v1.2.3 From f75a5747a8b99e04c02c475791c476f1fbd2b674 Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Sat, 31 Jan 2009 11:37:22 -0500 Subject: change log level for unacceptable keys that can not be translated to be "debug" instead of "error". --- src/common | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/common b/src/common index eb3a083..815aacc 100644 --- a/src/common +++ b/src/common @@ -674,7 +674,7 @@ process_user_id() { else log debug " - unacceptable primary key." if [ -z "$sshKey" ] ; then - log error " ! primary key could not be translated (not RSA or DSA?)." + log debug " ! primary key could not be translated (not RSA or DSA?)." else echo "1:${sshKey}" fi @@ -732,7 +732,7 @@ process_user_id() { else log debug " - unacceptable sub key." if [ -z "$sshKey" ] ; then - log error " ! sub key could not be translated (not RSA or DSA?)." + log debug " ! sub key could not be translated (not RSA or DSA?)." else echo "1:${sshKey}" fi -- cgit v1.2.3