diff options
author | o <o@immerda.ch> | 2011-06-30 20:28:19 +0200 |
---|---|---|
committer | o <o@immerda.ch> | 2011-06-30 20:28:19 +0200 |
commit | 3fe777cbfd99bd6a12240273cdfc3600993775ca (patch) | |
tree | 9300ac6633891a325a0362f866ea96dc0641a38f | |
parent | dc9ea45018bbd02df01991e94469da1ff53a291f (diff) |
implemented secretKey parsing which is quite annoying. the length
has to be calculated since all length info is encrypted together with
the secret key.
23 files changed, 279 insertions, 132 deletions
diff --git a/lib/fpg/_preload.rb b/lib/fpg/_preload.rb index ab676ef..e8cb784 100644 --- a/lib/fpg/_preload.rb +++ b/lib/fpg/_preload.rb @@ -1,7 +1,3 @@ module FPG require 'bindata' - module Algos - class Algo < BinData::Record ; end - class Rsa < Algo ; end - end end diff --git a/lib/fpg/algos/_symmetric.rb b/lib/fpg/algos/_symmetric.rb new file mode 100644 index 0000000..acd9a13 --- /dev/null +++ b/lib/fpg/algos/_symmetric.rb @@ -0,0 +1,7 @@ +module FPG + module Algos + class Symmetric + include TaggedSubclasses + end + end +end diff --git a/lib/fpg/algos/asymmetric.rb b/lib/fpg/algos/asymmetric.rb new file mode 100644 index 0000000..4daa794 --- /dev/null +++ b/lib/fpg/algos/asymmetric.rb @@ -0,0 +1,7 @@ +module FPG + module Algos + class Asymmetric + include TaggedSubclasses + end + end +end diff --git a/lib/fpg/algos/cast5.rb b/lib/fpg/algos/cast5.rb new file mode 100644 index 0000000..d07712f --- /dev/null +++ b/lib/fpg/algos/cast5.rb @@ -0,0 +1,12 @@ +module FPG + module Algos + class Cast5 < Symmetric + def block_size + 8 + end + def self.tag + 3 + end + end + end +end diff --git a/lib/fpg/algos/rsa.rb b/lib/fpg/algos/rsa.rb new file mode 100644 index 0000000..49131d0 --- /dev/null +++ b/lib/fpg/algos/rsa.rb @@ -0,0 +1,9 @@ +module FPG + module Algos + class Rsa < Asymmetric + def self.tag + 1 + end + end + end +end diff --git a/lib/fpg/fields/key_id.rb b/lib/fpg/fields/key_id.rb new file mode 100644 index 0000000..66bb4a5 --- /dev/null +++ b/lib/fpg/fields/key_id.rb @@ -0,0 +1,8 @@ +module FPG + module Fields + class KeyId < BinData::Record + endian :big + uint64 :id + end + end +end diff --git a/lib/fpg/fields/packet_header.rb b/lib/fpg/fields/packet_header.rb new file mode 100644 index 0000000..bf0dd17 --- /dev/null +++ b/lib/fpg/fields/packet_header.rb @@ -0,0 +1,92 @@ +module FPG + module Parse + class PacketHeader < BinData::BasePrimitive + def read_and_return_value(io) + @io = io + header = io.readbytes(1).unpack("C1")[0] + parse_id(header) + parse_header(header) + {:packet_size=>@byte_size,:packet_num=>@id,:header_size=>@header_size} + end + def sensible_default + 0 + end + def value_to_binary_string(value) + #Dummy implementation to calculate the size + return ".." if value[:packet_size] < 192 + return "..." if value[:packet_size] < 8383 + return "....." if value[:packet_size] < 4294967295 + ".." + end + + private + + def parse_header(header) + @byte_size = packet_size(header) + @byte_size += strip_partials if partial_length? + end + def parse_id(byte) + raise "this is not a valid packet header" unless byte>>7 == 1 + if new_packet?(byte) + @id = byte-(1<<7)-(1<<6) + else + @id = (byte-(1<<7))>>2 + end + end + def strip_partials + package_start = @io.raw_io.pos + start = package_start + @byte_size + partial_size = 0 + + while partial_length? do + @io.raw_io.pos= start+partial_size + + size = new_packet_size + partial_size += size[:s] + + #remove the header packets from the stream => subclasses wont have to worry abouth this + @io.raw_io.zap(size[:h]) + end + @io.raw_io.pos= package_start + partial_size + end + def partial_length? + @partial_length + end + def packet_size(header) + if new_packet?(header) + size = new_packet_size + @header_size = size[:h]+1 + size[:s] + else + tag = old_size_tag(header) + @header_size = [2,3,5][tag] + old_packet_size(tag) + end + end + def new_packet_size + @partial_length= false + first = @io.readbytes(1).unpack("C1")[0] + return { :s => first, :h => 1 } if first < 192 + return { :s => (((first-192)<<8)+@io.readbytes(1).unpack("C1")[0]+192), :h => 2 } if first < 224 + return { :s => ((first+@io.readbytes(3)).unpack("N")[0]), :h => 4 } if first == 255 + @partial_length= true + return { :s => (1<<(first&0x1f)), :h => 1 } + end + def old_packet_size(tag) + return @io.readbytes(1).unpack("C1")[0] if tag == 0 + return @io.readbytes(2).unpack("n")[0] if tag == 1 + return @io.readbytes(4).unpack("N")[0] if tag == 2 + #tag==3 => size is until eof + raise 'partial size for old packets not implemented' + end + def old_size_tag(header) + #size tag = two lowest bits + header & 3 + end + def new_packet?(byte) + byte>>6 == 3 + end + end + end +end diff --git a/lib/fpg/public_key_algos/rsa.rb b/lib/fpg/fields/rsa.rb index 528e808..f2c7c25 100644 --- a/lib/fpg/public_key_algos/rsa.rb +++ b/lib/fpg/fields/rsa.rb @@ -1,12 +1,8 @@ module FPG - module Algos - class Rsa < Algo + module Fields + class Rsa < BinData::Record multi_precision_integer :n multi_precision_integer :e - - def self.tag - 1 - end end end end diff --git a/lib/fpg/fields/rsa_secret.rb b/lib/fpg/fields/rsa_secret.rb new file mode 100644 index 0000000..9ecb175 --- /dev/null +++ b/lib/fpg/fields/rsa_secret.rb @@ -0,0 +1,10 @@ +module FPG + module Fields + class RsaSecret < BinData::Record + multi_precision_integer :d + multi_precision_integer :p + multi_precision_integer :q + multi_precision_integer :u + end + end +end diff --git a/lib/fpg/fields/rsa_session.rb b/lib/fpg/fields/rsa_session.rb new file mode 100644 index 0000000..13397a4 --- /dev/null +++ b/lib/fpg/fields/rsa_session.rb @@ -0,0 +1,7 @@ +module FPG + module Fields + class RsaSession < BinData::Record + multi_precision_integer :session_key + end + end +end diff --git a/lib/fpg/fields/s2k_specifier.rb b/lib/fpg/fields/s2k_specifier.rb new file mode 100644 index 0000000..68fb0e2 --- /dev/null +++ b/lib/fpg/fields/s2k_specifier.rb @@ -0,0 +1,12 @@ +module FPG + module Fields + class S2kSpecifier < BinData::Record + endian :big + uint8 :id + uint8 :hash_algo + uint64 :salt, :onlyif => lambda { id > 0 } + uint8 :iterations, :onlyif => lambda { id > 2 } + + end + end +end diff --git a/lib/fpg/io/zappable_byte_stream.rb b/lib/fpg/io/zappable_byte_stream.rb index 73ba0b6..5358602 100644 --- a/lib/fpg/io/zappable_byte_stream.rb +++ b/lib/fpg/io/zappable_byte_stream.rb @@ -39,8 +39,17 @@ module FPG io.pos= to_internal_pos(position) end def seek(n,wh) - raise 'only support relative positioning' unless wh == IO::SEEK_CUR - self.pos= @pos+n + case wh + when IO::SEEK_CUR + self.pos= @pos+n + when IO::SEEK_END + io.seek(n,IO::SEEK_END) + @pos = io.pos - zapped.size + rewind_to_next_byte + when IO::SEEK_SET + self.pos= n + else + raise "positioning not supported #{wh}" + end end private @@ -54,6 +63,14 @@ module FPG def skip_to_next_byte io.readbyte while zapped[io.pos] end + def rewind_to_next_byte + i = 0 + while zapped[io.pos] do + io.seek(-1,IO::SEEK_CUR) + i += 1 + end + i + end end end end diff --git a/lib/fpg/packets/_packet.rb b/lib/fpg/packets/_packet.rb index f1b93a7..7a8b09d 100644 --- a/lib/fpg/packets/_packet.rb +++ b/lib/fpg/packets/_packet.rb @@ -3,92 +3,17 @@ module FPG require 'bindata' class Packet < BinData::Record include TaggedSubclasses - endian :big - - attr_accessor :byte_size, :stream, :partial_length + packet_header :header - def parse_header - @byte_size = packet_size - @byte_size += strip_partials if partial_length? - end - def read(stream) - puts "parsing #{self.class}" - @stream = stream - parse_header - start = @stream.pos - puts "size is #{byte_size}" - super(@stream) - raise 'parsing failed. packet length does not match with parsed content' if start+byte_size != @stream.pos - puts "done" - end - def skip - stream.seek(byte_size,IO::SEEK_CUR) - end - def parse_content - fail "shoul be implemented by subclass #{self.class}" - end - - private - - def strip_partials - package_start = @stream.pos - start = package_start + @byte_size - partial_size = 0 - - @stream = ZappableByteStream.new(stream) - - while partial_length? do - @stream.pos= start+partial_size - - size = new_packet_size - partial_size += size[:s] - - #remove the header packets from the stream => subclasses wont have to worry abouth this - @stream.zap(size[:h]) - end - @stream.pos= package_start - partial_size - end - def partial_length? - partial_length - end - def packet_size - header = stream.getbyte - if new_packet?(header) - new_packet_size()[:s] - else - tag = old_size_tag(header) - old_packet_size(tag) - end - end - def new_packet_size - @partial_length= false - first = stream.getbyte - return { :s => first, :h => 1 } if first < 192 - return { :s => (((first-192)<<8)+stream.getbyte+192), :h => 2 } if first < 224 - return { :s => ((first+stream.read(3)).unpack("N")[0]), :h => 4 } if first == 255 - @partial_length= true - return { :s => (1<<(first&0x1f)), :h => 1 } - end - def old_packet_size(tag) - return stream.getbyte if tag == 0 - return stream.read(2).unpack("n")[0] if tag == 1 - return stream.read(4).unpack("N")[0] if tag == 2 - #tag==3 => size is until eof - #TODO this seems a bit cumbersome - current_pos = stream.pos - stream.seek(0,IO::SEEK_END) - size = 1+stream.pos - current_pos - stream.pos= current_pos - return size - end - def old_size_tag(header) - #size tag = two lowest bits - header & 3 - end - def new_packet?(byte) - byte>>6 == 3 + def byte_size + header[:packet_size] + end + def read(io) + pos = io.pos + super + parse_offset = io.pos-pos-byte_size-header[:header_size] + raise "parse err #{self.class}: off by #{parse_offset} bytes" if parse_offset != 0 end end end diff --git a/lib/fpg/packets/key_material.rb b/lib/fpg/packets/key_material.rb index 01fd983..4ba5f23 100644 --- a/lib/fpg/packets/key_material.rb +++ b/lib/fpg/packets/key_material.rb @@ -2,7 +2,10 @@ module FPG module Packets class KeyMaterial < Packet def algo - Algos::Algo.with_tag(pubkey_algo) + Algos::Asymmetric.with_tag(pubkey_algo) + end + def self.tag + -1 end end end diff --git a/lib/fpg/packets/public_key_encrypted_session_key.rb b/lib/fpg/packets/public_key_encrypted_session_key.rb index 1724d4b..38f50a7 100644 --- a/lib/fpg/packets/public_key_encrypted_session_key.rb +++ b/lib/fpg/packets/public_key_encrypted_session_key.rb @@ -1,8 +1,12 @@ module FPG module Packets - class PublicKeyEncryptedSessionKey < Packet - attr_accessor :key_id, :encryption_algo, :session_key - string :skip_this, :read_length => :byte_size + class PublicKeyEncryptedSessionKey < KeyMaterial + uint8 :version, :check_value => lambda { value == 3 } + key_id :key_id + uint8 :pubkey_algo + choice :key_material, :selection => :pubkey_algo do + rsa_session 1 + end def self.tag 1 end diff --git a/lib/fpg/packets/secret_key.rb b/lib/fpg/packets/secret_key.rb index fa9a599..c83a67b 100644 --- a/lib/fpg/packets/secret_key.rb +++ b/lib/fpg/packets/secret_key.rb @@ -1,7 +1,41 @@ module FPG module Packets - class SecretKey < KeyMaterial - string :skip_this, :read_length => :byte_size + class SecretKey < PublicKey + uint8 :s2k + uint8 :s2k_al, :onlyif => :s2k_specifier_given? + s2k_specifier :s2k_specifier, :onlyif => :s2k_specifier_given? + string :iv, :read_length => lambda { s2k_algo.block_size }, + :onlyif => :is_encrypted? + choice :secret_key_material, :onlyif => :is_not_encrypted?, :selection => :pubkey_algo do + rsa_secret 1 + end + string :encrypted_secret_key_material, :read_length => :secret_key_bytes, + :onlyif => :is_encrypted? + uint16 :checksum, :onlyif => lambda{ ! long_checksum? } + uint160 :lchecksum, :onlyif => :long_checksum? + + def secret_key_bytes + header[:header_size] + byte_size - (encrypted_secret_key_material.offset) - checksum_size + end + def checksum_size + (long_checksum? ? 20 : 2) + end + def s2k_algo + @s2k_algo ||= Algos::Symmetric.with_tag( s2k_specifier_given? ? s2k_al : s2k ) + end + def s2k_specifier_given? + s2k >= 254 + end + def is_encrypted? + s2k > 0 + end + def is_not_encrypted? + ! is_encrypted? + end + def long_checksum? + s2k == 254 + end + def self.tag 5 end diff --git a/lib/fpg/parse/parser.rb b/lib/fpg/parse/parser.rb index 627eeb1..82d6322 100644 --- a/lib/fpg/parse/parser.rb +++ b/lib/fpg/parse/parser.rb @@ -1,8 +1,9 @@ module FPG module Parse class Parser - def self.parse( stream ) + def self.parse( aStream ) packets = [] + stream = ZappableByteStream.new(aStream) until stream.eof? do packets << parse_packet(stream) #rescue Exception => e @@ -14,9 +15,16 @@ module FPG def self.parse_packet(stream) num = packet_number(stream.getbyte) stream.seek(-1,IO::SEEK_CUR) - p = Packets::Packet.with_tag(num) - p.read(stream) - p + packet = Packets::Packet.with_tag(num) + if packet.is_a? SecretKey then + puts + BinData::trace_reading do + packet.read(stream) + end + else + packet.read(stream) + end + packet end def self.packet_number(byte) raise "this is not a valid packet header" unless byte>>7 == 1 diff --git a/lib/fpg/public_key_algos/algo.rb b/lib/fpg/public_key_algos/algo.rb deleted file mode 100644 index ec0954b..0000000 --- a/lib/fpg/public_key_algos/algo.rb +++ /dev/null @@ -1,9 +0,0 @@ -module FPG - module Algos - class Algo < BinData::Record - include TaggedSubclasses - endian :big - attr_accessor :key_material - end - end -end diff --git a/lib/fpg/public_key_algos/rsa_encryption_only.rb b/lib/fpg/public_key_algos/rsa_encryption_only.rb deleted file mode 100644 index fd8df6e..0000000 --- a/lib/fpg/public_key_algos/rsa_encryption_only.rb +++ /dev/null @@ -1,6 +0,0 @@ -module FPG - module Algos - class RsaEncryptionOnly < Rsa - end - end -end diff --git a/lib/fpg/public_key_algos/rsa_sign_only.rb b/lib/fpg/public_key_algos/rsa_sign_only.rb deleted file mode 100644 index 6e3f71b..0000000 --- a/lib/fpg/public_key_algos/rsa_sign_only.rb +++ /dev/null @@ -1,6 +0,0 @@ -module FPG - module Algos - class RsaSignOnly < Rsa - end - end -end diff --git a/spec/.public_key_spec.rb.swp b/spec/.public_key_spec.rb.swp Binary files differdeleted file mode 100644 index 9af39e1..0000000 --- a/spec/.public_key_spec.rb.swp +++ /dev/null diff --git a/spec/packet_parsing_spec.rb b/spec/packet_parsing_spec.rb index d4949fa..376de22 100644 --- a/spec/packet_parsing_spec.rb +++ b/spec/packet_parsing_spec.rb @@ -21,11 +21,11 @@ describe Parser do parsed.any?{|packet| packet.is_a? PublicSubkey}.should be_true end it "should find all the packets in a secretkey" do -# parsed = Parser.parse(@binary_sec) -# parsed.any?{|packet| packet.is_a? Signature}.should be_true -# parsed.any?{|packet| packet.is_a? UserId}.should be_true -# parsed.any?{|packet| packet.is_a? SecretSubkey}.should be_true -# parsed.any?{|packet| packet.is_a? SecretKey}.should be_true + parsed = Parser.parse(@binary_sec) + parsed.any?{|packet| packet.is_a? Signature}.should be_true + parsed.any?{|packet| packet.is_a? UserId}.should be_true + parsed.any?{|packet| packet.is_a? SecretSubkey}.should be_true + parsed.any?{|packet| packet.is_a? SecretKey}.should be_true end it "should find all the packets in an encrypted message" do parsed = Parser.parse(@msg_enc) diff --git a/spec/pk_encrypted_session_key_spec.rb b/spec/pk_encrypted_session_key_spec.rb new file mode 100644 index 0000000..f3ebf13 --- /dev/null +++ b/spec/pk_encrypted_session_key_spec.rb @@ -0,0 +1,21 @@ +require File.join(File.dirname(__FILE__), %w[spec_helper]) + +require 'stringio' + +include FPG::Parse +include FPG::Packets +include FPG::Algos + +describe PublicKeyEncryptedSessionKey do + before(:each) do + @msg_enc = File.open "spec/fixtures/messages/1_enc.gpg", 'rb' + end + + it "should find all the packets in an encrypted message" do + parsed = Parser.parse(@msg_enc) + sk = parsed.select{|packet| packet.is_a? PublicKeyEncryptedSessionKey}.first + sk.algo.is_a?(Rsa).should be_true + sk.key_id.id.should == 0x8AC9A89E54DBDCF0 + end +end + |