Remove generation code for legacy salmon slap from library

Create legacy_helper.rb to still generate old XMLs for the tests.

Related to #30
This commit is contained in:
Benjamin Neff 2017-05-01 18:56:38 +02:00
parent 18a23df8d5
commit ba01882cb2
No known key found for this signature in database
GPG key ID: 971464C3F1A90194
8 changed files with 71 additions and 305 deletions

View file

@ -48,32 +48,12 @@ module DiasporaFederation
# Finally, before decrypting the magic envelope payload, the signature should
# first be verified.
#
# @example Generating an encrypted Salmon Slap
# author_id = "author@pod.example.tld"
# author_privkey = however_you_retrieve_the_authors_private_key(author_id)
# recipient_pubkey = however_you_retrieve_the_recipients_public_key()
# entity = YourEntity.new(attr: "val")
#
# slap_xml = EncryptedSlap.prepare(author_id, author_privkey, entity).generate_xml(recipient_pubkey)
#
# @example Parsing a Salmon Slap
# recipient_privkey = however_you_retrieve_the_recipients_private_key()
# entity = EncryptedSlap.from_xml(slap_xml, recipient_privkey).payload
#
# @deprecated
class EncryptedSlap < Slap
# the author of the slap
# @param [String] value the author diaspora* ID
attr_writer :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
# the prepared encrypted magic envelope xml
# @param [Nokogiri::XML::Element] value magic envelope xml
attr_writer :magic_envelope_xml
# Creates a {MagicEnvelope} instance from the data within the given XML string
# containing an encrypted payload.
#
@ -98,44 +78,6 @@ module DiasporaFederation
MagicEnvelope.unenvelop(magic_env_from_doc(doc), sender, cipher_params)
end
# Creates an encrypted Salmon Slap.
#
# @param [String] author_id diaspora* ID 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(entity)
slap.cipher_params = magic_envelope.encrypt!
slap.magic_envelope_xml = magic_envelope.envelop(privkey).root
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 generate_xml(pubkey)
raise ArgumentError unless pubkey.instance_of?(OpenSSL::PKey::RSA)
Slap.build_xml do |xml|
xml.encrypted_header(encrypted_header(@author_id, @cipher_params, pubkey))
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
@ -162,45 +104,6 @@ module DiasporaFederation
xml = AES.decrypt(cipher_header["ciphertext"], Base64.decode64(key["key"]), Base64.decode64(key["iv"]))
Nokogiri::XML(xml).root
end
# Encrypt the header xml with an AES cipher and encrypt the cipher params
# with the recipients public_key.
# @param [String] author_id diaspora_handle
# @param [Hash] envelope_key envelope cipher params
# @param [OpenSSL::PKey::RSA] pubkey recipient public_key
# @return [String] encrypted base64 encoded header
def encrypted_header(author_id, envelope_key, pubkey)
data = header_xml(author_id, strict_base64_encode(envelope_key))
header_key = AES.generate_key_and_iv
ciphertext = AES.encrypt(data, header_key[:key], header_key[:iv])
json_key = JSON.generate(strict_base64_encode(header_key))
encrypted_key = Base64.strict_encode64(pubkey.public_encrypt(json_key))
json_header = JSON.generate(aes_key: encrypted_key, ciphertext: ciphertext)
Base64.strict_encode64(json_header)
end
# 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 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)
}
}.to_xml.strip
end
# @param [Hash] hash { key: "...", iv: "..." }
# @return [Hash] encoded hash: { key: "...", iv: "..." }
def strict_base64_encode(hash)
hash.map {|k, v| [k, Base64.strict_encode64(v)] }.to_h
end
end
end
end

View file

@ -76,22 +76,6 @@ module DiasporaFederation
}
end
# Encrypts the payload with a new, random AES cipher and returns the cipher
# params that were used.
#
# This must happen after the MagicEnvelope instance was created and before
# {MagicEnvelope#envelop} is called.
#
# @see AES#generate_key_and_iv
# @see AES#encrypt
#
# @return [Hash] AES key and iv. E.g.: { key: "...", iv: "..." }
def encrypt!
AES.generate_key_and_iv.tap do |key|
@payload_data = AES.encrypt(payload_data, key[:key], key[:iv])
end
end
# Extracts the entity encoded in the magic envelope data, if the signature
# is valid. If +cipher_params+ is given, also attempts to decrypt the payload first.
#

View file

@ -13,13 +13,6 @@ module DiasporaFederation
# {magic_envelope}
# </diaspora>
#
# @example Generating a Salmon Slap
# author_id = "author@pod.example.tld"
# author_privkey = however_you_retrieve_the_authors_private_key(author_id)
# entity = YourEntity.new(attr: "val")
#
# slap_xml = Slap.generate_xml(author_id, author_privkey, entity)
#
# @example Parsing a Salmon Slap
# entity = Slap.from_xml(slap_xml).payload
#
@ -49,19 +42,6 @@ module DiasporaFederation
MagicEnvelope.unenvelop(magic_env_from_doc(doc), sender)
end
# Builds the xml for the Salmon Slap.
#
# @yield [xml] Invokes the block with the
# {http://www.rubydoc.info/gems/nokogiri/Nokogiri/XML/Builder Nokogiri::XML::Builder}
# @return [String] Slap XML
def self.build_xml
Nokogiri::XML::Builder.new(encoding: "UTF-8") {|xml|
xml.diaspora("xmlns" => Salmon::XMLNS, "xmlns:me" => MagicEnvelope::XMLNS) {
yield xml
}
}.to_xml
end
# Parses the magic envelop from the document.
#
# @param [Nokogiri::XML::Document] doc Salmon XML Document

