From 91c3753019f56e637a3b84801d05290deae16ebf Mon Sep 17 00:00:00 2001 From: cmrd Senya Date: Wed, 11 Nov 2015 22:02:28 +0300 Subject: [PATCH] Add signature computation for entities support --- lib/diaspora_federation.rb | 6 + lib/diaspora_federation/entities/relayable.rb | 50 ++++++- lib/diaspora_federation/properties_dsl.rb | 28 ++-- lib/diaspora_federation/salmon/xml_payload.rb | 36 +++-- lib/diaspora_federation/signing.rb | 48 +++++++ spec/factories.rb | 19 ++- .../entities/comment_spec.rb | 4 +- .../entities/conversation_spec.rb | 6 +- .../diaspora_federation/entities/like_spec.rb | 4 +- .../entities/message_spec.rb | 4 +- .../entities/participation_spec.rb | 4 +- .../entities/poll_participation_spec.rb | 4 +- .../entities/relayable_spec.rb | 125 ++++++++++++++++++ .../salmon/xml_payload_spec.rb | 29 ++++ spec/lib/diaspora_federation/signing_spec.rb | 60 +++++++++ spec/spec_helper.rb | 4 + spec/support/shared_entity_specs.rb | 19 +++ .../initializers/diaspora_federation.rb | 24 ++++ 18 files changed, 443 insertions(+), 31 deletions(-) create mode 100644 lib/diaspora_federation/signing.rb create mode 100644 spec/lib/diaspora_federation/entities/relayable_spec.rb create mode 100644 spec/lib/diaspora_federation/signing_spec.rb diff --git a/lib/diaspora_federation.rb b/lib/diaspora_federation.rb index ffd1cbd..08598c5 100644 --- a/lib/diaspora_federation.rb +++ b/lib/diaspora_federation.rb @@ -7,6 +7,7 @@ require "diaspora_federation/validators" require "diaspora_federation/fetcher" +require "diaspora_federation/signing" require "diaspora_federation/entities" require "diaspora_federation/discovery" @@ -20,6 +21,11 @@ module DiasporaFederation fetch_person_for_webfinger fetch_person_for_hcard save_person_after_webfinger + fetch_private_key_by_id + fetch_private_key_by_post_guid + fetch_public_key_by_id + fetch_public_key_by_post_guid + post_author_is_local? ) class << self diff --git a/lib/diaspora_federation/entities/relayable.rb b/lib/diaspora_federation/entities/relayable.rb index 3144b9a..509d427 100644 --- a/lib/diaspora_federation/entities/relayable.rb +++ b/lib/diaspora_federation/entities/relayable.rb @@ -4,10 +4,56 @@ module DiasporaFederation def self.included(model) model.class_eval do property :parent_guid - property :parent_author_signature - property :author_signature + property :parent_author_signature, default: nil + property :author_signature, default: nil end end + + # Generates XML and updates signatures + def to_xml + xml = entity_xml + hash = to_h + Relayable.update_signatures!(hash) + + xml.at_xpath("author_signature").content = hash[:author_signature] + xml.at_xpath("parent_author_signature").content = hash[:parent_author_signature] + xml + end + + class SignatureVerificationFailed < ArgumentError + end + + def self.verify_signatures(data) + pkey = DiasporaFederation.callbacks.trigger(:fetch_public_key_by_id, data[:diaspora_id]) + raise SignatureVerificationFailed, "failed to fetch public key for #{data[:diaspora_id]}" if pkey.nil? + raise SignatureVerificationFailed, "wrong author_signature" unless Signing.verify_signature( + data, data[:author_signature], pkey + ) + + unless DiasporaFederation.callbacks.trigger(:post_author_is_local?, data[:parent_guid]) + # this happens only on downstream federation + pkey = DiasporaFederation.callbacks.trigger(:fetch_public_key_by_post_guid, data[:parent_guid]) + raise SignatureVerificationFailed, + "failed to fetch public key for parent of #{data[:parent_guid]}" if pkey.nil? + raise SignatureVerificationFailed, "wrong parent_author_signature" unless Signing.verify_signature( + data, data[:parent_author_signature], pkey + ) + end + end + + def self.update_signatures!(data) + if data[:author_signature].nil? + pkey = DiasporaFederation.callbacks.trigger(:fetch_private_key_by_id, data[:diaspora_id]) + data[:author_signature] = Signing.sign_with_key(data, pkey) unless pkey.nil? + end + + if data[:parent_author_signature].nil? + pkey = DiasporaFederation.callbacks.trigger(:fetch_private_key_by_post_guid, data[:parent_guid]) + data[:parent_author_signature] = Signing.sign_with_key(data, pkey) unless pkey.nil? + end + + data + end end end end diff --git a/lib/diaspora_federation/properties_dsl.rb b/lib/diaspora_federation/properties_dsl.rb index 12525d7..35ea4ae 100644 --- a/lib/diaspora_federation/properties_dsl.rb +++ b/lib/diaspora_federation/properties_dsl.rb @@ -68,17 +68,29 @@ module DiasporaFederation private + def determine_xml_name(name, type, opts={}) + raise ArgumentError, "xml_name is not supported for nested entities" if type != String && opts.has_key?(:xml_name) + + if type == String + if opts.has_key? :xml_name + raise InvalidName, "invalid xml_name" unless name_valid?(opts[:xml_name]) + opts[:xml_name] + else + name + end + elsif type.instance_of?(Array) + type.first.entity_name.to_sym + elsif type.ancestors.include?(Entity) + type.entity_name.to_sym + else + raise ArgumentError, "unknown type #{type} supplied" + end + end + def define_property(name, type, opts={}) raise InvalidName unless name_valid?(name) - xml_name = name - if opts.has_key? :xml_name - raise ArgumentError, "xml_name is not supported for nested entities" unless type == String - xml_name = opts[:xml_name] - raise InvalidName, "invalid xml_name" unless name_valid?(xml_name) - end - - class_props << {name: name, xml_name: xml_name, type: type} + class_props << {name: name, xml_name: determine_xml_name(name, type, opts), type: type} default_props[name] = opts[:default] if opts.has_key? :default instance_eval { attr_reader name } diff --git a/lib/diaspora_federation/salmon/xml_payload.rb b/lib/diaspora_federation/salmon/xml_payload.rb index 81d65db..0928c4a 100644 --- a/lib/diaspora_federation/salmon/xml_payload.rb +++ b/lib/diaspora_federation/salmon/xml_payload.rb @@ -11,7 +11,7 @@ module DiasporaFederation # # # (The +post+ element is there for historic reasons...) - class XmlPayload + module XmlPayload # Encapsulates an Entity inside the wrapping xml structure # and returns the XML Object. # @@ -85,19 +85,31 @@ module DiasporaFederation # @param [Nokogiri::XML::Element] node xml nodes # @return [Entity] instance def self.populate_entity(klass, node) - data = {} - klass.class_props.each do |prop_def| - name = prop_def[:name] - type = prop_def[:type] + # Build a hash of attributes basing on XML tree. If elements are known in "props" they respect the Entity logic. + # All other elemnts are respected and attached to resulted hash as string. + # It is intended to build a hash invariable of an Entity definition, in order to support receiving objects + # from the future versions of Diaspora, where new elements may have been added. + xml_names = klass.class_props.map {|prop_def| prop_def[:xml_name].to_s } - if type == String - data[name] = parse_string_from_node(prop_def[:xml_name], node) - elsif type.instance_of?(Array) - data[name] = parse_array_from_node(type, node) - elsif type.ancestors.include?(Entity) - data[name] = parse_entity_from_node(type, node) + data = node.element_children.map { |child| + xml_name = child.name + if xml_names.include?(xml_name) + prop = klass.class_props.find {|prop| prop[:xml_name].to_s == xml_name } + type = prop[:type] + + if type == String + [prop[:name], parse_string_from_node(xml_name, node)] + elsif type.instance_of?(Array) + [prop[:name], parse_array_from_node(type, node)] + elsif type.ancestors.include?(Entity) + [prop[:name], parse_entity_from_node(type, node)] + end + else + [xml_name, child.text] end - end + }.to_h + + Entities::Relayable.verify_signatures(data) if klass.included_modules.include?(Entities::Relayable) klass.new(data) end diff --git a/lib/diaspora_federation/signing.rb b/lib/diaspora_federation/signing.rb new file mode 100644 index 0000000..3ea3897 --- /dev/null +++ b/lib/diaspora_federation/signing.rb @@ -0,0 +1,48 @@ +module DiasporaFederation + module Signing + extend Logging + # @param [OpenSSL::PKey::RSA] key An RSA key + # @return [String] A Base64 encoded signature of #signable_string with key + def self.sign_with_key(hash, key) + sig = Base64.strict_encode64( + key.sign( + OpenSSL::Digest::SHA256.new, + signable_string(hash) + ) + ) + logger.info "event=sign_with_key status=complete guid=#{hash[:guid]}" + sig + end + + # Check that signature is a correct signature + # + # @param [String] signature The signature to be verified. + # @param [OpenSSL::PKey::RSA] key An RSA key + # @return [Boolean] + def self.verify_signature(hash, signature, key) + if key.nil? + logger.warn "event=verify_signature status=abort reason=no_key guid=#{hash[:guid]}" + return false + elsif signature.nil? + logger.warn "event=verify_signature status=abort reason=no_signature guid=#{hash[:guid]}" + return false + end + + validity = key.verify( + OpenSSL::Digest::SHA256.new, + Base64.decode64(signature), + signable_string(hash) + ) + logger.info "event=verify_signature status=complete guid=#{hash[:guid]} validity=#{validity}" + validity + end + + private + + def self.signable_string(hash) + hash.map { |name, value| + value.to_s unless name.match(/signature/) + }.compact.join(";") + end + end +end diff --git a/spec/factories.rb b/spec/factories.rb index b80e37e..81ad5cd 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -4,6 +4,23 @@ def r_str SecureRandom.hex(3) end +# +# Sort hash according to an entity class's property sequence. +# This is used for rspec tests in order to generate correct input hash to +# compare results with. +# +def sort_hash(data, klass) + klass.class_props.map { |prop| + [prop[:name], data[prop[:name]]] unless data[prop[:name]].nil? + }.compact.to_h +end + +def relayable_attributes_with_signatures(entity_type) + DiasporaFederation::Entities::Relayable.update_signatures!( + sort_hash(FactoryGirl.attributes_for(entity_type), FactoryGirl.factory_by_name(entity_type).build_class) + ) +end + FactoryGirl.define do initialize_with { new(attributes) } sequence(:guid) { UUID.generate :compact } @@ -95,8 +112,6 @@ FactoryGirl.define do factory :relayable_entity, class: DiasporaFederation::Entities::Relayable do parent_guid { generate(:guid) } - parent_author_signature { generate(:signature) } - author_signature { generate(:signature) } end factory :participation_entity, class: DiasporaFederation::Entities::Participation, parent: :relayable_entity do diff --git a/spec/lib/diaspora_federation/entities/comment_spec.rb b/spec/lib/diaspora_federation/entities/comment_spec.rb index 525c0c6..74e01d5 100644 --- a/spec/lib/diaspora_federation/entities/comment_spec.rb +++ b/spec/lib/diaspora_federation/entities/comment_spec.rb @@ -1,6 +1,6 @@ module DiasporaFederation describe Entities::Comment do - let(:data) { FactoryGirl.attributes_for(:comment_entity) } + let(:data) { relayable_attributes_with_signatures(:comment_entity) } let(:xml) { <<-XML @@ -18,5 +18,7 @@ XML it_behaves_like "an Entity subclass" it_behaves_like "an XML Entity" + + it_behaves_like "a relayable Entity" end end diff --git a/spec/lib/diaspora_federation/entities/conversation_spec.rb b/spec/lib/diaspora_federation/entities/conversation_spec.rb index 487baea..c93267e 100644 --- a/spec/lib/diaspora_federation/entities/conversation_spec.rb +++ b/spec/lib/diaspora_federation/entities/conversation_spec.rb @@ -1,7 +1,9 @@ module DiasporaFederation describe Entities::Conversation do - let(:msg1) { FactoryGirl.build(:message_entity) } - let(:msg2) { FactoryGirl.build(:message_entity) } + let(:msg1_data) { relayable_attributes_with_signatures(:message_entity) } + let(:msg2_data) { relayable_attributes_with_signatures(:message_entity) } + let(:msg1) { FactoryGirl.build(:message_entity, msg1_data) } + let(:msg2) { FactoryGirl.build(:message_entity, msg2_data) } let(:data) { FactoryGirl.attributes_for(:conversation_entity).merge!( messages: [msg1, msg2], diff --git a/spec/lib/diaspora_federation/entities/like_spec.rb b/spec/lib/diaspora_federation/entities/like_spec.rb index bc24952..ac85798 100644 --- a/spec/lib/diaspora_federation/entities/like_spec.rb +++ b/spec/lib/diaspora_federation/entities/like_spec.rb @@ -1,6 +1,6 @@ module DiasporaFederation describe Entities::Like do - let(:data) { FactoryGirl.attributes_for(:like_entity) } + let(:data) { relayable_attributes_with_signatures(:like_entity) } let(:xml) { <<-XML @@ -19,5 +19,7 @@ XML it_behaves_like "an Entity subclass" it_behaves_like "an XML Entity" + + it_behaves_like "a relayable Entity" end end diff --git a/spec/lib/diaspora_federation/entities/message_spec.rb b/spec/lib/diaspora_federation/entities/message_spec.rb index 93fb53f..2813f68 100644 --- a/spec/lib/diaspora_federation/entities/message_spec.rb +++ b/spec/lib/diaspora_federation/entities/message_spec.rb @@ -1,6 +1,6 @@ module DiasporaFederation describe Entities::Message do - let(:data) { FactoryGirl.attributes_for(:message_entity) } + let(:data) { relayable_attributes_with_signatures(:message_entity) } let(:xml) { <<-XML @@ -20,5 +20,7 @@ XML it_behaves_like "an Entity subclass" it_behaves_like "an XML Entity" + + it_behaves_like "a relayable Entity" end end diff --git a/spec/lib/diaspora_federation/entities/participation_spec.rb b/spec/lib/diaspora_federation/entities/participation_spec.rb index 53c9db0..cce73d2 100644 --- a/spec/lib/diaspora_federation/entities/participation_spec.rb +++ b/spec/lib/diaspora_federation/entities/participation_spec.rb @@ -1,6 +1,6 @@ module DiasporaFederation describe Entities::Participation do - let(:data) { FactoryGirl.attributes_for(:participation_entity) } + let(:data) { relayable_attributes_with_signatures(:participation_entity) } let(:xml) { <<-XML @@ -18,5 +18,7 @@ XML it_behaves_like "an Entity subclass" it_behaves_like "an XML Entity" + + it_behaves_like "a relayable Entity" end end diff --git a/spec/lib/diaspora_federation/entities/poll_participation_spec.rb b/spec/lib/diaspora_federation/entities/poll_participation_spec.rb index e711293..777f8ef 100644 --- a/spec/lib/diaspora_federation/entities/poll_participation_spec.rb +++ b/spec/lib/diaspora_federation/entities/poll_participation_spec.rb @@ -1,6 +1,6 @@ module DiasporaFederation describe Entities::PollParticipation do - let(:data) { FactoryGirl.attributes_for(:poll_participation_entity) } + let(:data) { relayable_attributes_with_signatures(:poll_participation_entity) } let(:xml) { <<-XML @@ -18,5 +18,7 @@ XML it_behaves_like "an Entity subclass" it_behaves_like "an XML Entity" + + it_behaves_like "a relayable Entity" end end diff --git a/spec/lib/diaspora_federation/entities/relayable_spec.rb b/spec/lib/diaspora_federation/entities/relayable_spec.rb new file mode 100644 index 0000000..a906b10 --- /dev/null +++ b/spec/lib/diaspora_federation/entities/relayable_spec.rb @@ -0,0 +1,125 @@ +module DiasporaFederation + describe Entities::Relayable do + let(:author_pkey) { OpenSSL::PKey::RSA.generate(1024) } + let(:parent_pkey) { OpenSSL::PKey::RSA.generate(1024) } + let(:hash) { + { + diaspora_id: FactoryGirl.generate(:diaspora_id), + parent_guid: FactoryGirl.generate(:guid), + some_other_data: "a_random_string" + } + } + + describe ".verify_signatures" do + it "doesn't raise anything if correct data were passed" do + hash[:author_signature] = Signing.sign_with_key(hash, author_pkey) + hash[:parent_author_signature] = Signing.sign_with_key(hash, parent_pkey) + + expect(DiasporaFederation.callbacks).to receive(:trigger).with(:fetch_public_key_by_id, hash[:diaspora_id]) + .and_return(author_pkey.public_key) + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with(:fetch_public_key_by_post_guid, hash[:parent_guid]) + .and_return(parent_pkey.public_key) + expect(DiasporaFederation.callbacks).to receive(:trigger).with(:post_author_is_local?, hash[:parent_guid]) + .and_return(false) + expect { Entities::Relayable.verify_signatures(hash) }.not_to raise_error + end + + it "raises when no public key for author was fetched" do + expect(DiasporaFederation.callbacks).to receive(:trigger).with(:fetch_public_key_by_id, anything) + .and_return(nil) + + expect { Entities::Relayable.verify_signatures(hash) }.to raise_error( + Entities::Relayable::SignatureVerificationFailed + ) + end + + it "raises when bad author signature was passed" do + hash[:author_signature] = nil + + expect(DiasporaFederation.callbacks).to receive(:trigger).with(:fetch_public_key_by_id, hash[:diaspora_id]) + .and_return(author_pkey.public_key) + expect { Entities::Relayable.verify_signatures(hash) }.to raise_error( + Entities::Relayable::SignatureVerificationFailed + ) + end + + it "raises when no public key for parent author was fetched" do + hash[:author_signature] = Signing.sign_with_key(hash, author_pkey) + + expect(DiasporaFederation.callbacks).to receive(:trigger).with(:fetch_public_key_by_id, hash[:diaspora_id]) + .and_return(author_pkey.public_key) + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with(:fetch_public_key_by_post_guid, hash[:parent_guid]) + .and_return(nil) + expect(DiasporaFederation.callbacks).to receive(:trigger).with(:post_author_is_local?, hash[:parent_guid]) + .and_return(false) + expect { Entities::Relayable.verify_signatures(hash) }.to raise_error( + Entities::Relayable::SignatureVerificationFailed + ) + end + + it "raises when bad parent author signature was passed" do + hash[:author_signature] = Signing.sign_with_key(hash, author_pkey) + hash[:parent_author_signature] = nil + + expect(DiasporaFederation.callbacks).to receive(:trigger).with(:fetch_public_key_by_id, hash[:diaspora_id]) + .and_return(author_pkey.public_key) + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with(:fetch_public_key_by_post_guid, hash[:parent_guid]) + .and_return(parent_pkey.public_key) + expect(DiasporaFederation.callbacks).to receive(:trigger).with(:post_author_is_local?, hash[:parent_guid]) + .and_return(false) + expect { Entities::Relayable.verify_signatures(hash) }.to raise_error( + Entities::Relayable::SignatureVerificationFailed + ) + end + + it "doesn't raise if parent_author_signature isn't set but we're on upstream federation" do + hash[:author_signature] = Signing.sign_with_key(hash, author_pkey) + hash[:parent_author_signature] = nil + + expect(DiasporaFederation.callbacks).to receive(:trigger).with(:fetch_public_key_by_id, hash[:diaspora_id]) + .and_return(author_pkey.public_key) + expect(DiasporaFederation.callbacks).to receive(:trigger).with(:post_author_is_local?, hash[:parent_guid]) + .and_return(true) + expect { Entities::Relayable.verify_signatures(hash) }.not_to raise_error + end + end + + describe ".update_singatures!" do + it "updates signatures when they were nil and keys were supplied" do + expect(DiasporaFederation.callbacks).to receive(:trigger).with(:fetch_private_key_by_id, hash[:diaspora_id]) + .and_return(author_pkey) + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with(:fetch_private_key_by_post_guid, hash[:parent_guid]) + .and_return(parent_pkey) + + Entities::Relayable.update_signatures!(hash) + expect(Signing.verify_signature(hash, hash[:author_signature], author_pkey)).to be_truthy + expect(Signing.verify_signature(hash, hash[:parent_author_signature], parent_pkey)).to be_truthy + end + + it "doesn't change signatures if they are already set" do + signatures = {author_signature: "aa", parent_author_signature: "bb"} + hash.merge!(signatures) + + Entities::Relayable.update_signatures!(hash) + expect(hash[:author_signature]).to eq(signatures[:author_signature]) + expect(hash[:parent_author_signature]).to eq(signatures[:parent_author_signature]) + end + + it "doesn't change signatures if keys weren't supplied" do + expect(DiasporaFederation.callbacks).to receive(:trigger).with(:fetch_private_key_by_id, hash[:diaspora_id]) + .and_return(nil) + expect(DiasporaFederation.callbacks).to receive(:trigger) + .with(:fetch_private_key_by_post_guid, hash[:parent_guid]) + .and_return(nil) + + Entities::Relayable.update_signatures!(hash) + expect(hash[:author_signature]).to eq(nil) + expect(hash[:parent_author_signature]).to eq(nil) + end + end + end +end diff --git a/spec/lib/diaspora_federation/salmon/xml_payload_spec.rb b/spec/lib/diaspora_federation/salmon/xml_payload_spec.rb index 14b10a5..5aaceb9 100644 --- a/spec/lib/diaspora_federation/salmon/xml_payload_spec.rb +++ b/spec/lib/diaspora_federation/salmon/xml_payload_spec.rb @@ -121,6 +121,35 @@ XML expect(entity.test).to eq("asdf") expect(entity.qwer).to eq("qwer") end + + it "doesn't drop unknown properties" do + xml = <<-XML + + + + some value + asdf + another value + + + +XML + expect(Entities::TestEntity).to receive(:new).with( + "a_prop_from_newer_diaspora_version" => "some value", + :test => "asdf", + "some_random_property" => "another value" + ) + Salmon::XmlPayload.unpack(Nokogiri::XML::Document.parse(xml).root) + end + end + + context "relayable signature verification feature support" do + it "calls signatures verification on relayable unpack" do + entity = FactoryGirl.build(:comment_entity) + payload = Salmon::XmlPayload.pack(entity) + expect(Entities::Relayable).to receive(:verify_signatures).once + Salmon::XmlPayload.unpack(payload) + end end context "nested entities" do diff --git a/spec/lib/diaspora_federation/signing_spec.rb b/spec/lib/diaspora_federation/signing_spec.rb new file mode 100644 index 0000000..3c776e2 --- /dev/null +++ b/spec/lib/diaspora_federation/signing_spec.rb @@ -0,0 +1,60 @@ +module DiasporaFederation + describe Signing do + let(:pkey) { + OpenSSL::PKey::RSA.new <<-RSA +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDT7vBTAl0Z55bPcBjM9dvSOTuVtBxsgfrw2W0hTAYpd1H5032C +cVW3mqd0l/9BHscgudVFAkvp+nf+wTQILn4qH4YAhOdWgrlSBA6Rbs3cmtmXzGNq +oQr4NOMbqs6sP+bBjDuDdB+cAFms/NDUH3cHBKPXi3e3csxiErmN1zyfWwIDAQAB +AoGAbpBC1CtxgqgtJz8l0ReafIvbJ/h0s68DyU7E/g/5TvyuyZSp77lMrKKEJfF9 ++u0hmVMZjgzqqcA/haopiPMoYcJAwwhJLeXAgAWA+8j60Y524WLDcMPwMxQvVFd9 +3FYXdOalojDoS34BWeBy6Gt+lLGyDvo/NnJBqIMPN0/KzYECQQDuslE4f1+RHhUq +wf2rL/7gCgrnkDOcH1SPjN2FrKG5ALmjThCq7Wr1Umj81uvmglfpIRY/ORgYgujA +kwNTB1ohAkEA40v0mHaYDegL//jucFmx/iK9Bs/722rJGIXI7bGIwLRC1hW101h3 +DLMEMT0QaamVEEnrXFdqhjz+bfYfqUkh+wJAU3a+t8ayIAgo1p6mmKlbsfNRBM+D +fF/oLZnQC+HlWs9KGjQ918bU05tRYre0HRIOs1ICeXD5X/jGci/1xZ6YgQJAJony +Zwd0sKbvoe8rPpF2xIhPVKBfK8znW+kTMHoxnbryuinkMnmFdfnEdDTOW5wNUj22 +Umnf/fLJkQtyQtnLkQJBANMoQPrP6aMRh45bhq+y6DbzHHHc2T5cuGBCtnhu+qrK +hWHXqQT4rArfq8YBpvDUa7qD13WwFGK3TPRpQSVGzNg= +-----END RSA PRIVATE KEY----- +RSA + } + + let(:hash) { + { + param1: "1", + param2: "2", + signature: "SIGNATURE_VALUE==", + param3: "3", + parent_signature: "SIGNATURE2_VALUE==", + param4: "4" + } + } + let(:signature) { + "OesXlpesuLcA0t8gPyBjvznvkl0pz63p8z6+o2fxFNUaZkuR6YQv/sJOTSMPYBAFwcWr048Ol7yw4jSHq0gFCdBBeF7Mg287jktCie"\ + "xa6G6mA24hBlOWnyRJLV2OyqcTU1P5pXWlUc1Mbwbr6bSIs6VK9djFMLLQ6wjjpusJ0XU=" + } + + describe ".signable_string" do + it "forms correct string for a hash" do + expect(Signing.signable_string(hash)).to eq("1;2;3;4") + end + end + + describe ".sign_with_key" do + it "produces correct signature" do + expect(Signing.sign_with_key(hash, pkey)).to eq(signature) + end + end + + describe ".verify_signature" do + it "verifies correct signature" do + expect(Signing.verify_signature(hash, signature, pkey)).to be_truthy + end + + it "doesn't verify wrong signature" do + expect(Signing.verify_signature(hash, "false signature==", pkey)).to be_falsy + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 68985bc..7f58320 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -33,6 +33,10 @@ def alice @alice ||= Person.find_by(diaspora_id: "alice@localhost:3000") end +def test_pkey + DiasporaFederation.callbacks.trigger(:fetch_private_key_by_id) +end + # Requires supporting files with custom matchers and macros, etc, # in ./support/ and its subdirectories. fixture_builder_file = "#{File.dirname(__FILE__)}/support/fixture_builder.rb" diff --git a/spec/support/shared_entity_specs.rb b/spec/support/shared_entity_specs.rb index 949f486..1f02a56 100644 --- a/spec/support/shared_entity_specs.rb +++ b/spec/support/shared_entity_specs.rb @@ -70,3 +70,22 @@ shared_examples "an XML Entity" do end end end + +shared_examples "a relayable Entity" do + let(:instance) { described_class.new(data.merge(author_signature: nil, parent_author_signature: nil)) } + + context "signatures generation" do + it "computes correct signatures for the entity" do + hash = instance.to_h + xml = DiasporaFederation::Salmon::XmlPayload.pack(instance) + + author_signature = xml.at_xpath("post/*[1]/author_signature").text + parent_author_signature = xml.at_xpath("post/*[1]/parent_author_signature").text + + expect(DiasporaFederation::Signing.verify_signature(hash, author_signature, test_pkey)) + .to be_truthy + expect(DiasporaFederation::Signing.verify_signature(hash, parent_author_signature, test_pkey)) + .to be_truthy + end + end +end diff --git a/test/dummy/config/initializers/diaspora_federation.rb b/test/dummy/config/initializers/diaspora_federation.rb index 5056d09..bfd817e 100644 --- a/test/dummy/config/initializers/diaspora_federation.rb +++ b/test/dummy/config/initializers/diaspora_federation.rb @@ -58,5 +58,29 @@ DiasporaFederation.configure do |config| serialized_public_key: person.exported_key, url: person.url).save! end end + + def pkey + @test_pkey ||= OpenSSL::PKey::RSA.generate(1024) + end + + on :fetch_private_key_by_id do + pkey + end + + on :fetch_private_key_by_post_guid do + pkey + end + + on :fetch_public_key_by_id do + pkey.public_key + end + + on :fetch_public_key_by_post_guid do + pkey.public_key + end + + on :post_author_is_local? do + false + end end end