comparison gpg:pgp6.5.1

sen_ml@eccosys.com sen_ml@eccosys.com
Wed, 19 Jan 2000 12:57:27 +0900


----Next_Part(Wed_Jan_19_12:54:18_2000_809)--
Content-Type: Text/Plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

thanks for the response.  my comments follow...

wk> On Tue, 18 Jan 2000, sen_ml@eccosys.com wrote:

> it's very nice that gpg can handle this. it would be even nicer if
> other rfc 2440 implementations handled it as well (it really shouldn't
wk> I guess you mean PGP 6 with the other rfc2440 implementation. wk> This feature is marked optional and so there is no need for an wk> implementation to support it. yes, i noticed the MAY in the rfc :-) there is no need, but it would be nice for the support to exist in pgp. it would be even nicer if the rfc said MUST as well -- at least for accepting messages w/ such key ids. actually, when the gpg for the !(win) platform becomes as usable (rng-wise, etc.) as the un*x version perhaps i can tell all of my correspondents to switch to gpg! wk> PGP 6 is not rfc2440 compliant: For example the do generate v3 sig wk> atures and have invented a new packet type (Photo ID) which is not wk> defined in OpenPGP nor is it in the range for private extension. the fact that there is a new packet type seems ok to me -- the fact that it is not in the range for private extension seems bad. was this ever discussed in the ietf openpgp work group? it seems like an important point to discuss.
> write it if you can use a library for handling rfc 2440, and that is
> not something that is available yet (iirc, i would love to be wrong
wk> Sorr, you are still right. However I see a demand for such a library. does anyone have any concrete plans for one?
> considered "cryptographic" software? it doesn't seem like it should
> be to me.
wk> No that's good to hear :-) well, it's not much, but i'll attach a tool i found that does do selective zeroing of key ids. wk> and since Friday it seems to be quite easy to get an export wk> license: You only have to send the URL to the BXA. i think many people are anxious to see others try this out first :-) ----Next_Part(Wed_Jan_19_12:54:18_2000_809)-- Content-Type: Text/Plain; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="taf.pl-0.3" #! /usr/bin/perl -w # taf.pl: rfc 2440 packet tweaker # # currently only messes w/ keyids in public-key encrypted session key packets # TODO: # # -anticipate that there will be byte-ordering problems w/ pack()/unpack() # templates -- determine how to work around them if necessary # # -implement handling of partial body lengths for new-format packets # # -implement handling of indeterminate length old-format packets # # -presently data is read from STDIN -- perhaps this should be # generalized... # # -an oo interface for different packets? # # -this whole thing should be written using libgcrypt and xs, but # at the time of this writing, libgrcrypt doesn't exist yet :-P # # -consider using references to strings to reduce memory usage? # # -check for and deal w/ TODO items through file :-) # # NOTES: # # -unless otherwise specified, 'section xxxx' refers to a section in # rfc 2440 ############################################################################ use English; use MIME::Base64; # tweakables :-) my($dearmor_incoming) = 1; my($debug) = 0; # stuff below shouldn't really be tweaked w/ my($program_name) = "Traffic Analysis Foiler"; my($version) = "0.3"; # TODO: should probably lowercase all keyids and check keyid lengths my(@keep_keyids) = @ARGV; my($keyid) = $ARGV[0]; # what's the clever way to read in array values into a hash all in one step? my(%keep_keyids); foreach my $keyid (@keep_keyids) { $keep_keyids{$keyid}++; } # where to keep the parsed packets -- may be this shouldn't be global? my(@packets); # TODO: explain what the two methods of reading and handling incoming data are my($fh); my($stream_string) = ""; my($stream_string_pos) = 0; my($end_of_stream) = 0; my($read_from_filehandle) = !$dearmor_incoming; ############################################################################ ### constants ### my($eol) = "\n"; # the packet tag is one byte long my($packet_tag_length) = 1; # note: 'packet_tag' is defined in rfc 2440 as the first byte in a packet # header see section 4.2 # content tag lookup table my(%content_tag_strings) = ( "0" => "Reserved - a packet tag must not have this value", "1" => "Public-Key Encrypted Session Key Packet", "2" => "Signature Packet", "3" => "Symmetric-Key Encrypted Session Key Packet", "4" => "One-Pass Signature Packet", "5" => "Secret Key Packet", "6" => "Public Key Packet", "7" => "Secret Subkey Packet", "8" => "Compressed Data Packet", "9" => "Symmetrically Encrypted Data Packet", "10" => "Marker Packet", "11" => "Literal Data Packet", "12" => "Trust Packet", "13" => "User ID Packet", "14" => "Public Subkey Packet", "60" => "Private or Experimental Values", "61" => "Private or Experimental Values", "62" => "Private or Experimental Values", "63" => "Private or Experimental Values" ); my($PT_RESERVED) = 0; my($PT_PUBKEY_ESK) = 1; my($PT_SIG) = 2; my($PT_SECKEY_ESK) = 3; my($PT_ONEPASS_SIG) = 4; my($PT_SECKEY) = 5; my($PT_PUBKEY) = 6; my($PT_SECSUBKEY) = 7; my($PT_COMPR_DATA) = 8; my($PT_SYMENC_DATA) = 9; my($PT_MARKER) = 10; my($PT_LIT_DATA) = 11; my($PT_TRUST) = 12; my($PT_USERID) = 13; my($PT_PUBSUBKEY) = 14; my($PT_PRIV_EXP_60) = 60; my($PT_PRIV_EXP_61) = 61; my($PT_PRIV_EXP_62) = 62; my($PT_PRIV_EXP_63) = 63; # for calculating the 24-bit crc -- see section 6.1 my($CRC24_INIT) = 0x00b704ce; my($CRC24_POLY) = 0x00864cfb; ############################################################################ ### subroutines ### sub get_packet_tag_from_packet_header { my($packet_header) = @_; return substr($packet_header, 0, 1); } sub has_top_bit_set # bit 7 (leftmost bit) of packet tag is 1 -- section 4.2 { my($packet_tag) = @_; return ( 1 == ((unpack("C", $packet_tag) >> 7) & 0x01) ); } sub is_old_format_packet # bit 6 of packet tag is 0 => old packet format -- section 4.2 { my($packet_tag) = @_; return ( 0 == ((unpack("C", $packet_tag) >> 6) & 0x01) ); } sub is_new_format_packet # bit 6 of packet tag is 1 => new packet format -- section 4.2 { my($packet_tag) = @_; return ( 1 == ((unpack("C", $packet_tag) >> 6) & 0x01) ); } sub content_tag_for_old { my($packet_tag) = @_; return (unpack("C", $packet_tag) >> 2) & 0x0f; } sub content_tag_for_new { my($packet_tag) = @_; return (unpack("C", $packet_tag) & 0x3f); } sub content_tag_for_packet { my($packet_tag) = @_; if (&is_old_format_packet($packet_tag)) { return &content_tag_for_old($packet_tag); } elsif (&is_new_format_packet($packet_tag)) { return &content_tag_for_new($packet_tag); } else { die "content_tag_for_packet: shouldn't get here"; } } sub length_type_for_old { my($packet_tag) = @_; return (unpack("C", $packet_tag) & 0x03); } sub octets_for_length_for_old { my($packet_tag) = @_; my($length_type) = &length_type_for_old($packet_tag); print STDERR "length type: $length_type\n" if $debug; # see section 4.2.1 if ( 0 == $length_type ) { return 1; } elsif ( 1 == $length_type ) { return 2; } elsif ( 2 == $length_type ) { return 4; } elsif ( 3 == $length_type ) { return 0; # indeterminate length } else { die "octets_for_length_for_old: invalid length type: $length_type"; } } sub check_read_result { my($result, $expected) = @_; # determine whether the read() was successful if ( $expected == $result ) { # we're ok print STDERR "read: $result of $expected bytes read as expected\n" if $debug; } elsif ( 0 == $result ) { die "read: encountered eof"; } elsif ( !defined($result) ) { die "read: error"; } else { # TODO: determine whether it is possible to just keep reading more # bytes... die "read: only $result of $expected bytes read"; } } sub read_bytes_from_filehandle # returns 0 if there are no more bytes to read, # returns 1 if there are more bytes to read { my($buffer_ref, $length) = @_; my($read_result) = read($fh, ${$buffer_ref}, $length); &check_read_result($read_result, $length); if (eof($fh)) { return 0; } else { return 1; } } sub read_bytes_from_string { my($buffer_ref, $length) = @_; if ( ($stream_string_pos + $length) >= length($stream_string) ) # trying to read too much, only read to the end { ${$buffer_ref} = substr($stream_string, $stream_string_pos); $stream_string_pos = length($stream_string); $end_of_stream = 1; return 0; } else # trying to read a reasonable amount :-) { ${$buffer_ref} = substr($stream_string, $stream_string_pos, $length); $stream_string_pos += $length; $end_of_stream = 0; return 1; } } sub read_bytes # returns 0 if there are no more bytes to read, # returns 1 if there are more bytes to read { my($buffer_ref, $length) = @_; if ( $read_from_filehandle ) { return &read_bytes_from_filehandle($buffer_ref, $length); } else { return &read_bytes_from_string; } } sub end_of_bytes # returns 0 if not at end of bytes # returns 1 if at end of bytes { if ( $read_from_filehandle ) { return eof($fh); } else { return $end_of_stream; } } sub get_old_format_packet { my($packet_tag) = @_; my($read_result, $buffer); my($packet_header, $packet_body); print STDERR "encountered old-format packet\n" if $debug; my($octets_for_length) = &octets_for_length_for_old($packet_tag); if ( 0 < $octets_for_length ) { print STDERR "length of packet header - $packet_tag_length is: $octets_for_length\n" if $debug; my($body_length); &read_bytes(\$buffer, $octets_for_length); $packet_header = $packet_tag . $buffer; # extract the total length of the packet if ( 1 == $octets_for_length ) { # 'C' is exactly 1 byte, perlfunc(1p) ($body_length) = unpack("C", $buffer); } elsif ( 2 == $octets_for_length ) { # 'S' is exactly 2 bytes, perlfunc(1p) ($body_length) = unpack("S", $buffer); } elsif ( 4 == $octets_for_length ) { # 'L' is exactly 4 bytes, perlfunc(1p) ($body_length) = unpack("L", $buffer); } else { die "octets for length: $octets_for_length != 1, 2, or 4"; } print STDERR "octets for length is: $octets_for_length\n" if $debug; print STDERR "length of packet body is: $body_length bytes\n" if $debug; &read_bytes(\$buffer, $body_length); $packet_body = $buffer; return ($packet_header, $packet_body); } else { print STDERR "length of packet header is indeterminate\n" if $debug; die "haven't implemented handling of indeterminate length old-format packets yet(?)"; } die "should not reach this point"; } sub get_new_format_packet { my($packet_tag) = @_; my($read_result, $buffer); my($body_length); my($packet_header, $packet_body); print STDERR "encountered new-format packet\n" if $debug; &read_bytes(\$buffer, 1); $packet_header = $packet_tag . $buffer; # interpret as an unsigned byte my($unpacked_value) = unpack("C", $buffer); # a one-octet length -- section 4.2.2.1 if ( ( 0 <= $unpacked_value ) && ( $unpacked_value <= 191 ) ) { $body_length = $unpacked_value; } # a two-octet length -- section 4.2.2.2 elsif ( ( 192 <= $unpacked_value ) && ( $unpacked_value <= 223 ) ) { &read_bytes(\$buffer, 1); $body_length = (($unpacked_value - 192) << 8) + unpack("C", $buffer) + 192; $packet_header .= $buffer; } # a five-octet length -- section 4.2.2.3 elsif ( 255 == $unpacked_value ) { &read_bytes(\$buffer, 4); # compute the body length... my(@bytes) = unpack("CCCC", $buffer); $body_length = ($bytes[0] << 24) + ($bytes[1] << 16) + ($bytes[2] << 8) + $bytes[3]; $packet_header .= $buffer; } # a partial body length -- section 4.2.2.4 elsif ( ( 224 <= $unpacked_value ) && ( $unpacked_value <= 254 ) ) { die "haven't implemented partial body lengths yet"; } # shouldn't get here else { die "unrecognized new-format packet length value: $unpacked_value"; } # TODO: fix this handling to work w/ partial body lengths... &read_bytes(\$buffer, $body_length); $packet_body = $buffer; return ($packet_header, $packet_body); } sub get_next_packet { my($buffer, $read_result); my($packet_tag); my($packet_header, $packet_body); &read_bytes(\$buffer, $packet_tag_length); $packet_tag = $buffer; # just checking to see whether the packet tag looks normal... if (&has_top_bit_set($packet_tag)) { # we're ok print STDERR "top bit of packet tag is set as expected\n" if $debug } else { die "the top bit of packet tag was not set -- violation of rfc 2440"; } # backward-compatibility introduces complexity, film at 11... if (&is_old_format_packet($packet_tag)) { ($packet_header, $packet_body) = &get_old_format_packet($packet_tag); return ($packet_header, $packet_body); } elsif (&is_new_format_packet($packet_tag)) { ($packet_header, $packet_body) = &get_new_format_packet($packet_tag); return ($packet_header, $packet_body); } else { die "rfc2440: implementation error?"; } die "get_next_packet: should not reach this point"; } sub get_keyid # note: this should only be called on a public-key encrypted session key packet { my($packet_body) = @_; return substr($packet_body, 1, 8); } # TODO: deal w/ potential byte-order problems sub raw_keyid_to_hex_string { my($raw_keyid) = @_; return unpack("H16", $raw_keyid); } # TODO: deal w/ potential byte-order problems sub get_keyid_as_hex_string # note: this should only be called on a public-key encrypted session key packet { my($packet_body) = @_; # see section 5.1 return unpack("H16", substr($packet_body, 1, 8)); } # TODO: very rough implementation -- need to think about the interface here... sub inject_keyid # note: this should only be called on a public-key encrypted session key packet # potential byte-ordering problems? { my($packet_body, $keyid_as_hexstring) = @_; my($raw_keyid) = pack("H16", $keyid_as_hexstring); # see section 5.1 substr($packet_body, 1, 8, $raw_keyid); return $packet_body; } # TODO: very rough implementation -- need to think about the interface here... sub throw_keyid # note: this should only be called on a public-key encrypted session key packet { my($packet_body) = @_; return &inject_keyid($packet_body, "00" x 8); } sub selective_keep_keyid # the default action is to throw away keyids -- only leave certain ones # in place (this is useful for those keyids of users who are not using # gnupg and cannot process public-key encrypted session packets that don't # have any keyid information) { my($packet_body) = @_; # see section 5.1 my($keyid_hex_string) = &get_keyid_as_hex_string($packet_body); # check a table for keyids not to remove... if (!exists($keep_keyids{$keyid_hex_string})) { $packet_body = &throw_keyid($packet_body); } return $packet_body; } sub packets_as_string { my($packets_ref) = @_; my($packets_str) = ""; foreach my $packet (@{$packets_ref}) { $packets_str .= $packet->[0] . $packet->[1]; } return $packets_str; } sub dearmor_pgp_message { } sub armor_packets { my($packet_str) = @_; return encode_base64($packet_str); } sub crc24 # see section 6.1 { my($packets_str) = @_; my($crc) = $CRC24_INIT; my($position) = 0; my($string_length) = length($packets_str); while ( $position < $string_length ) { $crc ^= (unpack("C", substr($packets_str, $position, 1)) << 16); foreach (0 .. 7) { $crc <<= 1; if ( $crc & 0x01000000 ) { $crc ^= $CRC24_POLY; } } $position++; } return $crc & 0x00ffffff; } sub armor_crc24_old # TODO: why are the bytes reversed? because we're using a little-endian # machine and stuff on the network is big-endian? { my($crc24) = @_; my($string) = pack("L", $crc24); $string = substr($string, 2, 1) . substr($string, 1, 1) . substr($string, 0, 1); return "=" . encode_base64($string); } sub armor_crc24 { my($crc24) = @_; my($string) = pack("N", $crc24); $string = substr($string, 1, 3); return "=" . encode_base64($string); } sub dump_packets { my($packets_ref) = @_; foreach my $packet (@{$packets_ref}) { print $packet->[0], $packet->[1]; } } sub dump_packet_types { my($packets_ref) = @_; foreach my $packet (@{$packets_ref}) { ($packet_tag) = &get_packet_tag_from_packet_header($packet->[0]); if (&is_old_format_packet($packet_tag)) { print $content_tag_strings{&content_tag_for_old($packet_tag)}, "\n"; } elsif (&is_new_format_packet($packet_tag)) { print $content_tag_strings{&content_tag_for_new($packet_tag)}, "\n"; } else { die "rfc2440: implementation error?"; } } } sub armor_header_line { my($header_line_text) = @_; return "-----BEGIN $header_line_text-----$eol"; } sub armor_tail_line { my($header_line_text) = @_; return "-----END $header_line_text-----$eol"; } sub parse_packets { my($packet_header, $packet_body); while (1) { last if (&end_of_bytes()); ($packet_header, $packet_body) = &get_next_packet(); push(@packets, [$packet_header, $packet_body]); } print STDERR "done parsing packets\n" if $debug; } sub construct_pgp_message { my($packet_str) = &packets_as_string(\@packets); my($crc24) = &crc24($packet_str); return &armor_header_line("PGP MESSAGE") . "Version: $program_name $version$eol" . "$eol" . &armor_packets($packet_str) . &armor_crc24($crc24) . &armor_tail_line("PGP MESSAGE"); } sub do_pass_through { &parse_packets(); print &construct_pgp_message(); } sub do_throw_keyid { &parse_packets(); foreach my $packet (@packets) { my($packet_tag) = &get_packet_tag_from_packet_header($packet->[0]); if ( $PT_PUBKEY_ESK == &content_tag_for_packet($packet_tag) ) { $packet->[1] = &throw_keyid($packet->[1]); } } print &construct_pgp_message(); } sub do_selective_keep_keyid { &parse_packets(); foreach my $packet (@packets) { my($packet_tag) = &get_packet_tag_from_packet_header($packet->[0]); if ( $PT_PUBKEY_ESK == &content_tag_for_packet($packet_tag) ) { $packet->[1] = &selective_keep_keyid($packet->[1]); } } print &construct_pgp_message(); } sub do_inject_keyid { &parse_packets(); foreach my $packet (@packets) { my($packet_tag) = &get_packet_tag_from_packet_header($packet->[0]); if ( $PT_PUBKEY_ESK == &content_tag_for_packet($packet_tag) ) { $packet->[1] = &inject_keyid($packet->[1], $keyid); } } print &construct_pgp_message(); } ############################################################################ ### main ### # TODO: clean this up! if ( $read_from_filehandle ) { print STDERR "not dearmoring\n" if $debug; # is there a nicer way to write this? $fh = *STDIN; } else { print STDERR "dearmoring\n" if $debug; my($line) = ""; $line = <STDIN>; # TODO: very draconian...what if there are blanks lines first? # (or other things...) if ($line !~ m/^-----BEGIN/) { die "didn't detect armor"; } $INPUT_RECORD_SEPARATOR = $eol x 2; # skip over any comments $line = <STDIN>; $INPUT_RECORD_SEPARATOR = $eol; while ($line = <STDIN>) { chomp($line); # are we there yet? if ($line =~ m/^=/) { # TODO: think about whether to save the crc -- should be in $line last; } $stream_string .= $line; } print STDERR "done dearmoring\n" if $debug; # for storing the contents of pgp data read in $stream_string = decode_base64($stream_string); # for keeping track of the current position in a string containing the # pgp data read in $stream_string_pos = 0; } &do_pass_through(); #&do_throw_keyid(); #&do_selective_keep_keyid(); #&do_inject_keyid(); exit 0; ----Next_Part(Wed_Jan_19_12:54:18_2000_809)----