create attr_reader for payload entity and sender on magic env instance

This commit is contained in:
Benjamin Neff 2016-03-20 15:51:36 +01:00
parent 930c3051c7
commit 4e0c7e205b
10 changed files with 61 additions and 46 deletions

View file

@ -30,7 +30,7 @@ module DiasporaFederation
def create_magic_envelope(entity) def create_magic_envelope(entity)
privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key_by_diaspora_id, entity.author) privkey = DiasporaFederation.callbacks.trigger(:fetch_private_key_by_diaspora_id, entity.author)
Salmon::MagicEnvelope.new(entity).envelop(privkey, entity.author) if privkey Salmon::MagicEnvelope.new(entity, entity.author).envelop(privkey) if privkey
end end
end end
end end

View file

@ -114,9 +114,9 @@ module DiasporaFederation
EncryptedSlap.new.tap do |slap| EncryptedSlap.new.tap do |slap|
slap.author_id = author_id slap.author_id = author_id
magic_envelope = MagicEnvelope.new(entity) magic_envelope = MagicEnvelope.new(entity, author_id)
slap.cipher_params = magic_envelope.encrypt! slap.cipher_params = magic_envelope.encrypt!
slap.magic_envelope_xml = magic_envelope.envelop(privkey, author_id) slap.magic_envelope_xml = magic_envelope.envelop(privkey)
end end
end end

View file

@ -36,31 +36,40 @@ module DiasporaFederation
# XML namespace url # XML namespace url
XMLNS = "http://salmon-protocol.org/ns/magic-env".freeze XMLNS = "http://salmon-protocol.org/ns/magic-env".freeze
# the payload entity of the magic envelope
# @return [Entity] payload entity
attr_reader :payload
# the sender of the magic envelope
# @return [String] diaspora-ID of the sender
attr_reader :sender
# Creates a new instance of MagicEnvelope. # Creates a new instance of MagicEnvelope.
# #
# @param [Entity] payload Entity instance # @param [Entity] payload Entity instance
# @param [String] sender diaspora-ID of the sender
# @raise [ArgumentError] if either argument is not of the right type # @raise [ArgumentError] if either argument is not of the right type
def initialize(payload) def initialize(payload, sender)
raise ArgumentError unless payload.is_a?(Entity) raise ArgumentError unless payload.is_a?(Entity) && sender.is_a?(String)
@payload = XmlPayload.pack(payload).to_xml.strip @payload = payload
@sender = sender
end end
# Builds the XML structure for the magic envelope, inserts the {ENCODING} # Builds the XML structure for the magic envelope, inserts the {ENCODING}
# encoded data and signs the envelope using {DIGEST}. # encoded data and signs the envelope using {DIGEST}.
# #
# @param [OpenSSL::PKey::RSA] privkey private key used for signing # @param [OpenSSL::PKey::RSA] privkey private key used for signing
# @param [String] sender_id diaspora-ID of the sender
# @return [Nokogiri::XML::Element] XML root node # @return [Nokogiri::XML::Element] XML root node
def envelop(privkey, sender_id) def envelop(privkey)
raise ArgumentError unless privkey.instance_of?(OpenSSL::PKey::RSA) && sender_id.is_a?(String) raise ArgumentError unless privkey.instance_of?(OpenSSL::PKey::RSA)
build_xml {|xml| build_xml {|xml|
xml["me"].env("xmlns:me" => XMLNS) { xml["me"].env("xmlns:me" => XMLNS) {
xml["me"].data(Base64.urlsafe_encode64(@payload), type: DATA_TYPE) xml["me"].data(Base64.urlsafe_encode64(payload_data), type: DATA_TYPE)
xml["me"].encoding(ENCODING) xml["me"].encoding(ENCODING)
xml["me"].alg(ALGORITHM) xml["me"].alg(ALGORITHM)
xml["me"].sig(Base64.urlsafe_encode64(sign(privkey)), key_id: Base64.urlsafe_encode64(sender_id)) xml["me"].sig(Base64.urlsafe_encode64(sign(privkey)), key_id: Base64.urlsafe_encode64(sender))
} }
} }
end end
@ -77,7 +86,7 @@ module DiasporaFederation
# @return [Hash] AES key and iv. E.g.: { key: "...", iv: "..." } # @return [Hash] AES key and iv. E.g.: { key: "...", iv: "..." }
def encrypt! def encrypt!
AES.generate_key_and_iv.tap do |key| AES.generate_key_and_iv.tap do |key|
@payload = AES.encrypt(@payload, key[:key], key[:iv]) @payload_data = AES.encrypt(payload_data, key[:key], key[:iv])
end end
end end
@ -117,6 +126,12 @@ module DiasporaFederation
private private
# the payload data as string
# @return [String] payload data
def payload_data
@payload_data ||= XmlPayload.pack(@payload).to_xml.strip
end
# Builds the xml root node of the magic envelope. # Builds the xml root node of the magic envelope.
# #
# @yield [xml] Invokes the block with the # @yield [xml] Invokes the block with the
@ -133,7 +148,7 @@ module DiasporaFederation
# @param [OpenSSL::PKey::RSA] privkey private key used for signing # @param [OpenSSL::PKey::RSA] privkey private key used for signing
# @return [String] the signature # @return [String] the signature
def sign(privkey) def sign(privkey)
subject = MagicEnvelope.send(:sig_subject, [@payload, DATA_TYPE, ENCODING, ALGORITHM]) subject = MagicEnvelope.send(:sig_subject, [payload_data, DATA_TYPE, ENCODING, ALGORITHM])
privkey.sign(DIGEST, subject) privkey.sign(DIGEST, subject)
end end

