Merge pull request #13 from cmrd-senya/entities-signatures
Add signature computation for entities support
This commit is contained in:
commit
a743f08158
18 changed files with 443 additions and 31 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ module DiasporaFederation
|
|||
# </XML>
|
||||
#
|
||||
# (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
|
||||
|
|
|
|||
48
lib/diaspora_federation/signing.rb
Normal file
48
lib/diaspora_federation/signing.rb
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
125
spec/lib/diaspora_federation/entities/relayable_spec.rb
Normal file
125
spec/lib/diaspora_federation/entities/relayable_spec.rb
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
<XML>
|
||||
<post>
|
||||
<test_entity>
|
||||
<a_prop_from_newer_diaspora_version>some value</a_prop_from_newer_diaspora_version>
|
||||
<test>asdf</test>
|
||||
<some_random_property>another value</some_random_property>
|
||||
</test_entity>
|
||||
</post>
|
||||
</XML>
|
||||
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
|
||||
|
|
|
|||
60
spec/lib/diaspora_federation/signing_spec.rb
Normal file
60
spec/lib/diaspora_federation/signing_spec.rb
Normal file
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue