add key_id to magic envelope
This commit is contained in:
parent
19621fecdf
commit
160da072b6
4 changed files with 85 additions and 73 deletions
|
|
@ -113,9 +113,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(privkey, entity)
|
magic_envelope = MagicEnvelope.new(entity)
|
||||||
slap.cipher_params = magic_envelope.encrypt!
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ module DiasporaFederation
|
||||||
# <me:data type="application/xml">{data}</me:data>
|
# <me:data type="application/xml">{data}</me:data>
|
||||||
# <me:encoding>base64url</me:encoding>
|
# <me:encoding>base64url</me:encoding>
|
||||||
# <me:alg>RSA-SHA256</me:alg>
|
# <me:alg>RSA-SHA256</me:alg>
|
||||||
# <me:sig>{signature}</me:sig>
|
# <me:sig key_id="{sender}">{signature}</me:sig>
|
||||||
# </me:env>
|
# </me:env>
|
||||||
#
|
#
|
||||||
# When parsing the XML of an incoming Magic Envelope {MagicEnvelope.unenvelop}
|
# 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
|
# @see http://salmon-protocol.googlecode.com/svn/trunk/draft-panzer-magicsig-01.html
|
||||||
class MagicEnvelope
|
class MagicEnvelope
|
||||||
# returns the payload (only used for testing purposes)
|
|
||||||
attr_reader :payload
|
|
||||||
|
|
||||||
# encoding used for the payload data
|
# encoding used for the payload data
|
||||||
ENCODING = "base64url".freeze
|
ENCODING = "base64url".freeze
|
||||||
|
|
||||||
|
|
@ -41,34 +38,31 @@ module DiasporaFederation
|
||||||
|
|
||||||
# Creates a new instance of MagicEnvelope.
|
# Creates a new instance of MagicEnvelope.
|
||||||
#
|
#
|
||||||
# @param [OpenSSL::PKey::RSA] rsa_privkey private key used for signing
|
|
||||||
# @param [Entity] payload Entity instance
|
# @param [Entity] payload Entity instance
|
||||||
# @raise [ArgumentError] if either argument is not of the right type
|
# @raise [ArgumentError] if either argument is not of the right type
|
||||||
def initialize(rsa_privkey, payload)
|
def initialize(payload)
|
||||||
raise ArgumentError unless rsa_privkey.instance_of?(OpenSSL::PKey::RSA) &&
|
raise ArgumentError unless payload.is_a?(Entity)
|
||||||
payload.is_a?(Entity)
|
|
||||||
|
|
||||||
@rsa_privkey = rsa_privkey
|
|
||||||
@payload = XmlPayload.pack(payload).to_xml.strip
|
@payload = XmlPayload.pack(payload).to_xml.strip
|
||||||
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 [String] sender_id diaspora-ID of the sender
|
||||||
# @return [Nokogiri::XML::Element] XML root node
|
# @return [Nokogiri::XML::Element] XML root node
|
||||||
def envelop
|
def envelop(privkey, sender_id)
|
||||||
env_doc = Nokogiri::XML::DocumentFragment.new(Nokogiri::XML::Document.new)
|
raise ArgumentError unless privkey.instance_of?(OpenSSL::PKey::RSA) && sender_id.is_a?(String)
|
||||||
Nokogiri::XML::Element.new("me:env", env_doc).tap do |env|
|
|
||||||
env << Nokogiri::XML::Element.new("me:data", env_doc).tap {|node|
|
build_xml {|xml|
|
||||||
node.content = Base64.urlsafe_encode64(@payload)
|
xml["me"].env("xmlns:me" => XMLNS) {
|
||||||
node["type"] = DATA_TYPE
|
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
|
end
|
||||||
|
|
||||||
# Encrypts the payload with a new, random AES cipher and returns the cipher
|
# Encrypts the payload with a new, random AES cipher and returns the cipher
|
||||||
|
|
@ -124,12 +118,24 @@ module DiasporaFederation
|
||||||
|
|
||||||
private
|
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
|
# create the signature for all fields according to specification
|
||||||
#
|
#
|
||||||
|
# @param [OpenSSL::PKey::RSA] privkey private key used for signing
|
||||||
# @return [String] the signature
|
# @return [String] the signature
|
||||||
def signature
|
def sign(privkey)
|
||||||
subject = MagicEnvelope.send(:sig_subject, [@payload, DATA_TYPE, ENCODING, ALGORITHM])
|
subject = MagicEnvelope.send(:sig_subject, [@payload, DATA_TYPE, ENCODING, ALGORITHM])
|
||||||
@rsa_privkey.sign(DIGEST, subject)
|
privkey.sign(DIGEST, subject)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [Nokogiri::XML::Element] env magic envelope XML
|
# @param [Nokogiri::XML::Element] env magic envelope XML
|
||||||
|
|
@ -137,8 +143,6 @@ module DiasporaFederation
|
||||||
(env.instance_of?(Nokogiri::XML::Element) &&
|
(env.instance_of?(Nokogiri::XML::Element) &&
|
||||||
env.name == "env" &&
|
env.name == "env" &&
|
||||||
!env.at_xpath("me:data").content.empty? &&
|
!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?)
|
!env.at_xpath("me:sig").content.empty?)
|
||||||
end
|
end
|
||||||
private_class_method :envelope_valid?
|
private_class_method :envelope_valid?
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ module DiasporaFederation
|
||||||
xml.author_id(author_id)
|
xml.author_id(author_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
xml.parent << MagicEnvelope.new(privkey, entity).envelop
|
xml.parent << MagicEnvelope.new(entity).envelop(privkey, author_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,9 @@
|
||||||
module DiasporaFederation
|
module DiasporaFederation
|
||||||
describe Salmon::MagicEnvelope do
|
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(:privkey) { OpenSSL::PKey::RSA.generate(512) } # use small key for speedy specs
|
||||||
let(:envelope) { envelop_xml(Salmon::MagicEnvelope.new(privkey, payload)) }
|
let(:payload) { Entities::TestEntity.new(test: "asdf") }
|
||||||
|
let(:envelope) { Salmon::MagicEnvelope.new(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
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
@ -21,40 +14,43 @@ module DiasporaFederation
|
||||||
[data, type, enc, alg].map {|i| Base64.urlsafe_encode64(i) }.join(".")
|
[data, type, enc, alg].map {|i| Base64.urlsafe_encode64(i) }.join(".")
|
||||||
end
|
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
|
context "sanity" do
|
||||||
it "constructs an instance" do
|
it "constructs an instance" do
|
||||||
expect {
|
expect {
|
||||||
Salmon::MagicEnvelope.new(privkey, payload)
|
Salmon::MagicEnvelope.new(payload)
|
||||||
}.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, val)
|
Salmon::MagicEnvelope.new(val)
|
||||||
}.to raise_error ArgumentError
|
}.to raise_error ArgumentError
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#envelop" do
|
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
|
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
|
end
|
||||||
|
|
||||||
it "returns a magic envelope of correct structure" do
|
it "returns a magic envelope of correct structure" do
|
||||||
env = envelop_xml(subject)
|
env_xml = envelope.envelop(privkey, sender_id)
|
||||||
expect(env.name).to eq("env")
|
expect(env_xml.name).to eq("env")
|
||||||
|
|
||||||
control = %w(data encoding alg sig)
|
control = %w(data encoding alg sig)
|
||||||
env.children.each do |node|
|
env_xml.children.each do |node|
|
||||||
expect(control).to include(node.name)
|
expect(control).to include(node.name)
|
||||||
control.reject! {|i| i == node.name }
|
control.reject! {|i| i == node.name }
|
||||||
end
|
end
|
||||||
|
|
@ -62,28 +58,38 @@ module DiasporaFederation
|
||||||
expect(control).to be_empty
|
expect(control).to be_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
it "signs the payload correctly" do
|
it "adds the sender_id to the signature" do
|
||||||
env = envelop_xml(subject)
|
key_id = envelope.envelop(privkey, sender_id).at_xpath("me:sig")["key_id"]
|
||||||
|
|
||||||
subj = sig_subj(env)
|
expect(Base64.urlsafe_decode64(key_id)).to eq(sender_id)
|
||||||
sig = Base64.urlsafe_decode64(env.at_xpath("me:sig").content)
|
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
|
expect(privkey.public_key.verify(OpenSSL::Digest::SHA256.new, sig, subj)).to be_truthy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#encrypt!" do
|
describe "#encrypt!" do
|
||||||
subject { Salmon::MagicEnvelope.new(privkey, payload) }
|
|
||||||
|
|
||||||
it "encrypts the payload, returning cipher params" do
|
it "encrypts the payload, returning cipher params" do
|
||||||
params = subject.encrypt!
|
params = envelope.encrypt!
|
||||||
expect(params).to include(:key, :iv)
|
expect(params).to include(:key, :iv)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "actually encrypts the payload" do
|
it "actually encrypts the payload" do
|
||||||
plain_payload = subject.payload
|
plain_payload = envelope.instance_variable_get(:@payload)
|
||||||
params = subject.encrypt!
|
params = envelope.encrypt!
|
||||||
encrypted_payload = subject.payload
|
encrypted_payload = envelope.instance_variable_get(:@payload)
|
||||||
|
|
||||||
cipher = OpenSSL::Cipher.new(Salmon::AES::CIPHER)
|
cipher = OpenSSL::Cipher.new(Salmon::AES::CIPHER)
|
||||||
cipher.encrypt
|
cipher.encrypt
|
||||||
|
|
@ -98,9 +104,14 @@ module DiasporaFederation
|
||||||
|
|
||||||
describe ".unenvelop" do
|
describe ".unenvelop" do
|
||||||
context "sanity" 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
|
it "works with sane input" do
|
||||||
expect {
|
expect {
|
||||||
Salmon::MagicEnvelope.unenvelop(envelope, privkey.public_key)
|
Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey, sender_id), privkey.public_key)
|
||||||
}.not_to raise_error
|
}.not_to raise_error
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -121,14 +132,13 @@ 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, other_key.public_key)
|
Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey, sender_id), 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 = envelop_xml(Salmon::MagicEnvelope.new(privkey, payload))
|
bad_env = envelope.envelop(privkey, sender_id)
|
||||||
elem = bad_env.at_xpath("me:encoding")
|
bad_env.at_xpath("me:encoding").content = "invalid_enc"
|
||||||
elem.content = "invalid_enc"
|
|
||||||
re_sign(bad_env, privkey)
|
re_sign(bad_env, privkey)
|
||||||
expect {
|
expect {
|
||||||
Salmon::MagicEnvelope.unenvelop(bad_env, privkey.public_key)
|
Salmon::MagicEnvelope.unenvelop(bad_env, privkey.public_key)
|
||||||
|
|
@ -136,9 +146,8 @@ module DiasporaFederation
|
||||||
end
|
end
|
||||||
|
|
||||||
it "verifies the algorithm" do
|
it "verifies the algorithm" do
|
||||||
bad_env = envelop_xml(Salmon::MagicEnvelope.new(privkey, payload))
|
bad_env = envelope.envelop(privkey, sender_id)
|
||||||
elem = bad_env.at_xpath("me:alg")
|
bad_env.at_xpath("me:alg").content = "invalid_alg"
|
||||||
elem.content = "invalid_alg"
|
|
||||||
re_sign(bad_env, privkey)
|
re_sign(bad_env, privkey)
|
||||||
expect {
|
expect {
|
||||||
Salmon::MagicEnvelope.unenvelop(bad_env, privkey.public_key)
|
Salmon::MagicEnvelope.unenvelop(bad_env, privkey.public_key)
|
||||||
|
|
@ -147,18 +156,17 @@ module DiasporaFederation
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns the original entity" do
|
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).to be_an_instance_of Entities::TestEntity
|
||||||
expect(entity.test).to eq("asdf")
|
expect(entity.test).to eq("asdf")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "decrypts on the fly, when cipher params are present" do
|
it "decrypts on the fly, when cipher params are present" do
|
||||||
env = Salmon::MagicEnvelope.new(privkey, payload)
|
params = envelope.encrypt!
|
||||||
params = env.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).to be_an_instance_of Entities::TestEntity
|
||||||
expect(entity.test).to eq("asdf")
|
expect(entity.test).to eq("asdf")
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue