refactor encrypted slap to reuse it for every recipient
This commit is contained in:
parent
c7f33d7cf4
commit
a0398430ed
12 changed files with 204 additions and 130 deletions
|
|
@ -67,7 +67,7 @@ module DiasporaFederation
|
|||
# Generates an XML document from the current instance and returns it as string
|
||||
# @return [String] XML document
|
||||
def to_xml
|
||||
builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
|
||||
Nokogiri::XML::Builder.new(encoding: "UTF-8") {|xml|
|
||||
xml.XRD("xmlns" => XMLNS) {
|
||||
xml.Expires(@expires.strftime(DATETIME_FORMAT)) if @expires.instance_of?(DateTime)
|
||||
|
||||
|
|
@ -77,8 +77,7 @@ module DiasporaFederation
|
|||
add_properties_to(xml)
|
||||
add_links_to(xml)
|
||||
}
|
||||
end
|
||||
builder.to_xml
|
||||
}.to_xml
|
||||
end
|
||||
|
||||
# Parse the XRD document from the given string and create a hash containing
|
||||
|
|
|
|||
|
|
@ -63,14 +63,22 @@ module DiasporaFederation
|
|||
#
|
||||
# entity = slap.entity(author_pubkey)
|
||||
#
|
||||
class EncryptedSlap
|
||||
class EncryptedSlap < Slap
|
||||
# the key and iv if it is an encrypted slap
|
||||
# @param [Hash] value hash containing the key and iv
|
||||
attr_writer :cipher_params
|
||||
|
||||
# the prepared encrypted magic envelope xml
|
||||
# @param [Nokogiri::XML::Element] value magic envelope xml
|
||||
attr_writer :magic_envelope_xml
|
||||
|
||||
# Creates a Slap instance from the data within the given XML string
|
||||
# containing an encrypted payload.
|
||||
#
|
||||
# @param [String] slap_xml encrypted Salmon xml
|
||||
# @param [OpenSSL::PKey::RSA] privkey recipient private_key for decryption
|
||||
#
|
||||
# @return [Slap] new Slap instance
|
||||
# @return [EncryptedSlap] new Slap instance
|
||||
#
|
||||
# @raise [ArgumentError] if any of the arguments is of the wrong type
|
||||
# @raise [MissingHeader] if the +encrypted_header+ element is missing in the XML
|
||||
|
|
@ -79,7 +87,7 @@ module DiasporaFederation
|
|||
raise ArgumentError unless slap_xml.instance_of?(String) && privkey.instance_of?(OpenSSL::PKey::RSA)
|
||||
doc = Nokogiri::XML::Document.parse(slap_xml)
|
||||
|
||||
Slap.new.tap do |slap|
|
||||
EncryptedSlap.new.tap do |slap|
|
||||
header_elem = doc.at_xpath("d:diaspora/d:encrypted_header", Slap::NS)
|
||||
raise MissingHeader if header_elem.nil?
|
||||
header = header_data(header_elem.content, privkey)
|
||||
|
|
@ -90,29 +98,44 @@ module DiasporaFederation
|
|||
end
|
||||
end
|
||||
|
||||
# Creates an encrypted Salmon Slap and returns the XML string.
|
||||
# Creates an encrypted Salmon Slap.
|
||||
#
|
||||
# @param [String] author_id Diaspora* handle of the author
|
||||
# @param [OpenSSL::PKey::RSA] privkey sender private key for signing the magic envelope
|
||||
# @param [Entity] entity payload
|
||||
# @return [EncryptedSlap] encrypted Slap instance
|
||||
# @raise [ArgumentError] if any of the arguments is of the wrong type
|
||||
def self.prepare(author_id, privkey, entity)
|
||||
raise ArgumentError unless author_id.instance_of?(String) &&
|
||||
privkey.instance_of?(OpenSSL::PKey::RSA) &&
|
||||
entity.is_a?(Entity)
|
||||
|
||||
EncryptedSlap.new.tap do |slap|
|
||||
slap.author_id = author_id
|
||||
|
||||
magic_envelope = MagicEnvelope.new(privkey, entity)
|
||||
slap.cipher_params = magic_envelope.encrypt!
|
||||
slap.magic_envelope_xml = magic_envelope.envelop
|
||||
end
|
||||
end
|
||||
|
||||
# Creates an encrypted Salmon Slap XML string.
|
||||
#
|
||||
# @param [OpenSSL::PKey::RSA] pubkey recipient public key for encrypting the AES key
|
||||
# @return [String] Salmon XML string
|
||||
# @raise [ArgumentError] if any of the arguments is of the wrong type
|
||||
def self.generate_xml(author_id, privkey, entity, pubkey)
|
||||
raise ArgumentError unless author_id.instance_of?(String) &&
|
||||
privkey.instance_of?(OpenSSL::PKey::RSA) &&
|
||||
entity.is_a?(Entity) &&
|
||||
pubkey.instance_of?(OpenSSL::PKey::RSA)
|
||||
def generate_xml(pubkey)
|
||||
raise ArgumentError unless pubkey.instance_of?(OpenSSL::PKey::RSA)
|
||||
|
||||
Slap.build_xml do |xml|
|
||||
magic_envelope = MagicEnvelope.new(privkey, entity)
|
||||
envelope_key = magic_envelope.encrypt!
|
||||
xml.encrypted_header(encrypted_header(author_id, @cipher_params, pubkey))
|
||||
|
||||
encrypted_header(author_id, envelope_key, pubkey, xml)
|
||||
magic_envelope.envelop(xml)
|
||||
xml.parent << @magic_envelope_xml
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# decrypts and reads the data from the encrypted XML header
|
||||
# @param [String] data base64 encoded, encrypted header data
|
||||
# @param [OpenSSL::PKey::RSA] privkey private key for decryption
|
||||
|
|
@ -147,43 +170,33 @@ module DiasporaFederation
|
|||
# @param [String] author_id diaspora_handle
|
||||
# @param [Hash] envelope_key envelope cipher params
|
||||
# @param [OpenSSL::PKey::RSA] pubkey recipient public_key
|
||||
# @param [Nokogiri::XML::Element] xml parent element for inserting in XML document
|
||||
def self.encrypted_header(author_id, envelope_key, pubkey, xml)
|
||||
data = header_xml(author_id, strict_base64_encode(envelope_key))
|
||||
key = AES.generate_key_and_iv
|
||||
ciphertext = AES.encrypt(data, key[:key], key[:iv])
|
||||
# @return [String] encrypted base64 encoded header
|
||||
def encrypted_header(author_id, envelope_key, pubkey)
|
||||
encoded_key = Hash[envelope_key.map {|k, v| [k, Base64.strict_encode64(v)] }]
|
||||
data = header_xml(author_id, encoded_key)
|
||||
ciphertext = AES.encrypt(data, envelope_key[:key], envelope_key[:iv])
|
||||
|
||||
json_key = JSON.generate(strict_base64_encode(key))
|
||||
json_key = JSON.generate(encoded_key)
|
||||
encrypted_key = Base64.strict_encode64(pubkey.public_encrypt(json_key))
|
||||
|
||||
json_header = JSON.generate(aes_key: encrypted_key, ciphertext: ciphertext)
|
||||
|
||||
xml.encrypted_header(Base64.strict_encode64(json_header))
|
||||
Base64.strict_encode64(json_header)
|
||||
end
|
||||
private_class_method :encrypted_header
|
||||
|
||||
# generate the header xml string, including the author, aes_key and iv
|
||||
# @param [String] author_id diaspora_handle of the author
|
||||
# @param [Hash] envelope_key { key: "...", iv: "..." } (values in base64)
|
||||
# @return [String] header XML string
|
||||
def self.header_xml(author_id, envelope_key)
|
||||
builder = Nokogiri::XML::Builder.new do |xml|
|
||||
def header_xml(author_id, envelope_key)
|
||||
@header_xml ||= Nokogiri::XML::Builder.new(encoding: "UTF-8") {|xml|
|
||||
xml.decrypted_header {
|
||||
xml.iv(envelope_key[:iv])
|
||||
xml.aes_key(envelope_key[:key])
|
||||
xml.author_id(author_id)
|
||||
}
|
||||
end
|
||||
builder.to_xml.strip
|
||||
end
|
||||
private_class_method :header_xml
|
||||
|
||||
# @param [Hash] hash { key: "...", iv: "..." }
|
||||
# @return [Hash] encoded hash: { key: "...", iv: "..." }
|
||||
def self.strict_base64_encode(hash)
|
||||
Hash[hash.map {|k, v| [k, Base64.strict_encode64(v)] }]
|
||||
end
|
||||
private_class_method :strict_base64_encode
|
||||
}.to_xml.strip
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -55,14 +55,20 @@ module DiasporaFederation
|
|||
# Builds the XML structure for the magic envelope, inserts the {ENCODING}
|
||||
# encoded data and signs the envelope using {DIGEST}.
|
||||
#
|
||||
# @param [Nokogiri::XML::Builder] xml Salmon XML builder
|
||||
def envelop(xml)
|
||||
xml["me"].env {
|
||||
xml["me"].data(Base64.urlsafe_encode64(@payload), type: DATA_TYPE)
|
||||
xml["me"].encoding(ENCODING)
|
||||
xml["me"].alg(ALGORITHM)
|
||||
xml["me"].sig(Base64.urlsafe_encode64(signature))
|
||||
# @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
|
||||
}
|
||||
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
|
||||
|
|
@ -102,8 +108,8 @@ module DiasporaFederation
|
|||
# @raise [InvalidEncoding] if the data is wrongly encoded
|
||||
# @raise [InvalidAlgorithm] if the algorithm used doesn't match
|
||||
def self.unenvelop(magic_env, rsa_pubkey, cipher_params=nil)
|
||||
raise ArgumentError unless rsa_pubkey.instance_of?(OpenSSL::PKey::RSA) &&
|
||||
magic_env.instance_of?(Nokogiri::XML::Element)
|
||||
raise ArgumentError unless magic_env.instance_of?(Nokogiri::XML::Element) &&
|
||||
rsa_pubkey.instance_of?(OpenSSL::PKey::RSA)
|
||||
|
||||
raise InvalidEnvelope unless envelope_valid?(magic_env)
|
||||
raise InvalidSignature unless signature_valid?(magic_env, rsa_pubkey)
|
||||
|
|
|
|||
|
|
@ -33,10 +33,6 @@ module DiasporaFederation
|
|||
# @param [String] the author diaspora id
|
||||
attr_accessor :author_id
|
||||
|
||||
# the key and iv if it is an encrypted slap
|
||||
# @param [Hash] value hash containing the key and iv
|
||||
attr_writer :cipher_params
|
||||
|
||||
# Namespaces
|
||||
NS = {d: Salmon::XMLNS, me: MagicEnvelope::XMLNS}
|
||||
|
||||
|
|
@ -94,7 +90,7 @@ module DiasporaFederation
|
|||
xml.author_id(author_id)
|
||||
}
|
||||
|
||||
MagicEnvelope.new(privkey, entity).envelop(xml)
|
||||
xml.parent << MagicEnvelope.new(privkey, entity).envelop
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -104,12 +100,11 @@ module DiasporaFederation
|
|||
# {http://www.rubydoc.info/gems/nokogiri/Nokogiri/XML/Builder Nokogiri::XML::Builder}
|
||||
# @return [String] Slap XML
|
||||
def self.build_xml
|
||||
builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
|
||||
Nokogiri::XML::Builder.new(encoding: "UTF-8") {|xml|
|
||||
xml.diaspora("xmlns" => Salmon::XMLNS, "xmlns:me" => MagicEnvelope::XMLNS) {
|
||||
yield xml
|
||||
}
|
||||
end
|
||||
builder.to_xml
|
||||
}.to_xml
|
||||
end
|
||||
|
||||
# Parses the magic envelop from the document.
|
||||
|
|
|
|||
|
|
@ -4,12 +4,8 @@ module DiasporaFederation
|
|||
let(:sender_key) { OpenSSL::PKey::RSA.generate(1024) }
|
||||
let(:recipient_key) { OpenSSL::PKey::RSA.generate(1024) }
|
||||
let(:xml) {
|
||||
DiasporaFederation::Salmon::EncryptedSlap.generate_xml(
|
||||
sender_id,
|
||||
sender_key,
|
||||
FactoryGirl.build(:request_entity),
|
||||
recipient_key
|
||||
)
|
||||
DiasporaFederation::Salmon::EncryptedSlap.prepare(sender_id, sender_key, FactoryGirl.build(:request_entity))
|
||||
.generate_xml(recipient_key)
|
||||
}
|
||||
|
||||
it "calls save_entity_after_receive if everything is fine" do
|
||||
|
|
|
|||
|
|
@ -4,21 +4,51 @@ module DiasporaFederation
|
|||
let(:privkey) { OpenSSL::PKey::RSA.generate(512) } # use small key for speedy specs
|
||||
let(:recipient_key) { OpenSSL::PKey::RSA.generate(1024) } # use small key for speedy specs
|
||||
let(:entity) { Entities::TestEntity.new(test: "qwertzuiop") }
|
||||
let(:slap_xml) { Salmon::EncryptedSlap.generate_xml(author_id, privkey, entity, recipient_key.public_key) }
|
||||
let(:ns) { {d: Salmon::XMLNS, me: Salmon::MagicEnvelope::XMLNS} }
|
||||
let(:slap_xml) { Salmon::EncryptedSlap.prepare(author_id, privkey, entity).generate_xml(recipient_key.public_key) }
|
||||
|
||||
context "generate" do
|
||||
describe ".prepare" do
|
||||
context "sanity" do
|
||||
it "raises an error when the author_id is the wrong type" do
|
||||
[1234, true, :symbol, entity, privkey].each do |val|
|
||||
expect {
|
||||
Salmon::EncryptedSlap.prepare(val, privkey, entity)
|
||||
}.to raise_error ArgumentError
|
||||
end
|
||||
end
|
||||
|
||||
it "raises an error when the privkey is the wrong type" do
|
||||
["asdf", 1234, true, :symbol, entity].each do |val|
|
||||
expect {
|
||||
Salmon::EncryptedSlap.prepare(author_id, val, entity)
|
||||
}.to raise_error ArgumentError
|
||||
end
|
||||
end
|
||||
|
||||
it "raises an error when the entity is the wrong type" do
|
||||
["asdf", 1234, true, :symbol, privkey].each do |val|
|
||||
expect {
|
||||
Salmon::EncryptedSlap.prepare(author_id, privkey, val)
|
||||
}.to raise_error ArgumentError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".generate_xml" do
|
||||
let(:ns) { {d: Salmon::XMLNS, me: Salmon::MagicEnvelope::XMLNS} }
|
||||
|
||||
context "sanity" do
|
||||
it "accepts correct params" do
|
||||
expect {
|
||||
Salmon::EncryptedSlap.generate_xml(author_id, privkey, entity, recipient_key.public_key)
|
||||
Salmon::EncryptedSlap.prepare(author_id, privkey, entity).generate_xml(recipient_key.public_key)
|
||||
}.not_to raise_error
|
||||
end
|
||||
|
||||
it "raises an error when the params are the wrong type" do
|
||||
["asdf", 1234, true, :symbol, entity, privkey].each do |val|
|
||||
["asdf", 1234, true, :symbol, entity].each do |val|
|
||||
expect {
|
||||
Salmon::EncryptedSlap.generate_xml(val, val, val, val)
|
||||
Salmon::EncryptedSlap.prepare(author_id, privkey, entity).generate_xml(val)
|
||||
}.to raise_error ArgumentError
|
||||
end
|
||||
end
|
||||
|
|
@ -31,6 +61,25 @@ module DiasporaFederation
|
|||
expect(doc.xpath("d:diaspora/me:env", ns)).to have(1).item
|
||||
end
|
||||
|
||||
it "can generate xml for two people" do
|
||||
slap = Salmon::EncryptedSlap.prepare(author_id, privkey, entity)
|
||||
|
||||
doc1 = Nokogiri::XML::Document.parse(slap.generate_xml(recipient_key.public_key))
|
||||
enc_header1 = doc1.at_xpath("d:diaspora/d:encrypted_header", ns).content
|
||||
cipher_header1 = JSON.parse(Base64.decode64(enc_header1))
|
||||
key_json1 = recipient_key.private_decrypt(Base64.decode64(cipher_header1["aes_key"]))
|
||||
|
||||
recipient2_key = OpenSSL::PKey::RSA.generate(1024)
|
||||
doc2 = Nokogiri::XML::Document.parse(slap.generate_xml(recipient2_key.public_key))
|
||||
enc_header2 = doc2.at_xpath("d:diaspora/d:encrypted_header", ns).content
|
||||
cipher_header2 = JSON.parse(Base64.decode64(enc_header2))
|
||||
key_json2 = recipient2_key.private_decrypt(Base64.decode64(cipher_header2["aes_key"]))
|
||||
|
||||
expect(enc_header1).not_to eq(enc_header2)
|
||||
expect(key_json1).to eq(key_json2)
|
||||
expect(doc1.xpath("d:diaspora/me:env", ns).to_xml).to eq(doc2.xpath("d:diaspora/me:env", ns).to_xml)
|
||||
end
|
||||
|
||||
context "header" do
|
||||
subject {
|
||||
doc = Nokogiri::XML::Document.parse(slap_xml)
|
||||
|
|
@ -73,6 +122,7 @@ module DiasporaFederation
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".from_xml" do
|
||||
context "sanity" do
|
||||
|
|
|
|||
|
|
@ -5,12 +5,11 @@ module DiasporaFederation
|
|||
let(:envelope) { envelop_xml(Salmon::MagicEnvelope.new(privkey, payload)) }
|
||||
|
||||
def envelop_xml(magic_env)
|
||||
builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
|
||||
Nokogiri::XML::Builder.new(encoding: "UTF-8") {|xml|
|
||||
xml.root("xmlns:me" => Salmon::MagicEnvelope::XMLNS) {
|
||||
magic_env.envelop(xml)
|
||||
xml.parent << magic_env.envelop
|
||||
}
|
||||
end
|
||||
builder.doc.at_xpath("//me:env")
|
||||
}.doc.at_xpath("//me:env")
|
||||
end
|
||||
|
||||
def sig_subj(env)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ module DiasporaFederation
|
|||
let(:author_id) { "test_user@pod.somedomain.tld" }
|
||||
let(:privkey) { OpenSSL::PKey::RSA.generate(512) } # use small key for speedy specs
|
||||
let(:entity) { Entities::TestEntity.new(test: "qwertzuiop") }
|
||||
let(:slap) { Salmon::Slap.generate_xml(author_id, privkey, entity) }
|
||||
let(:slap_xml) { Salmon::Slap.generate_xml(author_id, privkey, entity) }
|
||||
|
||||
describe ".generate_xml" do
|
||||
context "sanity" do
|
||||
|
|
@ -13,10 +13,26 @@ module DiasporaFederation
|
|||
}.not_to raise_error
|
||||
end
|
||||
|
||||
it "raises an error when the params are the wrong type" do
|
||||
["asdf", 1234, true, :symbol, entity, privkey].each do |val|
|
||||
it "raises an error when the author_id is the wrong type" do
|
||||
[1234, true, :symbol, entity, privkey].each do |val|
|
||||
expect {
|
||||
Salmon::Slap.generate_xml(val, val, val)
|
||||
Salmon::Slap.generate_xml(val, privkey, entity)
|
||||
}.to raise_error ArgumentError
|
||||
end
|
||||
end
|
||||
|
||||
it "raises an error when the privkey is the wrong type" do
|
||||
["asdf", 1234, true, :symbol, entity].each do |val|
|
||||
expect {
|
||||
Salmon::Slap.generate_xml(author_id, val, entity)
|
||||
}.to raise_error ArgumentError
|
||||
end
|
||||
end
|
||||
|
||||
it "raises an error when the entity is the wrong type" do
|
||||
["asdf", 1234, true, :symbol, privkey].each do |val|
|
||||
expect {
|
||||
Salmon::Slap.generate_xml(author_id, privkey, val)
|
||||
}.to raise_error ArgumentError
|
||||
end
|
||||
end
|
||||
|
|
@ -24,7 +40,7 @@ module DiasporaFederation
|
|||
|
||||
it "generates valid xml" do
|
||||
ns = {d: Salmon::XMLNS, me: Salmon::MagicEnvelope::XMLNS}
|
||||
doc = Nokogiri::XML::Document.parse(slap)
|
||||
doc = Nokogiri::XML::Document.parse(slap_xml)
|
||||
expect(doc.root.name).to eq("diaspora")
|
||||
expect(doc.at_xpath("d:diaspora/d:header/d:author_id", ns).content).to eq(author_id)
|
||||
expect(doc.xpath("d:diaspora/me:env", ns)).to have(1).item
|
||||
|
|
@ -35,7 +51,7 @@ module DiasporaFederation
|
|||
context "sanity" do
|
||||
it "accepts salmon xml as param" do
|
||||
expect {
|
||||
Salmon::Slap.from_xml(slap)
|
||||
Salmon::Slap.from_xml(slap_xml)
|
||||
}.not_to raise_error
|
||||
end
|
||||
|
||||
|
|
@ -75,7 +91,7 @@ XML
|
|||
|
||||
context "generated instance" do
|
||||
it_behaves_like "a Slap instance" do
|
||||
subject { Salmon::Slap.from_xml(slap) }
|
||||
subject { Salmon::Slap.from_xml(slap_xml) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue