move signing logic for relayables to Relayable

refactoring exceptions for relayables
This commit is contained in:
Benjamin Neff 2016-01-31 15:56:01 +01:00
parent 714f6d8273
commit b19e1b8e52
11 changed files with 152 additions and 194 deletions

View file

@ -7,7 +7,6 @@ require "diaspora_federation/validators"
require "diaspora_federation/fetcher" require "diaspora_federation/fetcher"
require "diaspora_federation/signing"
require "diaspora_federation/entities" require "diaspora_federation/entities"
require "diaspora_federation/discovery" require "diaspora_federation/discovery"
@ -221,7 +220,7 @@ module DiasporaFederation
end end
def configuration_error(message) def configuration_error(message)
logger.fatal("diaspora federation configuration error: #{message}") logger.fatal "diaspora federation configuration error: #{message}"
raise ConfigurationError, message raise ConfigurationError, message
end end
end end

View file

@ -2,9 +2,14 @@ module DiasporaFederation
module Entities module Entities
# this is a module that defines common properties for relayable entities # this is a module that defines common properties for relayable entities
# which include Like, Comment, Participation, Message, etc. Each relayable # which include Like, Comment, Participation, Message, etc. Each relayable
# has a parent, identified by guid. Relayables also are signed and signing/verificating # has a parent, identified by guid. Relayables also are signed and signing/verification
# logic is embedded into Salmon XML processing code. # logic is embedded into Salmon XML processing code.
module Relayable module Relayable
include Logging
# digest instance used for signing
DIGEST = OpenSSL::Digest::SHA256.new
# on inclusion of this module the required properties for a relayable are added to the object that includes it # on inclusion of this module the required properties for a relayable are added to the object that includes it
# #
# @!attribute [r] parent_guid # @!attribute [r] parent_guid
@ -43,15 +48,12 @@ module DiasporaFederation
super.tap do |hash| super.tap do |hash|
if author_signature.nil? if author_signature.nil?
privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key_by_diaspora_id, diaspora_id) privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key_by_diaspora_id, diaspora_id)
hash[:author_signature] = Signing.sign_with_key(hash, privkey) unless privkey.nil? raise AuthorPrivateKeyNotFound, "author=#{diaspora_id} guid=#{guid}" if privkey.nil?
hash[:author_signature] = sign_with_key(privkey, hash)
logger.info "event=sign_with_key signature=author_signature author=#{diaspora_id} guid=#{guid}"
end end
if parent_author_signature.nil? try_sign_with_parent_author(hash) if parent_author_signature.nil?
privkey = DiasporaFederation.callbacks.trigger(
:fetch_author_private_key_by_entity_guid, parent_type, parent_guid
)
hash[:parent_author_signature] = Signing.sign_with_key(hash, privkey) unless privkey.nil?
end
end end
end end
@ -66,33 +68,84 @@ module DiasporaFederation
end end
end end
# Exception raised when verify_signatures fails to verify signatures (signatures are wrong)
class SignatureVerificationFailed < ArgumentError
end
# verifies the signatures (+author_signature+ and +parent_author_signature+ if needed) # verifies the signatures (+author_signature+ and +parent_author_signature+ if needed)
# @raise [SignatureVerificationFailed] if the signature is not valid or no public key is found # @raise [SignatureVerificationFailed] if the signature is not valid or no public key is found
def verify_signatures def verify_signatures
pubkey = DiasporaFederation.callbacks.trigger(:fetch_public_key_by_diaspora_id, diaspora_id) pubkey = DiasporaFederation.callbacks.trigger(:fetch_public_key_by_diaspora_id, diaspora_id)
raise SignatureVerificationFailed, "failed to fetch public key for #{diaspora_id}" if pubkey.nil? raise PublicKeyNotFound, "author_signature author=#{diaspora_id} guid=#{guid}" if pubkey.nil?
raise SignatureVerificationFailed, "wrong author_signature" unless Signing.verify_signature( raise SignatureVerificationFailed, "wrong author_signature" unless verify_signature(pubkey, author_signature)
data, author_signature, pubkey
)
author_is_local = DiasporaFederation.callbacks.trigger(:entity_author_is_local?, parent_type, parent_guid) parent_author_local = DiasporaFederation.callbacks.trigger(:entity_author_is_local?, parent_type, parent_guid)
verify_parent_signature unless author_is_local verify_parent_author_signature unless parent_author_local
end end
private private
# sign with parent author, if the parent author is local (if the private key is found)
# @param [Hash] hash the hash to sign
def try_sign_with_parent_author(hash)
privkey = DiasporaFederation.callbacks.trigger(
:fetch_author_private_key_by_entity_guid, parent_type, parent_guid
)
unless privkey.nil?
hash[:parent_author_signature] = sign_with_key(privkey, hash)
logger.info "event=sign_with_key signature=parent_author_signature guid=#{guid}"
end
end
# this happens only on downstream federation # this happens only on downstream federation
def verify_parent_signature def verify_parent_author_signature
pubkey = DiasporaFederation.callbacks.trigger(:fetch_author_public_key_by_entity_guid, parent_type, parent_guid) pubkey = DiasporaFederation.callbacks.trigger(:fetch_author_public_key_by_entity_guid, parent_type, parent_guid)
raise SignatureVerificationFailed, "failed to fetch public key for author of #{parent_guid}" if pubkey.nil? raise PublicKeyNotFound, "parent_author_signature parent_guid=#{parent_guid} guid=#{guid}" if pubkey.nil?
raise SignatureVerificationFailed, "wrong parent_author_signature" unless Signing.verify_signature( unless verify_signature(pubkey, parent_author_signature)
data, parent_author_signature, pubkey raise SignatureVerificationFailed, "wrong parent_author_signature parent_guid=#{parent_guid}"
) end
end
# Sign the data with the key
#
# @param [OpenSSL::PKey::RSA] privkey An RSA key
# @param [Hash] hash data to sign
# @return [String] A Base64 encoded signature of #signable_string with key
def sign_with_key(privkey, hash)
Base64.strict_encode64(privkey.sign(DIGEST, signable_string(hash)))
end
# Check that signature is a correct signature
#
# @param [OpenSSL::PKey::RSA] pubkey An RSA key
# @param [String] signature The signature to be verified.
# @return [Boolean]
def verify_signature(pubkey, signature)
if signature.nil?
logger.warn "event=verify_signature status=abort reason=no_signature guid=#{guid}"
return false
end
validity = pubkey.verify(DIGEST, Base64.decode64(signature), signable_string(data))
logger.info "event=verify_signature status=complete guid=#{guid} validity=#{validity}"
validity
end
# @param [Hash] hash data to sign
# @return [String] signature data string
def signable_string(hash)
hash.map {|name, value|
value.to_s unless name =~ /signature/
}.compact.join(";")
end
# Exception raised when creating the author_signature failes, because the private key was not found
class AuthorPrivateKeyNotFound < RuntimeError
end
# Exception raised when verify_signatures fails to verify signatures (no public key found)
class PublicKeyNotFound < RuntimeError
end
# Exception raised when verify_signatures fails to verify signatures (signatures are wrong)
class SignatureVerificationFailed < RuntimeError
end end
end end
end end

View file

@ -64,7 +64,7 @@ module DiasporaFederation
# @param [SignedRetraction, RelayableRetraction] ret the retraction to sign # @param [SignedRetraction, RelayableRetraction] ret the retraction to sign
# @return [String] a Base64 encoded signature of the retraction with the key # @return [String] a Base64 encoded signature of the retraction with the key
def self.sign_with_key(privkey, ret) def self.sign_with_key(privkey, ret)
Base64.strict_encode64(privkey.sign(OpenSSL::Digest::SHA256.new, [ret.target_guid, ret.target_type].join(";"))) Base64.strict_encode64(privkey.sign(Relayable::DIGEST, [ret.target_guid, ret.target_type].join(";")))
end end
end end
end end

View file

@ -1,11 +1,11 @@
module DiasporaFederation module DiasporaFederation
module Federation module Federation
# Raised if failed to fetch a public key of the sender of the received message # Raised if failed to fetch a public key of the sender of the received message
class SenderKeyNotFound < Exception class SenderKeyNotFound < RuntimeError
end end
# Raised if recipient private key is missing for a private receive # Raised if recipient private key is missing for a private receive
class RecipientKeyNotFound < Exception class RecipientKeyNotFound < RuntimeError
end end
end end
end end

View file

@ -1,55 +0,0 @@
module DiasporaFederation
# this module defines operations of signing an arbitrary hash with an arbitrary key
module Signing
extend Logging
# Sign the data with the key
#
# @param [Hash] hash data to sign
# @param [OpenSSL::PKey::RSA] privkey An RSA key
# @return [String] A Base64 encoded signature of #signable_string with key
def self.sign_with_key(hash, privkey)
sig = Base64.strict_encode64(
privkey.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 [Hash] hash data to verify
# @param [String] signature The signature to be verified.
# @param [OpenSSL::PKey::RSA] pubkey An RSA key
# @return [Boolean]
def self.verify_signature(hash, signature, pubkey)
if pubkey.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 = pubkey.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
# @param [Hash] hash data to sign
# @return [String] signature data string
def self.signable_string(hash)
hash.map {|name, value|
value.to_s unless name =~ /signature/
}.compact.join(";")
end
private_class_method :signable_string
end
end

View file

@ -31,12 +31,12 @@ XML
:fetch_private_key_by_diaspora_id, hash[:diaspora_id] :fetch_private_key_by_diaspora_id, hash[:diaspora_id]
).and_return(author_pkey) ).and_return(author_pkey)
signed_string = "#{hash[:target_guid]};#{hash[:target_type]}"
signed_hash = Entities::RelayableRetraction.new(hash).to_h signed_hash = Entities::RelayableRetraction.new(hash).to_h
signable_hash = hash.select do |key, _| signature = Base64.decode64(signed_hash[:target_author_signature])
%i(target_guid target_type).include?(key) expect(author_pkey.verify(OpenSSL::Digest::SHA256.new, signature, signed_string)).to be_truthy
end
expect(Signing.verify_signature(signable_hash, signed_hash[:target_author_signature], author_pkey)).to be_truthy
end end
it "updates parent author signature when it was nil, key was supplied and sender is not author of the target" do it "updates parent author signature when it was nil, key was supplied and sender is not author of the target" do
@ -48,12 +48,12 @@ XML
:fetch_private_key_by_diaspora_id, hash[:diaspora_id] :fetch_private_key_by_diaspora_id, hash[:diaspora_id]
).and_return(author_pkey) ).and_return(author_pkey)
signed_string = "#{hash[:target_guid]};#{hash[:target_type]}"
signed_hash = Entities::RelayableRetraction.new(hash).to_h signed_hash = Entities::RelayableRetraction.new(hash).to_h
signable_hash = hash.select do |key, _| signature = Base64.decode64(signed_hash[:parent_author_signature])
%i(target_guid target_type).include?(key) expect(author_pkey.verify(OpenSSL::Digest::SHA256.new, signature, signed_string)).to be_truthy
end
expect(Signing.verify_signature(signable_hash, signed_hash[:parent_author_signature], author_pkey)).to be_truthy
end end
it "doesn't change signatures if they are already set" do it "doesn't change signatures if they are already set" do

