diff --git a/lib/diaspora_federation/salmon/encrypted_slap.rb b/lib/diaspora_federation/salmon/encrypted_slap.rb
index 3a22aae..278cfeb 100644
--- a/lib/diaspora_federation/salmon/encrypted_slap.rb
+++ b/lib/diaspora_federation/salmon/encrypted_slap.rb
@@ -113,9 +113,9 @@ module DiasporaFederation
EncryptedSlap.new.tap do |slap|
slap.author_id = author_id
- magic_envelope = MagicEnvelope.new(privkey, entity)
+ magic_envelope = MagicEnvelope.new(entity)
slap.cipher_params = magic_envelope.encrypt!
- slap.magic_envelope_xml = magic_envelope.envelop
+ slap.magic_envelope_xml = magic_envelope.envelop(privkey, author_id)
end
end
diff --git a/lib/diaspora_federation/salmon/magic_envelope.rb b/lib/diaspora_federation/salmon/magic_envelope.rb
index bfe84ff..7fa3d69 100644
--- a/lib/diaspora_federation/salmon/magic_envelope.rb
+++ b/lib/diaspora_federation/salmon/magic_envelope.rb
@@ -13,7 +13,7 @@ module DiasporaFederation
# {data}
# base64url
# RSA-SHA256
- # {signature}
+ # {signature}
#
#
# When parsing the XML of an incoming Magic Envelope {MagicEnvelope.unenvelop}
@@ -21,9 +21,6 @@ module DiasporaFederation
#
# @see http://salmon-protocol.googlecode.com/svn/trunk/draft-panzer-magicsig-01.html
class MagicEnvelope
- # returns the payload (only used for testing purposes)
- attr_reader :payload
-
# encoding used for the payload data
ENCODING = "base64url".freeze
@@ -41,34 +38,31 @@ module DiasporaFederation
# Creates a new instance of MagicEnvelope.
#
- # @param [OpenSSL::PKey::RSA] rsa_privkey private key used for signing
# @param [Entity] payload Entity instance
# @raise [ArgumentError] if either argument is not of the right type
- def initialize(rsa_privkey, payload)
- raise ArgumentError unless rsa_privkey.instance_of?(OpenSSL::PKey::RSA) &&
- payload.is_a?(Entity)
+ def initialize(payload)
+ raise ArgumentError unless payload.is_a?(Entity)
- @rsa_privkey = rsa_privkey
@payload = XmlPayload.pack(payload).to_xml.strip
end
# Builds the XML structure for the magic envelope, inserts the {ENCODING}
# encoded data and signs the envelope using {DIGEST}.
#
+ # @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
- def envelop
- env_doc = Nokogiri::XML::DocumentFragment.new(Nokogiri::XML::Document.new)
- Nokogiri::XML::Element.new("me:env", env_doc).tap do |env|
- env << Nokogiri::XML::Element.new("me:data", env_doc).tap {|node|
- node.content = Base64.urlsafe_encode64(@payload)
- node["type"] = DATA_TYPE
+ def envelop(privkey, sender_id)
+ raise ArgumentError unless privkey.instance_of?(OpenSSL::PKey::RSA) && sender_id.is_a?(String)
+
+ build_xml {|xml|
+ xml["me"].env("xmlns:me" => XMLNS) {
+ xml["me"].data(Base64.urlsafe_encode64(@payload), type: DATA_TYPE)
+ xml["me"].encoding(ENCODING)
+ xml["me"].alg(ALGORITHM)
+ xml["me"].sig(Base64.urlsafe_encode64(sign(privkey)), key_id: Base64.urlsafe_encode64(sender_id))
}
- env << Nokogiri::XML::Element.new("me:encoding", env_doc).tap {|node| node.content = ENCODING }
- env << Nokogiri::XML::Element.new("me:alg", env_doc).tap {|node| node.content = ALGORITHM }
- env << Nokogiri::XML::Element.new("me:sig", env_doc).tap {|node|
- node.content = Base64.urlsafe_encode64(signature)
- }
- end
+ }
end
# Encrypts the payload with a new, random AES cipher and returns the cipher
@@ -124,12 +118,24 @@ module DiasporaFederation
private
+ # Builds the xml root node of the magic envelope.
+ #
+ # @yield [xml] Invokes the block with the
+ # {http://www.rubydoc.info/gems/nokogiri/Nokogiri/XML/Builder Nokogiri::XML::Builder}
+ # @return [Nokogiri::XML::Element] XML root node
+ def build_xml
+ Nokogiri::XML::Builder.new(encoding: "UTF-8") {|xml|
+ yield xml
+ }.doc.root
+ end
+
# create the signature for all fields according to specification
#
+ # @param [OpenSSL::PKey::RSA] privkey private key used for signing
# @return [String] the signature
- def signature
+ def sign(privkey)
subject = MagicEnvelope.send(:sig_subject, [@payload, DATA_TYPE, ENCODING, ALGORITHM])
- @rsa_privkey.sign(DIGEST, subject)
+ privkey.sign(DIGEST, subject)
end
# @param [Nokogiri::XML::Element] env magic envelope XML
@@ -137,8 +143,6 @@ module DiasporaFederation
(env.instance_of?(Nokogiri::XML::Element) &&
env.name == "env" &&
!env.at_xpath("me:data").content.empty? &&
- !env.at_xpath("me:encoding").content.empty? &&
- !env.at_xpath("me:alg").content.empty? &&
!env.at_xpath("me:sig").content.empty?)
end
private_class_method :envelope_valid?
diff --git a/lib/diaspora_federation/salmon/slap.rb b/lib/diaspora_federation/salmon/slap.rb
index aa3ead4..9cb8c2e 100644
--- a/lib/diaspora_federation/salmon/slap.rb
+++ b/lib/diaspora_federation/salmon/slap.rb
@@ -90,7 +90,7 @@ module DiasporaFederation
xml.author_id(author_id)
}
- xml.parent << MagicEnvelope.new(privkey, entity).envelop
+ xml.parent << MagicEnvelope.new(entity).envelop(privkey, author_id)
end
end
diff --git a/spec/lib/diaspora_federation/salmon/magic_envelope_spec.rb b/spec/lib/diaspora_federation/salmon/magic_envelope_spec.rb
index b23a3f9..e1d2850 100644
--- a/spec/lib/diaspora_federation/salmon/magic_envelope_spec.rb
+++ b/spec/lib/diaspora_federation/salmon/magic_envelope_spec.rb
@@ -1,16 +1,9 @@
module DiasporaFederation
describe Salmon::MagicEnvelope do
- let(:payload) { Entities::TestEntity.new(test: "asdf") }
+ let(:sender_id) { FactoryGirl.generate(:diaspora_id) }
let(:privkey) { OpenSSL::PKey::RSA.generate(512) } # use small key for speedy specs
- let(:envelope) { envelop_xml(Salmon::MagicEnvelope.new(privkey, payload)) }
-
- def envelop_xml(magic_env)
- Nokogiri::XML::Builder.new(encoding: "UTF-8") {|xml|
- xml.root("xmlns:me" => Salmon::MagicEnvelope::XMLNS) {
- xml.parent << magic_env.envelop
- }
- }.doc.at_xpath("//me:env")
- end
+ let(:payload) { Entities::TestEntity.new(test: "asdf") }
+ let(:envelope) { Salmon::MagicEnvelope.new(payload) }
def sig_subj(env)
data = Base64.urlsafe_decode64(env.at_xpath("me:data").content)
@@ -21,40 +14,43 @@ module DiasporaFederation
[data, type, enc, alg].map {|i| Base64.urlsafe_encode64(i) }.join(".")
end
- def re_sign(env, key)
- new_sig = Base64.urlsafe_encode64(key.sign(OpenSSL::Digest::SHA256.new, sig_subj(env)))
- env.at_xpath("me:sig").content = new_sig
- end
-
context "sanity" do
it "constructs an instance" do
expect {
- Salmon::MagicEnvelope.new(privkey, payload)
+ Salmon::MagicEnvelope.new(payload)
}.not_to raise_error
end
it "raises an error if the param types are wrong" do
["asdf", 1234, :test, false].each do |val|
expect {
- Salmon::MagicEnvelope.new(val, val)
+ Salmon::MagicEnvelope.new(val)
}.to raise_error ArgumentError
end
end
end
describe "#envelop" do
- subject { Salmon::MagicEnvelope.new(privkey, payload) }
+ context "sanity" do
+ it "raises an error if the param types are wrong" do
+ ["asdf", 1234, :test, false].each do |val|
+ expect {
+ envelope.envelop(val, val)
+ }.to raise_error ArgumentError
+ end
+ end
+ end
it "should be an instance of Nokogiri::XML::Element" do
- expect(envelop_xml(subject)).to be_an_instance_of Nokogiri::XML::Element
+ expect(envelope.envelop(privkey, sender_id)).to be_an_instance_of Nokogiri::XML::Element
end
it "returns a magic envelope of correct structure" do
- env = envelop_xml(subject)
- expect(env.name).to eq("env")
+ env_xml = envelope.envelop(privkey, sender_id)
+ expect(env_xml.name).to eq("env")
control = %w(data encoding alg sig)
- env.children.each do |node|
+ env_xml.children.each do |node|
expect(control).to include(node.name)
control.reject! {|i| i == node.name }
end
@@ -62,28 +58,38 @@ module DiasporaFederation
expect(control).to be_empty
end
- it "signs the payload correctly" do
- env = envelop_xml(subject)
+ it "adds the sender_id to the signature" do
+ key_id = envelope.envelop(privkey, sender_id).at_xpath("me:sig")["key_id"]
- subj = sig_subj(env)
- sig = Base64.urlsafe_decode64(env.at_xpath("me:sig").content)
+ expect(Base64.urlsafe_decode64(key_id)).to eq(sender_id)
+ end
+
+ it "adds the data_type" do
+ data_type = envelope.envelop(privkey, sender_id).at_xpath("me:data")["type"]
+
+ expect(data_type).to eq("application/xml")
+ end
+
+ it "signs the payload correctly" do
+ env_xml = envelope.envelop(privkey, sender_id)
+
+ subj = sig_subj(env_xml)
+ sig = Base64.urlsafe_decode64(env_xml.at_xpath("me:sig").content)
expect(privkey.public_key.verify(OpenSSL::Digest::SHA256.new, sig, subj)).to be_truthy
end
end
describe "#encrypt!" do
- subject { Salmon::MagicEnvelope.new(privkey, payload) }
-
it "encrypts the payload, returning cipher params" do
- params = subject.encrypt!
+ params = envelope.encrypt!
expect(params).to include(:key, :iv)
end
it "actually encrypts the payload" do
- plain_payload = subject.payload
- params = subject.encrypt!
- encrypted_payload = subject.payload
+ plain_payload = envelope.instance_variable_get(:@payload)
+ params = envelope.encrypt!
+ encrypted_payload = envelope.instance_variable_get(:@payload)
cipher = OpenSSL::Cipher.new(Salmon::AES::CIPHER)
cipher.encrypt
@@ -98,9 +104,14 @@ module DiasporaFederation
describe ".unenvelop" do
context "sanity" do
+ def re_sign(env, key)
+ new_sig = Base64.urlsafe_encode64(key.sign(OpenSSL::Digest::SHA256.new, sig_subj(env)))
+ env.at_xpath("me:sig").content = new_sig
+ end
+
it "works with sane input" do
expect {
- Salmon::MagicEnvelope.unenvelop(envelope, privkey.public_key)
+ Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey, sender_id), privkey.public_key)
}.not_to raise_error
end
@@ -121,14 +132,13 @@ module DiasporaFederation
it "verifies the signature" do
other_key = OpenSSL::PKey::RSA.generate(512)
expect {
- Salmon::MagicEnvelope.unenvelop(envelope, other_key.public_key)
+ Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey, sender_id), other_key.public_key)
}.to raise_error Salmon::InvalidSignature
end
it "verifies the encoding" do
- bad_env = envelop_xml(Salmon::MagicEnvelope.new(privkey, payload))
- elem = bad_env.at_xpath("me:encoding")
- elem.content = "invalid_enc"
+ bad_env = envelope.envelop(privkey, sender_id)
+ bad_env.at_xpath("me:encoding").content = "invalid_enc"
re_sign(bad_env, privkey)
expect {
Salmon::MagicEnvelope.unenvelop(bad_env, privkey.public_key)
@@ -136,9 +146,8 @@ module DiasporaFederation
end
it "verifies the algorithm" do
- bad_env = envelop_xml(Salmon::MagicEnvelope.new(privkey, payload))
- elem = bad_env.at_xpath("me:alg")
- elem.content = "invalid_alg"
+ bad_env = envelope.envelop(privkey, sender_id)
+ bad_env.at_xpath("me:alg").content = "invalid_alg"
re_sign(bad_env, privkey)
expect {
Salmon::MagicEnvelope.unenvelop(bad_env, privkey.public_key)
@@ -147,18 +156,17 @@ module DiasporaFederation
end
it "returns the original entity" do
- entity = Salmon::MagicEnvelope.unenvelop(envelope, privkey.public_key)
+ entity = Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey, sender_id), privkey.public_key)
expect(entity).to be_an_instance_of Entities::TestEntity
expect(entity.test).to eq("asdf")
end
it "decrypts on the fly, when cipher params are present" do
- env = Salmon::MagicEnvelope.new(privkey, payload)
- params = env.encrypt!
+ params = envelope.encrypt!
- envelope = envelop_xml(env)
+ env_xml = envelope.envelop(privkey, sender_id)
- entity = Salmon::MagicEnvelope.unenvelop(envelope, 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.test).to eq("asdf")
end