Extract account migration sign feature to a module

This module can be used to compute account migration signature in other
objects besides Entities::AccountMigration. For instance this is to be
used in AccountMigration model of diaspora* web application.
This commit is contained in:
cmrd Senya 2017-10-30 14:36:21 +02:00
parent 3cffc9d1d2
commit 69e523abd0
6 changed files with 134 additions and 59 deletions

View file

@ -13,6 +13,7 @@ require "diaspora_federation/entities/related_entity"
# abstract types
require "diaspora_federation/entities/post"
require "diaspora_federation/entities/signable"
require "diaspora_federation/entities/account_migration/signable"
require "diaspora_federation/entities/relayable"
# types

View file

@ -5,7 +5,7 @@ module DiasporaFederation
#
# @see Validators::AccountMigrationValidator
class AccountMigration < Entity
include Signable
include AccountMigration::Signable
# @!attribute [r] author
# The old diaspora* ID of the person who changes their ID
@ -23,16 +23,18 @@ module DiasporaFederation
# @return [String] signature
property :signature, :string, default: nil
# @return [String] string representation of this object
def to_s
"AccountMigration:#{author}:#{profile.author}"
def old_user_id
author
end
# @return [String] string representation of this object
alias to_s unique_migration_descriptor
# Shortcut for calling super method with sensible arguments
#
# @see DiasporaFederation::Entities::Signable#verify_signature
def verify_signature
super(profile.author, :signature)
super(signer_id, :signature)
end
# Calls super and additionally does signature verification for the instantiated entity.
@ -44,9 +46,12 @@ module DiasporaFederation
private
# @see DiasporaFederation::Entities::Signable#signature_data
def signature_data
to_s
def new_user_id
profile.author
end
def signer_id
new_user_id
end
def enriched_properties
@ -59,10 +64,10 @@ module DiasporaFederation
# @raise [NewPrivateKeyNotFound] if the new user's private key is not found
# @return [String] A Base64 encoded signature of #signature_data with key
def sign_with_new_key
privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key, profile.author)
raise NewPrivateKeyNotFound, "author=#{profile.author} obj=#{self}" if privkey.nil?
privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key, signer_id)
raise NewPrivateKeyNotFound, "signer=#{signer_id} obj=#{self}" if privkey.nil?
sign_with_key(privkey).tap do
logger.info "event=sign status=complete signature=signature author=#{profile.author} obj=#{self}"
logger.info "event=sign status=complete signature=signature signer=#{signer_id} obj=#{self}"
end
end

View file

@ -0,0 +1,24 @@
module DiasporaFederation
module Entities
class AccountMigration < Entity
# AccountMigration::Signable is a module that encapsulates basic signature generation/verification flow for
# AccountMigration entity.
#
# It is possible that implementation of diaspora* protocol requires to compute the signature for the
# AccountMigration entity without instantiating the entity. In this case this module may be useful.
module Signable
include Entities::Signable
# @return [String] string which is uniquely represents migration occasion
def unique_migration_descriptor
"AccountMigration:#{old_identity}:#{new_identity}"
end
# @see DiasporaFederation::Entities::Signable#signature_data
def signature_data
unique_migration_descriptor
end
end
end
end
end

View file

@ -0,0 +1,39 @@
module DiasporaFederation
describe Entities::AccountMigration::Signable do
let(:entity) { TestAMSignableEntity.new({}) }
class TestAMSignableEntity < Entity
include Entities::AccountMigration::Signable
property :my_signature, :string, default: nil
def old_identity
"old"
end
def new_identity
"new"
end
def freeze; end
end
it_behaves_like "a signable" do
let(:test_class) { TestAMSignableEntity }
let(:test_string) { "AccountMigration:old:new" }
end
describe "#unique_migration_descriptor" do
it "composes a string using #old_identity and #new_identity" do
expect(entity.unique_migration_descriptor).to eq("AccountMigration:old:new")
end
end
describe "#signature_data" do
it "delegates to #unique_migration_descriptor" do
expect(entity).to receive(:unique_migration_descriptor).and_return("test123")
expect(entity.signature_data).to eq("test123")
end
end
end
end

View file

@ -1,9 +1,6 @@
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
@ -27,51 +24,9 @@ module DiasporaFederation
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
it_behaves_like "a signable" do
let(:test_class) { TestSignableEntity }
let(:test_string) { TEST_STRING_VALUE }
end
end
end

View file

@ -0,0 +1,51 @@
shared_examples "a signable" do
let(:private_key) { OpenSSL::PKey::RSA.generate(1024) }
let(:test_signature) { sign_with_key(private_key, test_string) }
describe "#sign_with_key" do
it "produces a correct signature" do
signature = test_class.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 {
test_class
.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 {
test_class
.new(my_signature: test_signature)
.verify_signature("id@example.tld", :my_signature)
}.to raise_error(DiasporaFederation::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 {
test_class.new({}).verify_signature("id@example.tld", :my_signature)
}.to raise_error(DiasporaFederation::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 {
test_class
.new(my_signature: "faked signature")
.verify_signature("id@example.tld", :my_signature)
}.to raise_error(DiasporaFederation::Entities::Signable::SignatureVerificationFailed)
end
end
end