View file

@ -4,6 +4,7 @@ module DiasporaFederation
let(:parent_pkey) { OpenSSL::PKey::RSA.generate(1024) } let(:parent_pkey) { OpenSSL::PKey::RSA.generate(1024) }
let(:hash) { let(:hash) {
{ {
guid: FactoryGirl.generate(:guid),
diaspora_id: FactoryGirl.generate(:diaspora_id), diaspora_id: FactoryGirl.generate(:diaspora_id),
parent_guid: FactoryGirl.generate(:guid), parent_guid: FactoryGirl.generate(:guid),
some_other_data: "a_random_string" some_other_data: "a_random_string"
@ -11,33 +12,39 @@ module DiasporaFederation
} }
class SomeRelayable < Entity class SomeRelayable < Entity
include Entities::Relayable property :guid
property :diaspora_id, xml_name: :diaspora_handle property :diaspora_id, xml_name: :diaspora_handle
include Entities::Relayable
def parent_type def parent_type
"Target" "Parent"
end end
end end
def legacy_sign_with_key(privkey, hash)
Base64.strict_encode64(privkey.sign(OpenSSL::Digest::SHA256.new, hash.values.join(";")))
end
describe "#verify_signatures" do describe "#verify_signatures" do
it "doesn't raise anything if correct data were passed" do it "doesn't raise anything if correct data were passed" do
hash[:author_signature] = Signing.sign_with_key(hash, author_pkey) signed_hash = hash.dup
hash[:parent_author_signature] = Signing.sign_with_key(hash, parent_pkey) signed_hash[:author_signature] = legacy_sign_with_key(author_pkey, hash)
signed_hash[:parent_author_signature] = legacy_sign_with_key(parent_pkey, hash)
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(
:fetch_public_key_by_diaspora_id, hash[:diaspora_id] :fetch_public_key_by_diaspora_id, hash[:diaspora_id]
).and_return(author_pkey.public_key) ).and_return(author_pkey.public_key)
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(
:fetch_author_public_key_by_entity_guid, "Target", hash[:parent_guid] :fetch_author_public_key_by_entity_guid, "Parent", hash[:parent_guid]
).and_return(parent_pkey.public_key) ).and_return(parent_pkey.public_key)
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(
:entity_author_is_local?, "Target", hash[:parent_guid] :entity_author_is_local?, "Parent", hash[:parent_guid]
).and_return(false) ).and_return(false)
expect { SomeRelayable.new(hash).verify_signatures }.not_to raise_error expect { SomeRelayable.new(signed_hash).verify_signatures }.not_to raise_error
end end
it "raises when no public key for author was fetched" do it "raises when no public key for author was fetched" do
@ -47,7 +54,7 @@ module DiasporaFederation
expect { expect {
SomeRelayable.new(hash).verify_signatures SomeRelayable.new(hash).verify_signatures
}.to raise_error Entities::Relayable::SignatureVerificationFailed }.to raise_error Entities::Relayable::PublicKeyNotFound
end end
it "raises when bad author signature was passed" do it "raises when bad author signature was passed" do
@ -63,27 +70,27 @@ module DiasporaFederation
end end
it "raises when no public key for parent author was fetched" do it "raises when no public key for parent author was fetched" do
hash[:author_signature] = Signing.sign_with_key(hash, author_pkey) hash[:author_signature] = legacy_sign_with_key(author_pkey, hash)
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(
:fetch_public_key_by_diaspora_id, hash[:diaspora_id] :fetch_public_key_by_diaspora_id, hash[:diaspora_id]
).and_return(author_pkey.public_key) ).and_return(author_pkey.public_key)
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(
:fetch_author_public_key_by_entity_guid, "Target", hash[:parent_guid] :fetch_author_public_key_by_entity_guid, "Parent", hash[:parent_guid]
).and_return(nil) ).and_return(nil)
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(
:entity_author_is_local?, "Target", hash[:parent_guid] :entity_author_is_local?, "Parent", hash[:parent_guid]
).and_return(false) ).and_return(false)
expect { expect {
SomeRelayable.new(hash).verify_signatures SomeRelayable.new(hash).verify_signatures
}.to raise_error Entities::Relayable::SignatureVerificationFailed }.to raise_error Entities::Relayable::PublicKeyNotFound
end end
it "raises when bad parent author signature was passed" do it "raises when bad parent author signature was passed" do
hash[:author_signature] = Signing.sign_with_key(hash, author_pkey) hash[:author_signature] = legacy_sign_with_key(author_pkey, hash)
hash[:parent_author_signature] = nil hash[:parent_author_signature] = nil
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(
@ -91,11 +98,11 @@ module DiasporaFederation
).and_return(author_pkey.public_key) ).and_return(author_pkey.public_key)
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(
:fetch_author_public_key_by_entity_guid, "Target", hash[:parent_guid] :fetch_author_public_key_by_entity_guid, "Parent", hash[:parent_guid]
).and_return(parent_pkey.public_key) ).and_return(parent_pkey.public_key)
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(
:entity_author_is_local?, "Target", hash[:parent_guid] :entity_author_is_local?, "Parent", hash[:parent_guid]
).and_return(false) ).and_return(false)
expect { expect {
@ -104,7 +111,7 @@ module DiasporaFederation
end end
it "doesn't raise if parent_author_signature isn't set but we're on upstream federation" do 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[:author_signature] = legacy_sign_with_key(author_pkey, hash)
hash[:parent_author_signature] = nil hash[:parent_author_signature] = nil
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(
@ -112,7 +119,7 @@ module DiasporaFederation
).and_return(author_pkey.public_key) ).and_return(author_pkey.public_key)
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(
:entity_author_is_local?, "Target", hash[:parent_guid] :entity_author_is_local?, "Parent", hash[:parent_guid]
).and_return(true) ).and_return(true)
expect { SomeRelayable.new(hash).verify_signatures }.not_to raise_error expect { SomeRelayable.new(hash).verify_signatures }.not_to raise_error
@ -126,13 +133,19 @@ module DiasporaFederation
).and_return(author_pkey) ).and_return(author_pkey)
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(
:fetch_author_private_key_by_entity_guid, "Target", hash[:parent_guid] :fetch_author_private_key_by_entity_guid, "Parent", hash[:parent_guid]
).and_return(parent_pkey) ).and_return(parent_pkey)
signed_string = hash.reject {|key, _| key == :some_other_data }.values.join(";")
signed_hash = SomeRelayable.new(hash).to_h signed_hash = SomeRelayable.new(hash).to_h
expect(Signing.verify_signature(signed_hash, signed_hash[:author_signature], author_pkey)).to be_truthy def verify_signature(pubkey, signature, signed_string)
expect(Signing.verify_signature(signed_hash, signed_hash[:parent_author_signature], parent_pkey)).to be_truthy pubkey.verify(OpenSSL::Digest::SHA256.new, Base64.decode64(signature), signed_string)
end
expect(verify_signature(author_pkey, signed_hash[:author_signature], signed_string)).to be_truthy
expect(verify_signature(parent_pkey, signed_hash[:parent_author_signature], signed_string)).to be_truthy
end end
it "doesn't change signatures if they are already set" do it "doesn't change signatures if they are already set" do
@ -141,18 +154,27 @@ module DiasporaFederation
expect(SomeRelayable.new(hash).to_h).to eq(hash) expect(SomeRelayable.new(hash).to_h).to eq(hash)
end end
it "doesn't change signatures if keys weren't supplied" do it "raises when author_signature not set and key isn't supplied" do
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(
:fetch_private_key_by_diaspora_id, hash[:diaspora_id] :fetch_private_key_by_diaspora_id, hash[:diaspora_id]
).and_return(nil) ).and_return(nil)
expect {
SomeRelayable.new(hash).to_h
}.to raise_error Entities::Relayable::AuthorPrivateKeyNotFound
end
it "doesn't set parent_author_signature if key isn't supplied" do
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(
:fetch_author_private_key_by_entity_guid, "Target", hash[:parent_guid] :fetch_private_key_by_diaspora_id, hash[:diaspora_id]
).and_return(author_pkey)
expect(DiasporaFederation.callbacks).to receive(:trigger).with(
:fetch_author_private_key_by_entity_guid, "Parent", hash[:parent_guid]
).and_return(nil) ).and_return(nil)
signed_hash = SomeRelayable.new(hash).to_h signed_hash = SomeRelayable.new(hash).to_h
expect(signed_hash[:author_signature]).to eq(nil)
expect(signed_hash[:parent_author_signature]).to eq(nil) expect(signed_hash[:parent_author_signature]).to eq(nil)
end end
end end

