From 9d0f8c581db901198c04c62fa9ea2cec178e8b32 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Thu, 12 Aug 2010 15:57:53 -0500 Subject: [PATCH 01/32] Implement Packet::CompressedData --- lib/openpgp/packet.rb | 45 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index c4f8df9..aab7d8a 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -1,3 +1,5 @@ +require 'zlib' + module OpenPGP ## # OpenPGP packet. @@ -328,7 +330,48 @@ class SecretSubkey < SecretKey # # @see http://tools.ietf.org/html/rfc4880#section-5.6 class CompressedData < Packet - # TODO + include Enumerable + attr_accessor :algorithm, :data + + def initialize(algorithm=nil, data=nil) + @algorithm = algorithm + @data = data + end + + def self.parse_body(body, options={}) + algorithm = body.read_byte.ord + data = body.read + data = Message::parse(case algorithm + when 0 # Uncompressed + data + when 1 # ZIP + Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(data) + when 2 # ZLIB + Zlib::Inflate.inflate(data) + when 3 # BZIP2 + # TODO + end) + self.new(algorithm, data) + end + + def body + body = algorithm.chr + body << case algorithm + when 0 # Uncompressed + data.to_s + when 1 # ZIP + Zlib::Deflate.new(nil, -Zlib::MAX_WBITS).deflate(data.to_s, Zlib::FINISH) + when 2 # ZLIB + Zlib::Deflate.deflate(data.to_s) + when 3 # BZIP2 + # TODO + end + end + + # Proxy onto embedded OpenPGP message + def each(&cb) + @data.each &cb + end end ## From 4b8f6bdf04d28d9cb3e1a19ff6f48f90ccbb3553 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Thu, 12 Aug 2010 15:58:37 -0500 Subject: [PATCH 02/32] Newline normalizer for textual LiteralData --- lib/openpgp/packet.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index aab7d8a..6721e9f 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -429,6 +429,15 @@ def initialize(options = {}, &block) super(defaults.merge(options), &block) end + def normalize + # Normalize line endings + if format == :u || format == :t + @data.gsub!(/\r\n/, "\n") + @data.gsub!(/\r/, "\n") + @data.gsub!(/\n/, "\r\n") + end + end + def write_body(buffer) buffer.write_byte(format) buffer.write_string(filename) From 9641c827f4a68a2f8aeacfaaa56a4fdd84b01a27 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Thu, 12 Aug 2010 15:59:00 -0500 Subject: [PATCH 03/32] Uncomment Packet to_s Need a way to get raw bytes of any packet, this is a good way --- lib/openpgp/packet.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index 6721e9f..a980daa 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -107,7 +107,7 @@ def initialize(options = {}, &block) block.call(self) if block_given? end - #def to_s() body end + def to_s() body end ## # @return [Integer] From 21888102e7b1fdfe183e0cd2ba37aed8070dc6b6 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Thu, 12 Aug 2010 16:00:44 -0500 Subject: [PATCH 04/32] Signature Subpackets and Trailer Actually parse signature subpackets and some util functios to use them. Store the literal trailer for use in signature verification --- lib/openpgp/packet.rb | 238 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 234 insertions(+), 4 deletions(-) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index a980daa..2e16b5d 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -147,8 +147,11 @@ class Signature < Packet attr_accessor :key_algorithm, :hash_algorithm attr_accessor :key_id attr_accessor :fields + attr_accessor :hashed_subpackets, :unhashed_subpackets + attr_accessor :hash_head, :trailer def self.parse_body(body, options = {}) + @hashed_subpackets = @unhashed_subpackets = [] case version = body.read_byte when 3 then self.new(:version => 3).send(:read_v3_signature, body) when 4 then self.new(:version => 4).send(:read_v4_signature, body) @@ -156,6 +159,49 @@ def self.parse_body(body, options = {}) end end + def key_algorithm_name + name = OpenPGP::Algorithm::Asymmetric::constants.select do |const| + OpenPGP::Algorithm::Asymmetric::const_get(const) == key_algorithm + end.first + name = :RSA if name == :RSA_S || name == :RSA_E + name.to_s + end + + def hash_algorithm_name + OpenPGP::Digest::for(hash_algorithm).algorithm.to_s + end + + def issuer + [hashed_subpackets + unhashed_subpackets].select {|packet| + packet.is_a?(OpenPGP::Packet::Signature::Issuer) + }.first + end + + def update_trailer + @trailer = body(true) + end + + def body(trailer=false) + body = 4.chr + type.chr + key_algorithm.chr + hash_algorithm.chr + + sub = hashed_subpackets.inject('') {|c,p| c + p.to_s} + body << [sub.length].pack('n') + sub + + # The trailer is just the top of the body plus some crap + return body + 4.chr + 0xff.chr + [body.length].pack('N') + + sub = unhashed_subpackets.inject('') {|c,p| c + p.to_s} + body << [sub.length].pack('n') + sub + + body << [hash_head].pack('n') + + fields.each {|data| + body << [data.length*8].pack('n') + body << data + } + body + end + protected ## @@ -164,7 +210,7 @@ def read_v3_signature(body) raise "Invalid OpenPGP signature packet V3 header" if body.read_byte != 5 @type, @timestamp, @key_id = body.read_byte, body.read_number(4), body.read_number(8, 16) @key_algorithm, @hash_algorithm = body.read_byte, body.read_byte - body.read_bytes(2) + @hash_head = body.read_bytes(2) read_signature(body) self end @@ -174,13 +220,56 @@ def read_v3_signature(body) def read_v4_signature(body) @type = body.read_byte @key_algorithm, @hash_algorithm = body.read_byte, body.read_byte - body.read_bytes(hashed_count = body.read_number(2)) - body.read_bytes(unhashed_count = body.read_number(2)) - body.read_bytes(2) + # We store exactly the original trailer for doing verifications + @trailer = 4.chr + type.chr + key_algorithm.chr + hash_algorithm.chr + hashed_count = body.read_number(2) + hashed_data = body.read_bytes(hashed_count) + @trailer << [hashed_count].pack('n') + hashed_data + 4.chr + 0xff.chr + [6 + hashed_count].pack('N') + @hashed_subpackets = read_subpackets(Buffer.new(hashed_data)) + unhashed_count = body.read_number(2) + unhashed_data = body.read_bytes(unhashed_count) + @unhashed_subpackets = read_subpackets(Buffer.new(unhashed_data)) + @hash_head = body.read_bytes(2) read_signature(body) self end + ## + # @see http://tools.ietf.org/html/rfc4880#section-5.2.3.1 + def read_subpackets(buf) + packets = [] + until buf.eof? + if packet = read_subpacket(buf) + packets << packet + else + raise "Invalid OpenPGP message data at position #{body.pos+buf.pos}" + end + end + packets + end + + def read_subpacket(buf) + length = buf.read_byte.ord + length_of_length = 1 + # if len < 192 One octet length, no furthur processing + if length > 190 && length < 255 # Two octet length + length_of_length = 2 + length = ((length - 192) << 8) + buf.read_byte.ord + 192 + end + if length == 255 # Five octet length + length_of_length = 5 + length = buf.read_unpacked(4, 'N') + end + + tag = buf.read_byte.ord + self.class.const_get(self.class.constants.select {|t| + self.class.const_get(t).const_defined?(:TAG) && \ + self.class.const_get(t)::TAG == tag + }.first).parse_body(Buffer.new(buf.read(length)), :tag => tag) + #rescue Exception + # nil # Parse error, return no subpacket + end + ## # @see http://tools.ietf.org/html/rfc4880#section-5.2.2 def read_signature(body) @@ -193,6 +282,147 @@ def read_signature(body) raise "Unknown OpenPGP signature packet public-key algorithm: #{key_algorithm}" end end + + class Subpacket < Packet + def header_and_body + b = body + # Use 5-octet lengths + 1 for tag as first packet body octet + size = 255.chr + [body.length+1].pack('N') + tag = self.get_const(:TAG).chr + {:header => size + tag, :body => body} + end + end + + ## + # @see http://tools.ietf.org/html/rfc4880#section-5.2.3.4 + class SignatureCreationTime < Subpacket + TAG = 2 + def initialize(time=nil) + super() + @data = time || Time.now.to_i + end + + def self.parse_body(body, options={}) + self.new(body.read_timestamp) + end + + def body + [@data].pack('N') + end + end + class SignatureExpirationTime < Subpacket + TAG = 3 + def initialize(time=nil) + super() + @data = time || Time.now.to_i + end + + def self.parse_body(body, options={}) + self.new(body.read_timestamp) + end + + def body + [@data].pack('N') + end + end + class ExportableCertification < Subpacket + TAG = 4 + end + class TrustSignature < Subpacket + TAG = 5 + end + class RegularExpression < Subpacket + TAG = 6 + end + class Revocable < Subpacket + TAG = 7 + end + class KeyExpirationTime < Subpacket + TAG = 9 + def initialize(time=nil) + super() + @data = time || Time.now.to_i + end + + def self.parse_body(body, options={}) + self.new(body.read_timestamp) + end + + def body + [@data].pack('N') + end + end + class PreferredSymmetricAlgorithms < Subpacket + TAG = 11 + end + class RevocationKey < Subpacket + TAG = 12 + end + + ## + # @see http://tools.ietf.org/html/rfc4880#section-5.2.3.5 + class Issuer < Subpacket + TAG = 16 + def initialize(keyid=nil) + super() + @data = keyid + end + + def self.parse_body(body, options={}) + data = '' + 8.times do # Store KeyID in Hex + data << '%02X' % body.read_byte.ord + end + self.new(data) + end + + def body + b = '' + 0.step(@data.length, 2) do |i| + b << @data[i,2].to_i(16).chr + end + b + end + end + class NotationData < Subpacket + TAG = 20 + end + class PreferredHashAlgorithms < Subpacket + TAG = 21 + end + class PreferredCompressionAlgorithms < Subpacket + TAG = 22 + end + class KeyServerPreferences < Subpacket + TAG = 23 + end + class PreferredKeyServer < Subpacket + TAG = 24 + end + class PrimaryUserID < Subpacket + TAG = 25 + end + class PolicyURI < Subpacket + TAG = 26 + end + class KeyFlags < Subpacket + TAG = 27 + end + class SignersUserID < Subpacket + TAG = 28 + end + class ReasonforRevocation < Subpacket + TAG = 29 + end + class Features < Subpacket + TAG = 30 + end + class SignatureTarget < Subpacket + TAG = 31 + end + class EmbeddedSignature < Subpacket + TAG = 32 + end end ## From caa79d149ad26362fc111d97b06973df2734b0d7 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Thu, 12 Aug 2010 16:01:59 -0500 Subject: [PATCH 05/32] Method to get signature+data packet from message, and to do verifications --- lib/openpgp/message.rb | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lib/openpgp/message.rb b/lib/openpgp/message.rb index 4eadd6c..63fb2f3 100644 --- a/lib/openpgp/message.rb +++ b/lib/openpgp/message.rb @@ -86,6 +86,35 @@ def initialize(*packets, &block) block.call(self) if block_given? end + def signature_and_data(index=0) + msg = self + msg = msg.first while msg.first.is_a?(OpenPGP::Packet::CompressedData) + signature_packet = data_packet = nil + i = 0 + msg.each { |packet| + if packet.is_a?(OpenPGP::Packet::Signature) + signature_packet = packet if i == index + i += 1 + elsif packet.is_a?(OpenPGP::Packet::LiteralData) + data_packet = packet + end + break if signature_packet && data_packet + } + [signature_packet, data_packet] + end + + ## + # @param verifiers a Hash of callables formatted like {'RSA' => {'SHA256' => callable}} that take two parameters: message and signature + # @param index signature number to verify (if more than one) + def verify(verifiers, index=0) + signature_packet, data_packet = signature_and_data(index) + return nil unless signature_packet && data_packet # No signature or no data + verifier = verifiers[signature_packet.key_algorithm_name][signature_packet.hash_algorithm_name] + return nil unless verifier # No verifier + data_packet.normalize + verifier.call(data_packet.data + signature_packet.trailer, signature_packet.fields) + end + ## # @yield [packet] # @yieldparam [Packet] packet From fa82f13fd074e7992d55e0b3ca818c4e5d34fdcb Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Thu, 12 Aug 2010 16:02:32 -0500 Subject: [PATCH 06/32] Implement Engine::OpenSSL::RSA --- lib/openpgp/engine/openssl.rb | 82 +++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/lib/openpgp/engine/openssl.rb b/lib/openpgp/engine/openssl.rb index 35f817b..619c43c 100644 --- a/lib/openpgp/engine/openssl.rb +++ b/lib/openpgp/engine/openssl.rb @@ -17,6 +17,88 @@ def self.install! [Random, Digest].each { |mod| install_extensions! mod } end + ## + # Wrap OpenSSL RSA methods for use with OpenPGP + class RSA + def initialize(packet) + packet = OpenPGP::Message::parse(packet) if packet.is_a?(String) + @key = @message = nil + if packet.is_a?(OpenPGP::Packet::PublicKey) || \ + (packet.respond_to?(:first) && packet.first.is_a?(OpenPGP::Packet::PublicKey)) + @key = packet + else + @message = packet + end + end + + ## + # @param [String] keyid (Optional) + # @return OpenPGP::Packet::PublicKey + def key(keyid=nil) + return nil unless @key + keyid.upcase! if keyid + if @key.is_a?(Enumerable) # Like an OpenPGP::Message + @key.select {|p| p.is_a?(OpenPGP::Packet::PublicKey) && (!keyid || \ + p.fingerprint[keyid.length*-1,keyid.length].upcase == keyid) + }.first + end || @key + end + + ## + # @param [String] keyid (Optional) + # @return OpenSSL::PKey::RSA + def rsa_key(keyid=nil) + self.class.convert_key(key(keyid)) + end + + ## + # @param packet message to verify with @key, or key (OpenPGP or RSA) to check @message with + # @param [Integer] index specify which signature to verify (if there is more than one) + # @return OpenSSL::PKey::RSA + def verify(packet, index=0) + packet = OpenPGP::Message::parse(packet) if packet.is_a?(String) + if packet.is_a?(OpenPGP::Message) && !packet.is_a?(OpenPGP::Packet::PublicKey) + signature_packet, data_packet = packet.signature_and_data(index) + k = rsa_key(signature_packet.issuer) + return nil unless k && signature_packet.key_algorithm_name == 'RSA' + return packet.verify({'RSA' => {signature_packet.hash_algorithm_name => lambda {|m,s| + k.verify(signature_packet.hash_algorithm_name, s.first, m) + }}}) + else + end + end + + ## + # @param packet + # @return [OpenSSL::PKey::RSA] + def self.convert_key(packet) + # packet is already an key + return packet if packet.is_a?(::OpenSSL::PKey::RSA) + unless packet.is_a?(Hash) + # Get the first item in a message + packet = packet.first if packet.is_a?(Enumerable) + # TODO: Error if packet.algorithm not RSA + packet = packet.key # Get key material + end + + # Create blank key and fill the fields + key = ::OpenSSL::PKey::RSA.new + packet.each {|k,v| + if v.is_a?(Numeric) + v = ::OpenSSL::BN.new(v.to_s) + elsif !(v.is_a?(::OpenSSL::BN)) + # Convert the byte string to an OpenSSL::BN + v = v.reverse.enum_for(:each_char).enum_for(:each_with_index) \ + .inject(::OpenSSL::BN.new('0')) {|c, (b,i)| + c + (b.force_encoding('binary').ord << i*8) + } + end + key.send("#{k}=".intern, v) + } + key + end + end + ## # @private module Random #:nodoc: From 47f91c1db20742b769ae8835acabbc1e4565b355 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Thu, 12 Aug 2010 16:05:48 -0500 Subject: [PATCH 07/32] I am a contributor --- CONTRIBUTORS | 1 + README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 9c1df4d..57dfa78 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1 +1,2 @@ * Kévin Lacointe (Some GnuPG patches) +* Stephen Paul Weber diff --git a/README.md b/README.md index f6dd6b6..b757b36 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ Contributors ------------ * [Kévin Lacointe](mailto:kevinlacointe@gmail.com) - +* [Stephen Paul Weber](mailto:singpolyma@singpolyma.net) - Contributing ------------ From 895f14460174228303c23a034da661c4b3b8cbc1 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 13 Aug 2010 10:37:12 -0500 Subject: [PATCH 08/32] Allow verify to be passed message OR key --- lib/openpgp/engine/openssl.rb | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/openpgp/engine/openssl.rb b/lib/openpgp/engine/openssl.rb index 619c43c..ec9b6b3 100644 --- a/lib/openpgp/engine/openssl.rb +++ b/lib/openpgp/engine/openssl.rb @@ -54,18 +54,25 @@ def rsa_key(keyid=nil) ## # @param packet message to verify with @key, or key (OpenPGP or RSA) to check @message with # @param [Integer] index specify which signature to verify (if there is more than one) - # @return OpenSSL::PKey::RSA + # @return Boolean def verify(packet, index=0) packet = OpenPGP::Message::parse(packet) if packet.is_a?(String) - if packet.is_a?(OpenPGP::Message) && !packet.is_a?(OpenPGP::Packet::PublicKey) - signature_packet, data_packet = packet.signature_and_data(index) - k = rsa_key(signature_packet.issuer) - return nil unless k && signature_packet.key_algorithm_name == 'RSA' - return packet.verify({'RSA' => {signature_packet.hash_algorithm_name => lambda {|m,s| - k.verify(signature_packet.hash_algorithm_name, s.first, m) - }}}) + if packet.is_a?(OpenPGP::Message) && !packet.first.is_a?(OpenPGP::Packet::PublicKey) + m = packet + k = self else + m = @message + k = self.class.new(packet) end + + return nil unless m + signature_packet, data_packet = m.signature_and_data(index) + k = k.rsa_key(signature_packet.issuer) + return nil unless k && signature_packet.key_algorithm_name == 'RSA' + + return m.verify({'RSA' => {signature_packet.hash_algorithm_name => lambda {|m,s| + k.verify(signature_packet.hash_algorithm_name, s.first, m) + }}}) end ## From 3774a036d7e00ec39392d26218f1fea23c952f49 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 13 Aug 2010 15:25:50 -0500 Subject: [PATCH 09/32] Implement Packet::SecretKey --- lib/openpgp/packet.rb | 65 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index 2e16b5d..97dbc41 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -512,12 +512,12 @@ def fingerprint when 2, 3 Digest::MD5.hexdigest([key[:n], key[:e]].join).upcase when 4 - material = [0x99.chr, [size].pack('n'), version.chr, [timestamp].pack('N'), algorithm.chr] - key_fields.each do |key_field| - material << [OpenPGP.bitlength(key[key_field])].pack('n') - material << key[key_field] - end - Digest::SHA1.hexdigest(material.join).upcase + head = [0x99.chr, nil, version.chr, [timestamp].pack('N'), algorithm.chr] + material = key_fields.map do |key_field| + [[OpenPGP.bitlength(key[key_field])].pack('n'), key[key_field]] + end.flatten.join + head[1] = [material.length + 6].pack('n') + Digest::SHA1.hexdigest((head + [material]).join).upcase end end end @@ -541,7 +541,58 @@ class PublicSubkey < PublicKey # @see http://tools.ietf.org/html/rfc4880#section-11.2 # @see http://tools.ietf.org/html/rfc4880#section-12 class SecretKey < PublicKey - # TODO + attr_accessor :s2k_useage, :symmetric_type, :s2k_type, :s2k_hash_algorithm + attr_accessor :s2k_salt, :s2k_count, :encrypted_data, :data + + def self.parse_body(body, options={}) + key = super # All the fields from PublicKey + data = {:s2k_useage => body.read_byte.ord} + if data[:s2k_useage] == 255 || data[:s2k_useage] == 254 + data[:symmetric_type] = body.read_byte.ord + data[:s2k_type] = body.read_byte.ord + data[:s2k_hash_algorithm] = self.read_byte.ord + if data[:s2k_type] == 1 || data[:s2k_type] == 3 + data[:s2k_salt] = body.read_bytes(8) + end + if data[:s2k_type] == 3 + c = self.read_byte.ord + data[:s2k_count] = (16 + (c & 15)).floor << ((c >> 4) + 6) + end + elsif data[:s2k_useage] > 0 + data[:symmetric_type] = data[:s2k_useage] + end + if data[:s2k_useage] > 0 + # TODO: IV of the same length as cipher's block size + data[:encrypted_data] = body.read # Rest of input is MPIs and checksum (encrypted) + else + data[:data] = body.read # Rest of input is MPIs and checksum + end + data.each {|k,v| key.send("#{k}=", v) } + key.key_from_data + key + end + + def key_from_data + return nil unless data # Not decrypted yet + @secret_key_fields = case algorithm + when Algorithm::Asymmetric::RSA, + Algorithm::Asymmetric::RSA_E, + Algorithm::Asymmetric::RSA_S then [:d, :p, :q, :u] + when Algorithm::Asymmetric::ELG_E then [:x] + when Algorithm::Asymmetric::DSA then [:x] + else raise "Unknown OpenPGP key algorithm: #{algorithm}" + end + body = Buffer.new(data) + @secret_key_fields.each {|mpi| + self.key[mpi] = body.read_mpi + } + # TODO: Validate checksum? + if s2k_useage == 254 # 20 octet sha1 hash + @private_hash = body.read_bytes(20) + else # 2 octet checksum + @private_hash = body.read_bytes(2) + end + end end ## From beefa91b141d37eb0844e0e3f0fa521116ebf713 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 13 Aug 2010 15:26:54 -0500 Subject: [PATCH 10/32] Improvements to byte serialization --- lib/openpgp/message.rb | 6 +----- lib/openpgp/packet.rb | 24 ++++++++++++++++++------ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/openpgp/message.rb b/lib/openpgp/message.rb index 63fb2f3..7cf7e24 100644 --- a/lib/openpgp/message.rb +++ b/lib/openpgp/message.rb @@ -153,11 +153,7 @@ def size def to_s Buffer.write do |buffer| packets.each do |packet| - if body = packet.body - buffer.write_byte(packet.class.tag | 0xC0) - buffer.write_byte(body.size) - buffer.write_bytes(body) - end + buffer.write_bytes(packet.to_s) end end end diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index 97dbc41..7dd02cf 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -107,12 +107,24 @@ def initialize(options = {}, &block) block.call(self) if block_given? end - def to_s() body end + def to_s + data = header_and_body + data[:header] + (data[:body] || '') + end ## # @return [Integer] def size() body.size end + ## + # @return [Hash] + def header_and_body + body = self.body # Get body first, we will need it's length + tag = (self.class.tag | 0xC0).chr # First two bits are 1 for new packet format + size = 255.chr + [body ? body.length : 0].pack('N') # Use 5-octet lengths + {:header => tag + size, :body => body} + end + ## # @return [String] def body @@ -184,11 +196,11 @@ def update_trailer def body(trailer=false) body = 4.chr + type.chr + key_algorithm.chr + hash_algorithm.chr - sub = hashed_subpackets.inject('') {|c,p| c + p.to_s} + sub = hashed_subpackets.inject('') {|c,p| p.to_s + c} body << [sub.length].pack('n') + sub # The trailer is just the top of the body plus some crap - return body + 4.chr + 0xff.chr + [body.length].pack('N') + return body + 4.chr + 0xff.chr + [body.length].pack('N') if trailer sub = unhashed_subpackets.inject('') {|c,p| c + p.to_s} body << [sub.length].pack('n') + sub @@ -266,8 +278,8 @@ def read_subpacket(buf) self.class.const_get(t).const_defined?(:TAG) && \ self.class.const_get(t)::TAG == tag }.first).parse_body(Buffer.new(buf.read(length)), :tag => tag) - #rescue Exception - # nil # Parse error, return no subpacket + rescue Exception + nil # Parse error, return no subpacket end ## @@ -288,7 +300,7 @@ def header_and_body b = body # Use 5-octet lengths + 1 for tag as first packet body octet size = 255.chr + [body.length+1].pack('N') - tag = self.get_const(:TAG).chr + tag = self.class.const_get(:TAG).chr {:header => size + tag, :body => body} end end From b7f4a900e6dc089898aaf72df883e73bbcbed419 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 13 Aug 2010 15:27:21 -0500 Subject: [PATCH 11/32] Methods for creating new signatures --- lib/openpgp/packet.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index 7dd02cf..ab9987a 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -162,6 +162,12 @@ class Signature < Packet attr_accessor :hashed_subpackets, :unhashed_subpackets attr_accessor :hash_head, :trailer + def initialize(options={}, &blk) + @hashed_subpackets = [] + @unhashed_subpackets = [] + super(options, &blk) + end + def self.parse_body(body, options = {}) @hashed_subpackets = @unhashed_subpackets = [] case version = body.read_byte @@ -171,6 +177,19 @@ def self.parse_body(body, options = {}) end end + ## + # @params [OpenPGP::Packet::LiteralData] m + # @params [Hash] signers in the same format as verifiers for Message + def sign_data(m, signers) + self.type = m.format == :b ? 0x00 : 0x01 + m.normalize # Line endings + update_trailer + signer = signers[key_algorithm_name][hash_algorithm_name] + self.fields = signer.call(m.data + trailer) + self.fields = [fields] unless fields.is_a?(Enumerable) + self.hash_head = fields.first[0,2].unpack('n').first + end + def key_algorithm_name name = OpenPGP::Algorithm::Asymmetric::constants.select do |const| OpenPGP::Algorithm::Asymmetric::const_get(const) == key_algorithm From 50d9c2ace6ec0e7edb0ac53962697f0fc69117a5 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Fri, 13 Aug 2010 15:27:43 -0500 Subject: [PATCH 12/32] Implement OpenSSL::RSA::sign --- lib/openpgp/engine/openssl.rb | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/lib/openpgp/engine/openssl.rb b/lib/openpgp/engine/openssl.rb index ec9b6b3..5a5a7a5 100644 --- a/lib/openpgp/engine/openssl.rb +++ b/lib/openpgp/engine/openssl.rb @@ -75,6 +75,50 @@ def verify(packet, index=0) }}}) end + ## + # @param packet message to sign with @key or key (OpenPGP or RSA) to sign @message with + # @param [String] hash name of hash function to use (default SHA256) + # @param [String] keyid id of key to use (if there is more than one) + # @return OpenPGP::Message + def sign(packet, hash='SHA256', keyid=nil) + packet = unless packet.is_a?(OpenPGP::Packet) || packet.is_a?(OpenPGP::Message) + if @key + OpenPGP::Packet::LiteralData.new(:data => packet, :timestamp => Time.now.to_i) + else + OpenPGP::Message::parse(packet) + end + else + packet + end + + if packet.is_a?(OpenPGP::Packet::SecretKey) || packet.is_a?(::OpenSSL::PKey::RSA) \ + || (packet.is_a?(Enumerable) && packet.first.is_a?(OpenPGP::Packet::SecretKey)) + m = @message + k = packet + else + m = packet + k = @key + end + + return nil unless k && m # Missing some data + + m = m.signature_and_data.first if m.is_a?(OpenPGP::Message) + + unless k.is_a?(::OpenSSL::PKey::RSA) + k = self.class.new(k) + keyid = k.key.fingerprint[-16,16] unless keyid + k = k.rsa_key(keyid) + end + + sig = OpenPGP::Packet::Signature.new(:version => 4, + :key_algorithm => OpenPGP::Algorithm::Asymmetric::RSA, + :hash_algorithm => OpenPGP::Digest::for(hash).to_i) + sig.hashed_subpackets << OpenPGP::Packet::Signature::Issuer.new(keyid) + sig.hashed_subpackets << OpenPGP::Packet::Signature::SignatureCreationTime.new(Time.now.to_i) + sig.sign_data(m, {'RSA' => {hash => lambda {|m| k.sign(hash, m)}}}) + OpenPGP::Message.new([sig, m]) + end + ## # @param packet # @return [OpenSSL::PKey::RSA] @@ -91,6 +135,7 @@ def self.convert_key(packet) # Create blank key and fill the fields key = ::OpenSSL::PKey::RSA.new packet.each {|k,v| + next if k == :u # OpenSSL doesn't call it that if v.is_a?(Numeric) v = ::OpenSSL::BN.new(v.to_s) elsif !(v.is_a?(::OpenSSL::BN)) From ef2c5f2bdf43e476f60d2e2d49bb4e2f0e2b3cb2 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Mon, 16 Aug 2010 11:00:16 -0500 Subject: [PATCH 13/32] Compatability with 1.9.0 --- lib/openpgp.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/openpgp.rb b/lib/openpgp.rb index e259f25..a9e8c25 100644 --- a/lib/openpgp.rb +++ b/lib/openpgp.rb @@ -12,6 +12,14 @@ end end +unless 1.respond_to?(:ord) + class Numeric + def ord + to_i + end + end +end + module OpenPGP require 'openpgp/util' From 521c2c7fc2175311cb067f61b07f86fcbcdc1f74 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Mon, 16 Aug 2010 11:03:19 -0500 Subject: [PATCH 14/32] Issuer for v3 sigs --- lib/openpgp/packet.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index ab9987a..5711151 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -205,7 +205,7 @@ def hash_algorithm_name def issuer [hashed_subpackets + unhashed_subpackets].select {|packet| packet.is_a?(OpenPGP::Packet::Signature::Issuer) - }.first + }.first || key_id end def update_trailer From bb9f3e3961c76ed22de7a15bc3a059e5c487c454 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Mon, 16 Aug 2010 11:54:43 -0500 Subject: [PATCH 15/32] Support critical subpackets, get lengths right --- lib/openpgp/packet.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index 5711151..2a8d7c3 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -293,10 +293,12 @@ def read_subpacket(buf) end tag = buf.read_byte.ord + critical = (tag & 0x80) != 0 + tag &= 0x7F self.class.const_get(self.class.constants.select {|t| self.class.const_get(t).const_defined?(:TAG) && \ self.class.const_get(t)::TAG == tag - }.first).parse_body(Buffer.new(buf.read(length)), :tag => tag) + }.first).parse_body(Buffer.new(buf.read(length-1)), :tag => tag) rescue Exception nil # Parse error, return no subpacket end @@ -317,7 +319,7 @@ def read_signature(body) class Subpacket < Packet def header_and_body b = body - # Use 5-octet lengths + 1 for tag as first packet body octet + # Use 5-octet lengths size = 255.chr + [body.length+1].pack('N') tag = self.class.const_get(:TAG).chr {:header => size + tag, :body => body} From 42accb993f7ddfabae022707d4aa26f23dd75d41 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Mon, 16 Aug 2010 11:55:09 -0500 Subject: [PATCH 16/32] Serialize issuer right --- lib/openpgp/packet.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index 2a8d7c3..b67ace3 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -411,8 +411,8 @@ def self.parse_body(body, options={}) def body b = '' - 0.step(@data.length, 2) do |i| - b << @data[i,2].to_i(16).chr + @data.enum_for(:each_char).each_slice(2) do |i| + b << i.join.to_i(16).chr end b end From e7e4757cd914bdd8621dd23c20c17d8aa4e2e46e Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Mon, 16 Aug 2010 11:55:43 -0500 Subject: [PATCH 17/32] Implement PreferredKeyServer --- lib/openpgp/packet.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index b67ace3..597b298 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -426,11 +426,26 @@ class PreferredHashAlgorithms < Subpacket class PreferredCompressionAlgorithms < Subpacket TAG = 22 end + + ## + # @see http://tools.ietf.org/html/rfc4880#section-5.2.3.18 class KeyServerPreferences < Subpacket TAG = 23 end class PreferredKeyServer < Subpacket TAG = 24 + def initialize(uri=nil) + super() + @data = uri + end + + def self.parse_body(body, options={}) + self.new(body.read) + end + + def body + @data + end end class PrimaryUserID < Subpacket TAG = 25 From b81de04bd8b3260fd3fd140974ec3aba771dc16a Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Mon, 16 Aug 2010 19:09:26 -0500 Subject: [PATCH 18/32] fix issuer search --- lib/openpgp/packet.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index 597b298..90f64ed 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -203,9 +203,14 @@ def hash_algorithm_name end def issuer - [hashed_subpackets + unhashed_subpackets].select {|packet| + packet = (hashed_subpackets + unhashed_subpackets).select {|packet| packet.is_a?(OpenPGP::Packet::Signature::Issuer) - }.first || key_id + }.first + if packet + packet.data + else + key_id + end end def update_trailer @@ -317,6 +322,7 @@ def read_signature(body) end class Subpacket < Packet + attr_reader :data def header_and_body b = body # Use 5-octet lengths From 80fa27824e9f611619a88a7be736bd001d01ce9d Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Tue, 17 Aug 2010 09:57:35 -0500 Subject: [PATCH 19/32] Oops, expiration time is seconds *after* creation time --- lib/openpgp/packet.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index 90f64ed..f4f7390 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -353,7 +353,7 @@ class SignatureExpirationTime < Subpacket TAG = 3 def initialize(time=nil) super() - @data = time || Time.now.to_i + @data = time || 0 end def self.parse_body(body, options={}) From 8453ab515d27ff835fa6f7e89c9dfd191024e8a1 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sat, 23 Jul 2011 09:12:54 -0500 Subject: [PATCH 20/32] Support leading 0s --- lib/openpgp/util.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/openpgp/util.rb b/lib/openpgp/util.rb index 67164d7..baa9f3d 100644 --- a/lib/openpgp/util.rb +++ b/lib/openpgp/util.rb @@ -64,6 +64,9 @@ def self.crc24(data) # @return [Integer] # @see http://tools.ietf.org/html/rfc4880#section-3.2 def self.bitlength(data) - data.empty? ? 0 : (data.size - 1) * 8 + (Math.log(data[0].ord) / Math.log(2)).floor + 1 + data = data.split(//) + while (f = data.shift) == '\0'; end + return 0 unless f + Math.log(f.ord, 2).floor + 1 + (data.length*8) end end From f5819e7a45a9e203c8c3231286b90d7b0ac66df4 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sat, 23 Jul 2011 09:15:00 -0500 Subject: [PATCH 21/32] Implement KeyFlags --- lib/openpgp/packet.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index f4f7390..7e719ff 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -461,6 +461,24 @@ class PolicyURI < Subpacket end class KeyFlags < Subpacket TAG = 27 + attr_accessor :flags + + def initialize(*flags) + super() + @flags = flags.flatten + end + + def self.parse_body(body, options={}) + flags = [] + until body.eof? + flags << body.read_byte.ord + end + self.new(flags) + end + + def body + flags.map {|f| f.chr}.join + end end class SignersUserID < Subpacket TAG = 28 From 330647f67ba38128b36c03fe83571cb183cddcb9 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sat, 23 Jul 2011 09:15:32 -0500 Subject: [PATCH 22/32] Implement Features --- lib/openpgp/packet.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index 7e719ff..737822d 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -486,7 +486,7 @@ class SignersUserID < Subpacket class ReasonforRevocation < Subpacket TAG = 29 end - class Features < Subpacket + class Features < KeyFlags TAG = 30 end class SignatureTarget < Subpacket From e3585bc77adee5803be876cbb84e376aee6bd760 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sat, 23 Jul 2011 09:16:09 -0500 Subject: [PATCH 23/32] order hashed subpackets correctly --- lib/openpgp/packet.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index 737822d..3c8f1b5 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -220,7 +220,7 @@ def update_trailer def body(trailer=false) body = 4.chr + type.chr + key_algorithm.chr + hash_algorithm.chr - sub = hashed_subpackets.inject('') {|c,p| p.to_s + c} + sub = hashed_subpackets.inject('') {|c,p| c + p.to_s} body << [sub.length].pack('n') + sub # The trailer is just the top of the body plus some crap From 5a08a6a7d4fb1d7656b617ba5ba2aa4e31c81f62 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sat, 23 Jul 2011 09:16:51 -0500 Subject: [PATCH 24/32] use bitlength, not *8 --- lib/openpgp/packet.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index 3c8f1b5..613ca88 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -232,7 +232,7 @@ def body(trailer=false) body << [hash_head].pack('n') fields.each {|data| - body << [data.length*8].pack('n') + body << [OpenPGP.bitlength(data)].pack('n') body << data } body From cc5c9d68ce8d21b57cad1e4bf476be7caaa1854f Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sat, 23 Jul 2011 09:17:29 -0500 Subject: [PATCH 25/32] Refactor PublicKey, Implement SecretKey --- lib/openpgp/packet.rb | 84 ++++++++++++++++++++++++++++++++----------- 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index 613ca88..6d400e2 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -547,7 +547,7 @@ class OnePassSignature < Packet class PublicKey < Packet attr_accessor :size attr_accessor :version, :timestamp, :algorithm - attr_accessor :key, :key_fields, :key_id, :fingerprint + attr_accessor :key, :key_id, :fingerprint #def parse(data) # FIXME def self.parse_body(body, options = {}) @@ -566,14 +566,29 @@ def self.parse_body(body, options = {}) ## # @see http://tools.ietf.org/html/rfc4880#section-5.5.2 def read_key_material(body) - @key_fields = case algorithm + key_fields.each { |field| key[field] = body.read_mpi } + @key_id = fingerprint[-8..-1] + end + + def key_fields + case algorithm when Algorithm::Asymmetric::RSA then [:n, :e] when Algorithm::Asymmetric::ELG_E then [:p, :g, :y] when Algorithm::Asymmetric::DSA then [:p, :q, :g, :y] else raise "Unknown OpenPGP key algorithm: #{algorithm}" end - @key_fields.each { |field| key[field] = body.read_mpi } - @key_id = fingerprint[-8..-1] + end + + def fingerprint_material + case version + when 2, 3 + [key[:n], key[:e]].join + when 4 + material = key_fields.map do |key_field| + [[OpenPGP.bitlength(key[key_field])].pack('n'), key[key_field]] + end.flatten.join + [0x99.chr, [material.length + 6].pack('n'), version.chr, [timestamp].pack('N'), algorithm.chr, material] + end end ## @@ -582,14 +597,18 @@ def read_key_material(body) def fingerprint @fingerprint ||= case version when 2, 3 - Digest::MD5.hexdigest([key[:n], key[:e]].join).upcase + Digest::MD5.hexdigest(fingerprint_material.join).upcase when 4 - head = [0x99.chr, nil, version.chr, [timestamp].pack('N'), algorithm.chr] - material = key_fields.map do |key_field| - [[OpenPGP.bitlength(key[key_field])].pack('n'), key[key_field]] - end.flatten.join - head[1] = [material.length + 6].pack('n') - Digest::SHA1.hexdigest((head + [material]).join).upcase + Digest::SHA1.hexdigest(fingerprint_material.join).upcase + end + end + + def body + case version + when 2, 3 + # TODO + when 4 + fingerprint_material[2..-1].join end end end @@ -646,16 +665,8 @@ def self.parse_body(body, options={}) def key_from_data return nil unless data # Not decrypted yet - @secret_key_fields = case algorithm - when Algorithm::Asymmetric::RSA, - Algorithm::Asymmetric::RSA_E, - Algorithm::Asymmetric::RSA_S then [:d, :p, :q, :u] - when Algorithm::Asymmetric::ELG_E then [:x] - when Algorithm::Asymmetric::DSA then [:x] - else raise "Unknown OpenPGP key algorithm: #{algorithm}" - end body = Buffer.new(data) - @secret_key_fields.each {|mpi| + secret_key_fields.each {|mpi| self.key[mpi] = body.read_mpi } # TODO: Validate checksum? @@ -665,6 +676,39 @@ def key_from_data @private_hash = body.read_bytes(2) end end + + def secret_key_fields + case algorithm + when Algorithm::Asymmetric::RSA, + Algorithm::Asymmetric::RSA_E, + Algorithm::Asymmetric::RSA_S then [:d, :p, :q, :u] + when Algorithm::Asymmetric::ELG_E then [:x] + when Algorithm::Asymmetric::DSA then [:x] + else raise "Unknown OpenPGP key algorithm: #{algorithm}" + end + end + + def body + super + s2k_useage.to_i.chr + \ + if s2k_useage == 255 || s2k_useage == 254 + symmetric_type.chr + s2k_type.chr + s2k_hash_algorithm.chr + \ + (s2k_type == 1 || s2k_type == 3 ? s2k_salt : '') + # (s2k_type == 3 ? reverse ugly bit manipulation + end.to_s + if s2k_useage.to_i > 0 + encrypted_data + else + secret_material = secret_key_fields.map {|f| [OpenPGP.bitlength(key[f].to_s)].pack('n') + key[f].to_s}.join + end + \ + if s2k_useage == 254 # SHA1 checksum + # TODO + "\0"*20 + else # 2-octet checksum + # TODO, this design will not work for encrypted keys + [secret_material.split(//).inject(0) {|chk, c| + chk = (chk + c.ord) % 65536 + }].pack('n') + end + end end ## From 54b92258cf7f2c42c3954b22552a5b070658a4c4 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sat, 23 Jul 2011 09:17:53 -0500 Subject: [PATCH 26/32] store hash_head unpacked --- lib/openpgp/packet.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index 6d400e2..6d518af 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -265,7 +265,7 @@ def read_v4_signature(body) unhashed_count = body.read_number(2) unhashed_data = body.read_bytes(unhashed_count) @unhashed_subpackets = read_subpackets(Buffer.new(unhashed_data)) - @hash_head = body.read_bytes(2) + @hash_head = body.read_bytes(2).unpack('n').first read_signature(body) self end From 790b27fd45512eaaf233b6a451c5a4567413e279 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sat, 23 Jul 2011 09:18:05 -0500 Subject: [PATCH 27/32] better exception message --- lib/openpgp/packet.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index 6d518af..4942481 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -278,7 +278,7 @@ def read_subpackets(buf) if packet = read_subpacket(buf) packets << packet else - raise "Invalid OpenPGP message data at position #{body.pos+buf.pos}" + raise "Invalid OpenPGP message data at position #{buf.pos} in signature subpackets" end end packets From 0932c10ba7751288ff7004073baed2f79430bb74 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sat, 23 Jul 2011 09:18:19 -0500 Subject: [PATCH 28/32] UserID packet body --- lib/openpgp/packet.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index 4942481..181b16b 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -895,7 +895,7 @@ def write_body(buffer) buffer.write(to_s) end - def to_s + def body text = [] text << name if name text << "(#{comment})" if comment From e1bed9181b9a034ac83a5ad4a41edd637d3c3df0 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sat, 23 Jul 2011 09:18:34 -0500 Subject: [PATCH 29/32] Add ability to sign keys --- lib/openpgp/packet.rb | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/openpgp/packet.rb b/lib/openpgp/packet.rb index 181b16b..2cdab9a 100644 --- a/lib/openpgp/packet.rb +++ b/lib/openpgp/packet.rb @@ -178,14 +178,23 @@ def self.parse_body(body, options = {}) end ## - # @params [OpenPGP::Packet::LiteralData] m + # @params [OpenPGP::Packet::LiteralData | OpenPGP::Message] m # @params [Hash] signers in the same format as verifiers for Message def sign_data(m, signers) - self.type = m.format == :b ? 0x00 : 0x01 - m.normalize # Line endings + data = if m.is_a?(LiteralData) + self.type = m.format == :b ? 0x00 : 0x01 + m.normalize # Line endings + m.data + else + # m must be message where PublicKey is first, UserID is second + m = m.to_a # Se we can index into it + key = m[0].fingerprint_material.join + user_id = m[1].body + key + 0xB4.chr + [user_id.length].pack('N') + user_id + end update_trailer signer = signers[key_algorithm_name][hash_algorithm_name] - self.fields = signer.call(m.data + trailer) + self.fields = signer.call(data + trailer) self.fields = [fields] unless fields.is_a?(Enumerable) self.hash_head = fields.first[0,2].unpack('n').first end From 348cf5b86ef068b159471e040e482c68536feba6 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sat, 23 Jul 2011 09:18:52 -0500 Subject: [PATCH 30/32] Add OpenSSL wrapper for signing keys --- lib/openpgp/engine/openssl.rb | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/lib/openpgp/engine/openssl.rb b/lib/openpgp/engine/openssl.rb index 5a5a7a5..3f29368 100644 --- a/lib/openpgp/engine/openssl.rb +++ b/lib/openpgp/engine/openssl.rb @@ -119,6 +119,38 @@ def sign(packet, hash='SHA256', keyid=nil) OpenPGP::Message.new([sig, m]) end + ## + # @param packet message with key/userid packets to sign (@key must be set) + # @param [String] hash name of hash function to use (default SHA256) + # @param [String] keyid id of key to use (if there is more than one) + # @return OpenPGP::Message + def sign_key_userid(packet, hash='SHA256', keyid=nil) + if packet.is_a?(String) + packet = OpenPGP::Message.parse(packet) + elsif !packet.is_a?(OpenPGP::Message) + packet = OpenPGP::Message.new(packet) + end + + return nil unless @key && packet # Missing some data + + keyid = key.fingerprint[-16,16] unless keyid + + sig = packet.signature_and_data[1] + unless sig + sig = OpenPGP::Packet::Signature.new(:version => 4, + :key_algorithm => OpenPGP::Algorithm::Asymmetric::RSA, + :hash_algorithm => OpenPGP::Digest::for(hash).to_i, + :type => 0x13) + sig.hashed_subpackets << OpenPGP::Packet::Signature::SignatureCreationTime.new(Time.now.to_i) + sig.hashed_subpackets << OpenPGP::Packet::Signature::KeyFlags.new(0x01 | 0x02) + sig.unhashed_subpackets << OpenPGP::Packet::Signature::Issuer.new(keyid) + packet << sig + end + sig.sign_data(packet, {'RSA' => {hash => lambda {|m| rsa_key.sign(hash, m)}}}) + + packet + end + ## # @param packet # @return [OpenSSL::PKey::RSA] From fc3021aa584eb8a75ae4a58ef8c2952917503913 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sat, 23 Jul 2011 10:27:54 -0500 Subject: [PATCH 31/32] add bn2bin and egcd algorithms --- lib/openpgp/util.rb | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/lib/openpgp/util.rb b/lib/openpgp/util.rb index baa9f3d..96392cf 100644 --- a/lib/openpgp/util.rb +++ b/lib/openpgp/util.rb @@ -69,4 +69,48 @@ def self.bitlength(data) return 0 unless f Math.log(f.ord, 2).floor + 1 + (data.length*8) end + + ## + # Returns the network-byte-order representation of n + # @param [Numeric] n + # @return [String] + def self.bn2bin(n) + raise RangeError.new('Cannot convert negative number') if n < 0 + bytes = n.size + + # Mask off any leading 0 bytes + mask = 0xFF << (8 * bytes - 1) + while (mask & n) == 0 + mask >>= 8 + bytes -= 1 + end + + result = [] + bits_left = n + until bits_left == 0 + result << (bits_left & 0xFF).chr + bits_left >>= 8 + end + result.reverse.join + end + + ## + # Returns the multiplicative inverse of b, mod m + # @param [Numeric] b + # @param [Numeric] m + # @return [Numeric] + def self.egcd(b,m,recLevel=0) + if b % m == 0 + [0,1] + else + tmpVal = egcd(m, b % m, recLevel+1) + tmpVal2 = [tmpVal[1], tmpVal[0]-tmpVal[1] * ((b/m).to_i)] + if recLevel == 0 + tmpVal2[0] % m + else + tmpVal2 + end + end + end + end From cecfc66bb93e5718a9d26a5ecc07f69e778d43b0 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sat, 23 Jul 2011 10:28:04 -0500 Subject: [PATCH 32/32] keygen example script --- bin/keygen | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100755 bin/keygen diff --git a/bin/keygen b/bin/keygen new file mode 100755 index 0000000..54f9aac --- /dev/null +++ b/bin/keygen @@ -0,0 +1,17 @@ +#!/usr/bin/ruby +$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))) +require 'openpgp' + +nkey = OpenSSL::PKey::RSA.new(1024) + +nkey = OpenPGP::Packet::SecretKey.new(:key => nkey.params.merge({ + :u => OpenPGP.egcd(nkey.params['p'].to_i, nkey.params['q'].to_i) +}).inject({}) {|c, (k, v)| + c.merge!({k.intern => OpenPGP.bn2bin(v.to_i)}) +}, :algorithm => OpenPGP::Algorithm::Asymmetric::RSA, :version => 4, :timestamp => Time.now.to_i) + +uid = OpenPGP::Packet::UserID.new(:name => 'Test', :email => 'test@example.com') + +m = OpenPGP::Engine::OpenSSL::RSA.new(nkey).sign_key_userid([nkey, uid]) + +print m.to_s