View file

@ -92,7 +92,7 @@ module DiasporaFederation
xml.author_id(author_id) xml.author_id(author_id)
} }
xml.parent << MagicEnvelope.new(entity).envelop(privkey, author_id) xml.parent << MagicEnvelope.new(entity, author_id).envelop(privkey)
end end
end end

View file

@ -1,7 +1,7 @@
module DiasporaFederation module DiasporaFederation
describe Federation::Fetcher do describe Federation::Fetcher do
let(:post) { FactoryGirl.build(:status_message_entity, public: true) } let(:post) { FactoryGirl.build(:status_message_entity, public: true) }
let(:post_magic_env) { Salmon::MagicEnvelope.new(post).envelop(alice.private_key, post.author).to_xml } let(:post_magic_env) { Salmon::MagicEnvelope.new(post, post.author).envelop(alice.private_key).to_xml }
describe ".fetch_public" do describe ".fetch_public" do
it "fetches a public post" do it "fetches a public post" do

View file

@ -4,7 +4,7 @@ module DiasporaFederation
let(:sender_key) { OpenSSL::PKey::RSA.generate(1024) } let(:sender_key) { OpenSSL::PKey::RSA.generate(1024) }
let(:recipient_key) { OpenSSL::PKey::RSA.generate(1024) } let(:recipient_key) { OpenSSL::PKey::RSA.generate(1024) }
let(:entity) { FactoryGirl.build(:status_message_entity, public: false) } let(:entity) { FactoryGirl.build(:status_message_entity, public: false) }
let(:magic_env) { Salmon::MagicEnvelope.new(entity).envelop(sender_key, sender_id) } let(:magic_env) { Salmon::MagicEnvelope.new(entity, sender_id).envelop(sender_key) }
let(:data) { Salmon::EncryptedMagicEnvelope.encrypt(magic_env, recipient_key.public_key) } let(:data) { Salmon::EncryptedMagicEnvelope.encrypt(magic_env, recipient_key.public_key) }
it "parses the entity if everything is fine" do it "parses the entity if everything is fine" do

View file