View file

@ -26,13 +26,14 @@ XML
:fetch_private_key_by_diaspora_id, hash[:diaspora_id] :fetch_private_key_by_diaspora_id, hash[:diaspora_id]
).and_return(author_pkey) ).and_return(author_pkey)
signable_hash = hash.select do |key, _| signed_string = "#{hash[:target_guid]};#{hash[:target_type]}"
%i(target_guid target_type).include?(key)
end
signed_hash = Entities::SignedRetraction.new(hash).to_h signed_hash = Entities::SignedRetraction.new(hash).to_h
expect(Signing.verify_signature(signable_hash, signed_hash[:target_author_signature], author_pkey)).to be_truthy valid = author_pkey.verify(
OpenSSL::Digest::SHA256.new, Base64.decode64(signed_hash[:target_author_signature]), signed_string
)
expect(valid).to be_truthy
end end
it "doesn't change signature if it is already set" do it "doesn't change signature if it is already set" do

View file

@ -147,8 +147,11 @@ XML
it "calls signatures verification on relayable unpack" do it "calls signatures verification on relayable unpack" do
entity = FactoryGirl.build(:comment_entity) entity = FactoryGirl.build(:comment_entity)
payload = Salmon::XmlPayload.pack(entity) payload = Salmon::XmlPayload.pack(entity)
expect(Signing).to receive(:verify_signature).twice.and_call_original payload.at_xpath("post/*[1]/author_signature").content = nil
Salmon::XmlPayload.unpack(payload)
expect {
Salmon::XmlPayload.unpack(payload)
}.to raise_error DiasporaFederation::Entities::Relayable::SignatureVerificationFailed
end end
end end