View file

@ -24,13 +24,7 @@ module DiasporaFederation
it "parses the entity with legacy slap receiver" do
expect_callback(:fetch_public_key, post.author).and_return(sender_key)
data = DiasporaFederation::Salmon::Slap.build_xml do |xml|
xml.header {
xml.author_id(post.author)
}
xml.parent << Salmon::MagicEnvelope.new(post, post.author).envelop(sender_key).root
end
data = generate_legacy_salmon_slap(post, post.author, sender_key)
expect_callback(:receive_entity, kind_of(Entities::StatusMessage), post.author, nil) do |_, entity|
expect(entity.guid).to eq(post.guid)
@ -71,8 +65,7 @@ module DiasporaFederation
it "parses the entity with legacy slap receiver" do
expect_callback(:fetch_public_key, post.author).and_return(sender_key)
data = DiasporaFederation::Salmon::EncryptedSlap.prepare(post.author, sender_key, post)
.generate_xml(recipient_key)
data = generate_legacy_encrypted_salmon_slap(post, post.author, sender_key, recipient_key)
expect_callback(:receive_entity, kind_of(Entities::StatusMessage), post.author, 1234) do |_, entity|
expect(entity.guid).to eq(post.guid)

View file

@ -4,137 +4,7 @@ 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(:payload) { Entities::TestEntity.new(test: "qwertzuiop") }
let(:slap_xml) { Salmon::EncryptedSlap.prepare(sender, privkey, payload).generate_xml(recipient_key.public_key) }
context "generate" do
describe ".prepare" do
context "sanity" do
it "raises an error when the sender is the wrong type" do
[1234, true, :symbol, payload, privkey].each do |val|
expect {
Salmon::EncryptedSlap.prepare(val, privkey, payload)
}.to raise_error ArgumentError
end
end
it "raises an error when the privkey is the wrong type" do
["asdf", 1234, true, :symbol, payload].each do |val|
expect {
Salmon::EncryptedSlap.prepare(sender, val, payload)
}.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(sender, 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.prepare(sender, privkey, payload).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, payload].each do |val|
expect {
Salmon::EncryptedSlap.prepare(sender, privkey, payload).generate_xml(val)
}.to raise_error ArgumentError
end
end
end
it "generates valid xml" do
doc = Nokogiri::XML(slap_xml)
expect(doc.root.name).to eq("diaspora")
expect(doc.at_xpath("d:diaspora/d:encrypted_header", ns).content).to_not be_empty
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(sender, privkey, payload)
doc1 = Nokogiri::XML(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))
header_key1 = JSON.parse(recipient_key.private_decrypt(Base64.decode64(cipher_header1["aes_key"])))
decrypted_header1 = Salmon::AES.decrypt(cipher_header1["ciphertext"],
Base64.decode64(header_key1["key"]),
Base64.decode64(header_key1["iv"]))
recipient2_key = OpenSSL::PKey::RSA.generate(1024)
doc2 = Nokogiri::XML(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))
header_key2 = JSON.parse(recipient2_key.private_decrypt(Base64.decode64(cipher_header2["aes_key"])))
decrypted_header2 = Salmon::AES.decrypt(cipher_header2["ciphertext"],
Base64.decode64(header_key2["key"]),
Base64.decode64(header_key2["iv"]))
expect(enc_header1).not_to eq(enc_header2)
expect(header_key1).not_to eq(header_key2)
expect(decrypted_header1).to eq(decrypted_header2)
expect(doc1.xpath("d:diaspora/me:env", ns).to_xml).to eq(doc2.xpath("d:diaspora/me:env", ns).to_xml)
end
it "does not add the sender to the magic envelope" do
doc = Nokogiri::XML(slap_xml)
expect(doc.at_xpath("d:diaspora/me:env/me:sig", ns)["key_id"]).to be_nil
end
context "header" do
subject {
doc = Nokogiri::XML(slap_xml)
doc.at_xpath("d:diaspora/d:encrypted_header", ns).content
}
let(:cipher_header) { JSON.parse(Base64.decode64(subject)) }
let(:header_key) {
JSON.parse(recipient_key.private_decrypt(Base64.decode64(cipher_header["aes_key"])))
}
it "encodes the header correctly" do
json_header = {}
expect {
json_header = JSON.parse(Base64.decode64(subject))
}.not_to raise_error
expect(json_header).to include("aes_key", "ciphertext")
end
it "encrypts the public_key encrypted header correctly" do
key = {}
expect {
key = JSON.parse(recipient_key.private_decrypt(Base64.decode64(cipher_header["aes_key"])))
}.not_to raise_error
expect(key).to include("key", "iv")
end
it "encrypts the aes encrypted header correctly" do
header = ""
expect {
header = Salmon::AES.decrypt(cipher_header["ciphertext"],
Base64.decode64(header_key["key"]),
Base64.decode64(header_key["iv"]))
}.not_to raise_error
header_doc = Nokogiri::XML(header)
expect(header_doc.root.name).to eq("decrypted_header")
expect(header_doc.xpath("//iv")).to have(1).item
expect(header_doc.xpath("//aes_key")).to have(1).item
expect(header_doc.xpath("//author_id")).to have(1).item
expect(header_doc.at_xpath("//author_id").content).to eq(sender)
end
end
end
end
let(:slap_xml) { generate_legacy_encrypted_salmon_slap(payload, sender, privkey, recipient_key.public_key) }
describe ".from_xml" do
context "sanity" do