@ -3,7 +3,7 @@ module DiasporaFederation
let(:sender_id) { FactoryGirl.generate(:diaspora_id) } let(:sender_id) { FactoryGirl.generate(:diaspora_id) }
let(:sender_key) { OpenSSL::PKey::RSA.generate(1024) } let(:sender_key) { OpenSSL::PKey::RSA.generate(1024) }
let(:entity) { FactoryGirl.build(:status_message_entity) } let(:entity) { FactoryGirl.build(:status_message_entity) }
let(:data) { Salmon::MagicEnvelope.new(entity).envelop(sender_key, sender_id).to_xml } let(:data) { Salmon::MagicEnvelope.new(entity, sender_id).envelop(sender_key).to_xml }
it "parses the entity if everything is fine" do it "parses the entity if everything is fine" do
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(

View file

@ -12,7 +12,7 @@ module DiasporaFederation
:fetch_public_key_by_diaspora_id, sender_id :fetch_public_key_by_diaspora_id, sender_id
).and_return(sender_key) ).and_return(sender_key)
data = Salmon::MagicEnvelope.new(post).envelop(sender_key, sender_id).to_xml data = Salmon::MagicEnvelope.new(post, sender_id).envelop(sender_key).to_xml
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(
:receive_entity, kind_of(Entities::StatusMessage), nil :receive_entity, kind_of(Entities::StatusMessage), nil
@ -54,7 +54,7 @@ module DiasporaFederation
:fetch_public_key_by_diaspora_id, sender_id :fetch_public_key_by_diaspora_id, sender_id
).and_return(sender_key) ).and_return(sender_key)
magic_env = Salmon::MagicEnvelope.new(post).envelop(sender_key, sender_id) magic_env = Salmon::MagicEnvelope.new(post, sender_id).envelop(sender_key)
data = Salmon::EncryptedMagicEnvelope.encrypt(magic_env, recipient_key.public_key) data = Salmon::EncryptedMagicEnvelope.encrypt(magic_env, recipient_key.public_key)
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(
@ -90,7 +90,7 @@ module DiasporaFederation
end end
it "raises when recipient private key is not available" do it "raises when recipient private key is not available" do
magic_env = Salmon::MagicEnvelope.new(post).envelop(sender_key, sender_id) magic_env = Salmon::MagicEnvelope.new(post, sender_id).envelop(sender_key)
data = Salmon::EncryptedMagicEnvelope.encrypt(magic_env, recipient_key.public_key) data = Salmon::EncryptedMagicEnvelope.encrypt(magic_env, recipient_key.public_key)
expect { expect {

View file

@ -3,7 +3,7 @@ module DiasporaFederation
let(:sender_id) { FactoryGirl.generate(:diaspora_id) } let(:sender_id) { FactoryGirl.generate(:diaspora_id) }
let(:sender_key) { OpenSSL::PKey::RSA.generate(512) } # use small key for speedy specs let(:sender_key) { OpenSSL::PKey::RSA.generate(512) } # use small key for speedy specs
let(:entity) { Entities::TestEntity.new(test: "abcd") } let(:entity) { Entities::TestEntity.new(test: "abcd") }
let(:magic_env) { Salmon::MagicEnvelope.new(entity).envelop(sender_key, sender_id) } let(:magic_env) { Salmon::MagicEnvelope.new(entity, sender_id).envelop(sender_key) }
let(:privkey) { OpenSSL::PKey::RSA.generate(1024) } # use small key for speedy specs let(:privkey) { OpenSSL::PKey::RSA.generate(1024) } # use small key for speedy specs

View file

@ -1,9 +1,9 @@
module DiasporaFederation module DiasporaFederation
describe Salmon::MagicEnvelope do describe Salmon::MagicEnvelope do
let(:sender_id) { FactoryGirl.generate(:diaspora_id) } let(:sender) { FactoryGirl.generate(:diaspora_id) }
let(:privkey) { OpenSSL::PKey::RSA.generate(512) } # use small key for speedy specs let(:privkey) { OpenSSL::PKey::RSA.generate(512) } # use small key for speedy specs
let(:payload) { Entities::TestEntity.new(test: "asdf") } let(:payload) { Entities::TestEntity.new(test: "asdf") }
let(:envelope) { Salmon::MagicEnvelope.new(payload) } let(:envelope) { Salmon::MagicEnvelope.new(payload, sender) }
def sig_subj(env) def sig_subj(env)
data = Base64.urlsafe_decode64(env.at_xpath("me:data").content) data = Base64.urlsafe_decode64(env.at_xpath("me:data").content)
@ -17,14 +17,14 @@ module DiasporaFederation
context "sanity" do context "sanity" do
it "constructs an instance" do it "constructs an instance" do
expect { expect {
Salmon::MagicEnvelope.new(payload) Salmon::MagicEnvelope.new(payload, sender)
}.not_to raise_error }.not_to raise_error
end end
it "raises an error if the param types are wrong" do it "raises an error if the param types are wrong" do
["asdf", 1234, :test, false].each do |val| ["asdf", 1234, :test, false].each do |val|
expect { expect {
Salmon::MagicEnvelope.new(val) Salmon::MagicEnvelope.new(val, val)
}.to raise_error ArgumentError }.to raise_error ArgumentError
end end
end end
@ -35,18 +35,18 @@ module DiasporaFederation
it "raises an error if the param types are wrong" do it "raises an error if the param types are wrong" do
["asdf", 1234, :test, false].each do |val| ["asdf", 1234, :test, false].each do |val|
expect { expect {
envelope.envelop(val, val) envelope.envelop(val)
}.to raise_error ArgumentError }.to raise_error ArgumentError
end end
end end
end end
it "should be an instance of Nokogiri::XML::Element" do it "should be an instance of Nokogiri::XML::Element" do
expect(envelope.envelop(privkey, sender_id)).to be_an_instance_of Nokogiri::XML::Element expect(envelope.envelop(privkey)).to be_an_instance_of Nokogiri::XML::Element
end end
it "returns a magic envelope of correct structure" do it "returns a magic envelope of correct structure" do
env_xml = envelope.envelop(privkey, sender_id) env_xml = envelope.envelop(privkey)
expect(env_xml.name).to eq("env") expect(env_xml.name).to eq("env")
control = %w(data encoding alg sig) control = %w(data encoding alg sig)
@ -58,20 +58,20 @@ module DiasporaFederation
expect(control).to be_empty expect(control).to be_empty
end end
it "adds the sender_id to the signature" do it "adds the sender to the signature" do
key_id = envelope.envelop(privkey, sender_id).at_xpath("me:sig")["key_id"] key_id = envelope.envelop(privkey).at_xpath("me:sig")["key_id"]
expect(Base64.urlsafe_decode64(key_id)).to eq(sender_id) expect(Base64.urlsafe_decode64(key_id)).to eq(sender)
end end
it "adds the data_type" do it "adds the data_type" do
data_type = envelope.envelop(privkey, sender_id).at_xpath("me:data")["type"] data_type = envelope.envelop(privkey).at_xpath("me:data")["type"]
expect(data_type).to eq("application/xml") expect(data_type).to eq("application/xml")
end end
it "signs the payload correctly" do it "signs the payload correctly" do
env_xml = envelope.envelop(privkey, sender_id) env_xml = envelope.envelop(privkey)
subj = sig_subj(env_xml) subj = sig_subj(env_xml)
sig = Base64.urlsafe_decode64(env_xml.at_xpath("me:sig").content) sig = Base64.urlsafe_decode64(env_xml.at_xpath("me:sig").content)
@ -87,9 +87,9 @@ module DiasporaFederation
end end
it "actually encrypts the payload" do it "actually encrypts the payload" do
plain_payload = envelope.instance_variable_get(:@payload) plain_payload = envelope.send(:payload_data)
params = envelope.encrypt! params = envelope.encrypt!
encrypted_payload = envelope.instance_variable_get(:@payload) encrypted_payload = envelope.send(:payload_data)
cipher = OpenSSL::Cipher.new(Salmon::AES::CIPHER) cipher = OpenSSL::Cipher.new(Salmon::AES::CIPHER)
cipher.encrypt cipher.encrypt
@ -111,7 +111,7 @@ module DiasporaFederation
it "works with sane input" do it "works with sane input" do
expect { expect {
Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey, sender_id), privkey.public_key) Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey), privkey.public_key)
}.not_to raise_error }.not_to raise_error
end end
@ -132,12 +132,12 @@ module DiasporaFederation
it "verifies the signature" do it "verifies the signature" do
other_key = OpenSSL::PKey::RSA.generate(512) other_key = OpenSSL::PKey::RSA.generate(512)
expect { expect {
Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey, sender_id), other_key.public_key) Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey), other_key.public_key)
}.to raise_error Salmon::InvalidSignature }.to raise_error Salmon::InvalidSignature
end end
it "verifies the encoding" do it "verifies the encoding" do
bad_env = envelope.envelop(privkey, sender_id) bad_env = envelope.envelop(privkey)
bad_env.at_xpath("me:encoding").content = "invalid_enc" bad_env.at_xpath("me:encoding").content = "invalid_enc"
re_sign(bad_env, privkey) re_sign(bad_env, privkey)
expect { expect {
@ -146,7 +146,7 @@ module DiasporaFederation
end end
it "verifies the algorithm" do it "verifies the algorithm" do
bad_env = envelope.envelop(privkey, sender_id) bad_env = envelope.envelop(privkey)
bad_env.at_xpath("me:alg").content = "invalid_alg" bad_env.at_xpath("me:alg").content = "invalid_alg"
re_sign(bad_env, privkey) re_sign(bad_env, privkey)
expect { expect {
@ -156,7 +156,7 @@ module DiasporaFederation
end end
it "returns the original entity" do it "returns the original entity" do
entity = Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey, sender_id), privkey.public_key) entity = Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey), privkey.public_key)
expect(entity).to be_an_instance_of Entities::TestEntity expect(entity).to be_an_instance_of Entities::TestEntity
expect(entity.test).to eq("asdf") expect(entity.test).to eq("asdf")
end end
@ -164,7 +164,7 @@ module DiasporaFederation
it "decrypts on the fly, when cipher params are present" do it "decrypts on the fly, when cipher params are present" do
params = envelope.encrypt! params = envelope.encrypt!
env_xml = envelope.envelop(privkey, sender_id) env_xml = envelope.envelop(privkey)
entity = Salmon::MagicEnvelope.unenvelop(env_xml, privkey.public_key, params) entity = Salmon::MagicEnvelope.unenvelop(env_xml, privkey.public_key, params)
expect(entity).to be_an_instance_of Entities::TestEntity expect(entity).to be_an_instance_of Entities::TestEntity
@ -174,16 +174,16 @@ module DiasporaFederation
context "use key_id from magic envelope" do context "use key_id from magic envelope" do
it "returns the original entity" do it "returns the original entity" do
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(
:fetch_public_key_by_diaspora_id, sender_id :fetch_public_key_by_diaspora_id, sender
).and_return(privkey.public_key) ).and_return(privkey.public_key)
entity = Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey, sender_id)) entity = Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey))
expect(entity).to be_an_instance_of Entities::TestEntity expect(entity).to be_an_instance_of Entities::TestEntity
expect(entity.test).to eq("asdf") expect(entity.test).to eq("asdf")
end end
it "raises if the magic envelope has no key_id" do it "raises if the magic envelope has no key_id" do
bad_env = envelope.envelop(privkey, sender_id) bad_env = envelope.envelop(privkey)
bad_env.at_xpath("me:sig").attributes["key_id"].remove bad_env.at_xpath("me:sig").attributes["key_id"].remove
@ -194,11 +194,11 @@ module DiasporaFederation
it "raises if the sender key is not found" do it "raises if the sender key is not found" do
expect(DiasporaFederation.callbacks).to receive(:trigger).with( expect(DiasporaFederation.callbacks).to receive(:trigger).with(
:fetch_public_key_by_diaspora_id, sender_id :fetch_public_key_by_diaspora_id, sender
).and_return(nil) ).and_return(nil)
expect { expect {
Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey, sender_id)) Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey))
}.to raise_error Salmon::SenderKeyNotFound }.to raise_error Salmon::SenderKeyNotFound
end end
end end