View file

@ -1,68 +0,0 @@
module DiasporaFederation
describe Signing do
let(:privkey) {
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.send(: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, privkey)).to eq(signature)
end
end
describe ".verify_signature" do
it "verifies correct signature" do
expect(Signing.verify_signature(hash, signature, privkey.public_key)).to be_truthy
end
it "doesn't verify wrong signature" do
expect(Signing.verify_signature(hash, "false signature==", privkey.public_key)).to be_falsy
end
it "doesn't verify when signature is missing" do
expect(Signing.verify_signature(hash, nil, privkey.public_key)).to be_falsy
end
it "doesn't verify when public key is missing" do
expect(Signing.verify_signature(hash, signature, nil)).to be_falsy
end
end
end
end

View file

@ -75,17 +75,20 @@ shared_examples "a relayable Entity" do
let(:instance) { described_class.new(data.merge(author_signature: nil, parent_author_signature: nil)) } let(:instance) { described_class.new(data.merge(author_signature: nil, parent_author_signature: nil)) }
context "signatures generation" do context "signatures generation" do
def legacy_verify_signature(pubkey, signature, signed_string)
pubkey.verify(OpenSSL::Digest::SHA256.new, Base64.decode64(signature), signed_string)
end
it "computes correct signatures for the entity" do it "computes correct signatures for the entity" do
hash = instance.to_h signed_string = instance.to_h.map {|name, value| value.to_s unless name =~ /signature/ }.compact.join(";")
xml = DiasporaFederation::Salmon::XmlPayload.pack(instance) xml = DiasporaFederation::Salmon::XmlPayload.pack(instance)
author_signature = xml.at_xpath("post/*[1]/author_signature").text author_signature = xml.at_xpath("post/*[1]/author_signature").text
parent_author_signature = xml.at_xpath("post/*[1]/parent_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)) expect(legacy_verify_signature(test_pkey, author_signature, signed_string)).to be_truthy
.to be_truthy expect(legacy_verify_signature(test_pkey, parent_author_signature, signed_string)).to be_truthy
expect(DiasporaFederation::Signing.verify_signature(hash, parent_author_signature, test_pkey))
.to be_truthy
end end
end end
end end