View file

@ -80,28 +80,6 @@ module DiasporaFederation
end
end
describe "#encrypt!" do
it "encrypts the payload, returning cipher params" do
params = envelope.encrypt!
expect(params).to include(:key, :iv)
end
it "actually encrypts the payload" do
plain_payload = envelope.send(:payload_data)
params = envelope.encrypt!
encrypted_payload = envelope.send(:payload_data)
cipher = OpenSSL::Cipher.new(Salmon::AES::CIPHER)
cipher.encrypt
cipher.iv = params[:iv]
cipher.key = params[:key]
ciphertext = cipher.update(plain_payload) + cipher.final
expect(Base64.strict_encode64(ciphertext)).to eq(encrypted_payload)
end
end
describe ".unenvelop" do
let(:envelope_root) { envelope.envelop(privkey).root }
@ -210,7 +188,7 @@ module DiasporaFederation
expect_callback(:fetch_public_key, sender).and_return(privkey.public_key)
env = Salmon::MagicEnvelope.new(payload)
params = env.encrypt!
params = encrypt_magic_env(env)
env_xml = env.envelop(privkey).root
magic_env = Salmon::MagicEnvelope.unenvelop(env_xml, sender, params)

View file

@ -3,15 +3,7 @@ module DiasporaFederation
let(:sender) { "test_user@pod.somedomain.tld" }
let(:privkey) { OpenSSL::PKey::RSA.generate(512) } # use small key for speedy specs
let(:payload) { Entities::TestEntity.new(test: "qwertzuiop") }
let(:slap_xml) {
Salmon::Slap.build_xml do |xml|
xml.header {
xml.author_id(sender)
}
xml.parent << Salmon::MagicEnvelope.new(payload, sender).envelop(privkey).root
end
}
let(:slap_xml) { generate_legacy_salmon_slap(payload, sender, privkey) }
describe ".from_xml" do
context "sanity" do

View file

@ -0,0 +1,66 @@
# This file only exists to generate legacy XMLs to test that we can still parse it.
def generate_legacy_salmon_slap(entity, sender, sender_privkey)
build_salmon_slap_xml do |xml|
xml.header {
xml.author_id(sender)
}
xml.parent << DiasporaFederation::Salmon::MagicEnvelope.new(entity, sender).envelop(sender_privkey).root
end
end
def generate_legacy_encrypted_salmon_slap(entity, sender, sender_privkey, recipient_pubkey)
magic_envelope = DiasporaFederation::Salmon::MagicEnvelope.new(entity)
cipher_params = encrypt_magic_env(magic_envelope)
build_salmon_slap_xml do |xml|
xml.encrypted_header(encrypted_header(sender, cipher_params, recipient_pubkey))
xml.parent << magic_envelope.envelop(sender_privkey).root
end
end
def build_salmon_slap_xml
Nokogiri::XML::Builder.new(encoding: "UTF-8") {|xml|
xml.diaspora("xmlns" => DiasporaFederation::Salmon::XMLNS,
"xmlns:me" => DiasporaFederation::Salmon::MagicEnvelope::XMLNS) {
yield xml
}
}.to_xml
end
def encrypt_magic_env(magic_env)
DiasporaFederation::Salmon::AES.generate_key_and_iv.tap do |key|
magic_env.instance_variable_set(
"@payload_data", DiasporaFederation::Salmon::AES.encrypt(magic_env.send(:payload_data), key[:key], key[:iv])
)
end
end
def encrypted_header(author_id, envelope_key, pubkey)
data = decrypted_header_xml(author_id, strict_base64_encode(envelope_key))
header_key = DiasporaFederation::Salmon::AES.generate_key_and_iv
ciphertext = DiasporaFederation::Salmon::AES.encrypt(data, header_key[:key], header_key[:iv])
json_key = JSON.generate(strict_base64_encode(header_key))
encrypted_key = Base64.strict_encode64(pubkey.public_encrypt(json_key))
json_header = JSON.generate(aes_key: encrypted_key, ciphertext: ciphertext)
Base64.strict_encode64(json_header)
end
def decrypted_header_xml(author_id, envelope_key)
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)
}
}.to_xml.strip
end
def strict_base64_encode(hash)
hash.map {|k, v| [k, Base64.strict_encode64(v)] }.to_h
end