Introduce Signable module
Signable is a module that encapsulate basic signature logic for entities.
This commit is contained in:
parent
423465c32f
commit
f23f4c0ea4
6 changed files with 140 additions and 46 deletions
|
|
@ -12,6 +12,7 @@ require "diaspora_federation/entities/related_entity"
|
|||
|
||||
# abstract types
|
||||
require "diaspora_federation/entities/post"
|
||||
require "diaspora_federation/entities/signable"
|
||||
require "diaspora_federation/entities/relayable"
|
||||
|
||||
# types
|
||||
|
|
|
|||
|
|
@ -5,10 +5,7 @@ module DiasporaFederation
|
|||
# has a parent, identified by guid. Relayables are also signed and signing/verification
|
||||
# logic is embedded into Salmon XML processing code.
|
||||
module Relayable
|
||||
include Logging
|
||||
|
||||
# Digest instance used for signing
|
||||
DIGEST = OpenSSL::Digest::SHA256.new
|
||||
include Signable
|
||||
|
||||
# Order from the parsed xml for signature
|
||||
# @return [Array] order from xml
|
||||
|
|
@ -107,24 +104,6 @@ module DiasporaFederation
|
|||
|
||||
private
|
||||
|
||||
# Check that signature is a correct signature
|
||||
#
|
||||
# @param [String] author The author of the signature
|
||||
# @param [String] signature_key The signature to be verified
|
||||
# @return [Boolean] signature valid
|
||||
def verify_signature(author, signature_key)
|
||||
pubkey = DiasporaFederation.callbacks.trigger(:fetch_public_key, author)
|
||||
raise PublicKeyNotFound, "signature=#{signature_key} person=#{author} obj=#{self}" if pubkey.nil?
|
||||
|
||||
signature = public_send(signature_key)
|
||||
raise SignatureVerificationFailed, "no #{signature_key} for #{self}" if signature.nil?
|
||||
|
||||
valid = pubkey.verify(DIGEST, Base64.decode64(signature), signature_data)
|
||||
raise SignatureVerificationFailed, "wrong #{signature_key} for #{self}" unless valid
|
||||
|
||||
logger.info "event=verify_signature signature=#{signature_key} status=valid obj=#{self}"
|
||||
end
|
||||
|
||||
# Sign with author key
|
||||
# @raise [AuthorPrivateKeyNotFound] if the author private key is not found
|
||||
# @return [String] A Base64 encoded signature of #signature_data with key
|
||||
|
|
@ -147,14 +126,6 @@ module DiasporaFederation
|
|||
end
|
||||
end
|
||||
|
||||
# Sign the data with the key
|
||||
#
|
||||
# @param [OpenSSL::PKey::RSA] privkey An RSA key
|
||||
# @return [String] A Base64 encoded signature of #signature_data with key
|
||||
def sign_with_key(privkey)
|
||||
Base64.strict_encode64(privkey.sign(DIGEST, signature_data))
|
||||
end
|
||||
|
||||
# Update the signatures with the keys of the author and the parent
|
||||
# if the signatures are not there yet and if the keys are available.
|
||||
#
|
||||
|
|
@ -245,14 +216,6 @@ module DiasporaFederation
|
|||
# Raised, if creating the author_signature fails, because the private key was not found
|
||||
class AuthorPrivateKeyNotFound < RuntimeError
|
||||
end
|
||||
|
||||
# Raised, if verify_signatures fails to verify signatures (no public key found)
|
||||
class PublicKeyNotFound < RuntimeError
|
||||
end
|
||||
|
||||
# Raised, if verify_signatures fails to verify signatures (signatures are wrong)
|
||||
class SignatureVerificationFailed < RuntimeError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
53
lib/diaspora_federation/entities/signable.rb
Normal file
53
lib/diaspora_federation/entities/signable.rb
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
module DiasporaFederation
|
||||
module Entities
|
||||
# Signable is a module that encapsulates basic signature generation/verification flow for entities.
|
||||
module Signable
|
||||
include Logging
|
||||
|
||||
# Digest instance used for signing
|
||||
DIGEST = OpenSSL::Digest::SHA256.new
|
||||
|
||||
# Sign the data with the key
|
||||
#
|
||||
# @param [OpenSSL::PKey::RSA] privkey An RSA key
|
||||
# @return [String] A Base64 encoded signature of #signature_data with key
|
||||
def sign_with_key(privkey)
|
||||
Base64.strict_encode64(privkey.sign(DIGEST, signature_data))
|
||||
end
|
||||
|
||||
# Check that signature is a correct signature
|
||||
#
|
||||
# @param [String] author The author of the signature
|
||||
# @param [String] signature_key The signature to be verified
|
||||
# @return [Boolean] signature valid
|
||||
def verify_signature(author, signature_key)
|
||||
pubkey = DiasporaFederation.callbacks.trigger(:fetch_public_key, author)
|
||||
raise PublicKeyNotFound, "signature=#{signature_key} person=#{author} obj=#{self}" if pubkey.nil?
|
||||
|
||||
signature = public_send(signature_key)
|
||||
raise SignatureVerificationFailed, "no #{signature_key} for #{self}" if signature.nil?
|
||||
|
||||
valid = pubkey.verify(DIGEST, Base64.decode64(signature), signature_data)
|
||||
raise SignatureVerificationFailed, "wrong #{signature_key} for #{self}" unless valid
|
||||
|
||||
logger.info "event=verify_signature signature=#{signature_key} status=valid obj=#{self}"
|
||||
end
|
||||
|
||||
# This method defines what data is used for a signature creation/verification
|
||||
#
|
||||
# @abstract
|
||||
# @return [String] a string to sign
|
||||
def signature_data
|
||||
raise NotImplementedError.new("you must override this method to define signature base string")
|
||||
end
|
||||
|
||||
# Raised, if verify_signatures fails to verify signatures (no public key found)
|
||||
class PublicKeyNotFound < RuntimeError
|
||||
end
|
||||
|
||||
# Raised, if verify_signatures fails to verify signatures (signatures are wrong)
|
||||
class SignatureVerificationFailed < RuntimeError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -15,14 +15,6 @@ module DiasporaFederation
|
|||
|
||||
let(:legacy_signature_data) { "#{guid};#{author};#{property};#{parent_guid}" }
|
||||
|
||||
def sign_with_key(privkey, signature_data)
|
||||
Base64.strict_encode64(privkey.sign(OpenSSL::Digest::SHA256.new, signature_data))
|
||||
end
|
||||
|
||||
def verify_signature(pubkey, signature, signed_string)
|
||||
pubkey.verify(OpenSSL::Digest::SHA256.new, Base64.decode64(signature), signed_string)
|
||||
end
|
||||
|
||||
describe "#initialize" do
|
||||
it "filters signatures from order" do
|
||||
xml_order = [:author, :guid, :parent_guid, :property, "new_property", :author_signature]
|
||||
|
|
|
|||
77
spec/lib/diaspora_federation/entities/signable_spec.rb
Normal file
77
spec/lib/diaspora_federation/entities/signable_spec.rb
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
module DiasporaFederation
|
||||
describe Entities::Signable do
|
||||
TEST_STRING_VALUE = "abc123".freeze
|
||||
let(:private_key) { OpenSSL::PKey::RSA.generate(1024) }
|
||||
let(:test_string) { TEST_STRING_VALUE }
|
||||
let(:test_signature) { sign_with_key(private_key, test_string) }
|
||||
|
||||
class TestSignableEntity < Entity
|
||||
include Entities::Signable
|
||||
|
||||
property :my_signature, :string, default: nil
|
||||
|
||||
def signature_data
|
||||
TEST_STRING_VALUE
|
||||
end
|
||||
end
|
||||
|
||||
describe "#signature_data" do
|
||||
it "raises NotImplementedError when not overridden" do
|
||||
class TestEntity < Entity
|
||||
include Entities::Signable
|
||||
end
|
||||
|
||||
expect {
|
||||
TestEntity.new({}).signature_data
|
||||
}.to raise_error(NotImplementedError)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#sign_with_key" do
|
||||
it "produces a correct signature" do
|
||||
signature = TestSignableEntity.new({}).sign_with_key(private_key)
|
||||
expect(verify_signature(private_key.public_key, signature, test_string)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe "#verify_signature" do
|
||||
it "doesn't raise if signature is correct" do
|
||||
expect_callback(:fetch_public_key, "id@example.tld").and_return(private_key.public_key)
|
||||
|
||||
expect {
|
||||
TestSignableEntity
|
||||
.new(my_signature: test_signature)
|
||||
.verify_signature("id@example.tld", :my_signature)
|
||||
}.not_to raise_error
|
||||
end
|
||||
|
||||
it "raises PublicKeyNotFound when key isn't provided" do
|
||||
expect_callback(:fetch_public_key, "id@example.tld").and_return(nil)
|
||||
|
||||
expect {
|
||||
TestSignableEntity
|
||||
.new(my_signature: test_signature)
|
||||
.verify_signature("id@example.tld", :my_signature)
|
||||
}.to raise_error(Entities::Signable::PublicKeyNotFound)
|
||||
end
|
||||
|
||||
it "raises SignatureVerificationFailed when signature isn't provided" do
|
||||
expect_callback(:fetch_public_key, "id@example.tld").and_return(private_key.public_key)
|
||||
|
||||
expect {
|
||||
TestSignableEntity.new({}).verify_signature("id@example.tld", :my_signature)
|
||||
}.to raise_error(Entities::Signable::SignatureVerificationFailed)
|
||||
end
|
||||
|
||||
it "raises SignatureVerificationFailed when signature is wrong" do
|
||||
expect_callback(:fetch_public_key, "id@example.tld").and_return(private_key.public_key)
|
||||
|
||||
expect {
|
||||
TestSignableEntity
|
||||
.new(my_signature: "faked signature")
|
||||
.verify_signature("id@example.tld", :my_signature)
|
||||
}.to raise_error(Entities::Signable::SignatureVerificationFailed)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -45,6 +45,14 @@ def add_signatures(hash, klass=described_class)
|
|||
hash[:parent_author_signature] = properties[:parent_author_signature]
|
||||
end
|
||||
|
||||
def sign_with_key(privkey, signature_data)
|
||||
Base64.strict_encode64(privkey.sign(OpenSSL::Digest::SHA256.new, signature_data))
|
||||
end
|
||||
|
||||
def verify_signature(pubkey, signature, signed_string)
|
||||
pubkey.verify(OpenSSL::Digest::SHA256.new, Base64.decode64(signature), signed_string)
|
||||
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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue