diff --git a/app/controllers/diaspora_federation/receive_controller.rb b/app/controllers/diaspora_federation/receive_controller.rb index a477dfd..a9568b5 100644 --- a/app/controllers/diaspora_federation/receive_controller.rb +++ b/app/controllers/diaspora_federation/receive_controller.rb @@ -5,18 +5,14 @@ require_dependency "diaspora_federation/application_controller" module DiasporaFederation # This controller processes receiving messages. class ReceiveController < ApplicationController - before_action :check_for_xml - # Receives public messages # # POST /receive/public def public - legacy = request.content_type != "application/magic-envelope+xml" - - data = data_for_public_message(legacy) + data = request.body.read logger.debug data - DiasporaFederation.callbacks.trigger(:queue_public_receive, data, legacy) + DiasporaFederation.callbacks.trigger(:queue_public_receive, data) head :accepted end @@ -25,43 +21,12 @@ module DiasporaFederation # # POST /receive/users/:guid def private - legacy = request.content_type != "application/json" - - data = data_for_private_message(legacy) + data = request.body.read logger.debug data - success = DiasporaFederation.callbacks.trigger(:queue_private_receive, params[:guid], data, legacy) + success = DiasporaFederation.callbacks.trigger(:queue_private_receive, params[:guid], data) head success ? :accepted : :not_found end - - private - - # Checks the xml parameter for legacy salmon slaps - # @deprecated - def check_for_xml - legacy_request = request.content_type.nil? || request.content_type == "application/x-www-form-urlencoded" - head :unprocessable_entity if params[:xml].nil? && legacy_request - end - - def data_for_public_message(legacy) - if legacy - logger.info "received a public salmon slap" - CGI.unescape(params[:xml]) - else - logger.info "received a public magic envelope" - request.body.read - end - end - - def data_for_private_message(legacy) - if legacy - logger.info "received a private salmon slap for #{params[:guid]}" - CGI.unescape(params[:xml]) - else - logger.info "received a private encrypted magic envelope for #{params[:guid]}" - request.body.read - end - end end end diff --git a/lib/diaspora_federation.rb b/lib/diaspora_federation.rb index ec2b036..f5079d6 100644 --- a/lib/diaspora_federation.rb +++ b/lib/diaspora_federation.rb @@ -184,13 +184,11 @@ module DiasporaFederation # queue_public_receive # Queue a public salmon xml to process in background # @param [String] data salmon slap xml or magic envelope xml - # @param [Boolean] legacy true if it is a legacy salmon slap, false if it is a magic envelope xml # # queue_private_receive # Queue a private salmon xml to process in background # @param [String] guid guid of the receiver person # @param [String] data salmon slap xml or encrypted magic envelope json - # @param [Boolean] legacy true if it is a legacy salmon slap, false if it is a encrypted magic envelope json # @return [Boolean] true if successful, false if the user was not found # # receive_entity diff --git a/lib/diaspora_federation/federation/receiver.rb b/lib/diaspora_federation/federation/receiver.rb index 4b6eb76..7552631 100644 --- a/lib/diaspora_federation/federation/receiver.rb +++ b/lib/diaspora_federation/federation/receiver.rb @@ -8,14 +8,9 @@ module DiasporaFederation # Receive a public message # @param [String] data message to receive - # @param [Boolean] legacy use old slap parser - def self.receive_public(data, legacy=false) - magic_env = if legacy - Salmon::Slap.from_xml(data) - else - magic_env_xml = Nokogiri::XML(data).root - Salmon::MagicEnvelope.unenvelop(magic_env_xml) - end + def self.receive_public(data) + magic_env_xml = Nokogiri::XML(data).root + magic_env = Salmon::MagicEnvelope.unenvelop(magic_env_xml) Public.new(magic_env).receive rescue => e # rubocop:disable Style/RescueStandardError logger.error "failed to receive public message: #{e.class}: #{e.message}" @@ -28,16 +23,11 @@ module DiasporaFederation # @param [OpenSSL::PKey::RSA] recipient_private_key recipient private key to decrypt the message # @param [Object] recipient_id the identifier to persist the entity for the correct user, # see +receive_entity+ callback - # @param [Boolean] legacy use old slap parser - def self.receive_private(data, recipient_private_key, recipient_id, legacy=false) + def self.receive_private(data, recipient_private_key, recipient_id) raise ArgumentError, "no recipient key provided" unless recipient_private_key.instance_of?(OpenSSL::PKey::RSA) - magic_env = if legacy - Salmon::EncryptedSlap.from_xml(data, recipient_private_key) - else - magic_env_xml = Salmon::EncryptedMagicEnvelope.decrypt(data, recipient_private_key) - Salmon::MagicEnvelope.unenvelop(magic_env_xml) - end + magic_env_xml = Salmon::EncryptedMagicEnvelope.decrypt(data, recipient_private_key) + magic_env = Salmon::MagicEnvelope.unenvelop(magic_env_xml) Private.new(magic_env, recipient_id).receive rescue => e # rubocop:disable Style/RescueStandardError logger.error "failed to receive private message for #{recipient_id}: #{e.class}: #{e.message}" diff --git a/lib/diaspora_federation/salmon.rb b/lib/diaspora_federation/salmon.rb index 0fc3ef9..0267598 100644 --- a/lib/diaspora_federation/salmon.rb +++ b/lib/diaspora_federation/salmon.rb @@ -16,5 +16,3 @@ require "diaspora_federation/salmon/exceptions" require "diaspora_federation/salmon/xml_payload" require "diaspora_federation/salmon/magic_envelope" require "diaspora_federation/salmon/encrypted_magic_envelope" -require "diaspora_federation/salmon/slap" -require "diaspora_federation/salmon/encrypted_slap" diff --git a/lib/diaspora_federation/salmon/encrypted_slap.rb b/lib/diaspora_federation/salmon/encrypted_slap.rb deleted file mode 100644 index 23da393..0000000 --- a/lib/diaspora_federation/salmon/encrypted_slap.rb +++ /dev/null @@ -1,113 +0,0 @@ -# frozen_string_literal: true - -require "json" - -module DiasporaFederation - module Salmon - # +EncryptedSlap+ provides class methods for generating and parsing encrypted - # Slaps. (In principle the same as {Slap}, but with encryption.) - # - # The basic encryption mechanism used here is based on the knowledge that - # asymmetrical encryption is slow and symmetrical encryption is fast. Keeping in - # mind that a message we want to de-/encrypt may greatly vary in length, - # performance considerations must play a part of this scheme. - # - # A diaspora*-flavored encrypted magic-enveloped XML message looks like the following: - # - # - # - # {encrypted_header} - # {magic_envelope with encrypted data} - # - # - # The encrypted header is encoded in JSON like this (when in plain text): - # - # { - # "aes_key" => "...", - # "ciphertext" => "..." - # } - # - # +aes_key+ is encrypted using the recipients public key, and contains the AES - # +key+ and +iv+ used to encrypt the +ciphertext+ also encoded as JSON. - # - # { - # "key" => "...", - # "iv" => "..." - # } - # - # +ciphertext+, once decrypted, contains the +author_id+, +aes_key+ and +iv+ - # relevant to the decryption of the data in the magic_envelope and the - # verification of its signature. - # - # The decrypted cyphertext has this XML structure: - # - # - # {iv} - # {aes_key} - # {author_id} - # - # - # Finally, before decrypting the magic envelope payload, the signature should - # first be verified. - # - # @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 - # Creates a {MagicEnvelope} 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 [MagicEnvelope] magic envelope instance with payload and sender - # - # @raise [ArgumentError] if any of the arguments is of the wrong type - # @raise [MissingHeader] if the +encrypted_header+ element is missing in the XML - # @raise [MissingMagicEnvelope] if the +me:env+ element is missing in the XML - def self.from_xml(slap_xml, privkey) - raise ArgumentError unless slap_xml.instance_of?(String) && privkey.instance_of?(OpenSSL::PKey::RSA) - - doc = Nokogiri::XML(slap_xml) - - 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) - sender = header[:author_id] - cipher_params = {key: Base64.decode64(header[:aes_key]), iv: Base64.decode64(header[:iv])} - - MagicEnvelope.unenvelop(magic_env_from_doc(doc), sender, cipher_params) - end - - # 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 - # @return [Hash] { iv: "...", aes_key: "...", author_id: "..." } - private_class_method def self.header_data(data, privkey) - header_elem = decrypt_header(data, privkey) - raise InvalidHeader unless header_elem.name == "decrypted_header" - - iv = header_elem.at_xpath("iv").content - key = header_elem.at_xpath("aes_key").content - author_id = header_elem.at_xpath("author_id").content - - {iv: iv, aes_key: key, author_id: author_id} - end - - # Decrypts the xml header - # @param [String] data base64 encoded, encrypted header data - # @param [OpenSSL::PKey::RSA] privkey private key for decryption - # @return [Nokogiri::XML::Element] header xml document - private_class_method def self.decrypt_header(data, privkey) - cipher_header = JSON.parse(Base64.decode64(data)) - key = JSON.parse(privkey.private_decrypt(Base64.decode64(cipher_header["aes_key"]))) - - xml = AES.decrypt(cipher_header["ciphertext"], Base64.decode64(key["key"]), Base64.decode64(key["iv"])) - Nokogiri::XML(xml).root - end - end - end -end diff --git a/lib/diaspora_federation/salmon/exceptions.rb b/lib/diaspora_federation/salmon/exceptions.rb index 07061be..b9bde07 100644 --- a/lib/diaspora_federation/salmon/exceptions.rb +++ b/lib/diaspora_federation/salmon/exceptions.rb @@ -2,26 +2,6 @@ module DiasporaFederation module Salmon - # Raised, if the element containing the Magic Envelope is missing from the XML - # @deprecated - class MissingMagicEnvelope < RuntimeError - end - - # Raised, if the element containing the author is empty. - # @deprecated - class MissingAuthor < RuntimeError - end - - # Raised, if the element containing the header is missing from the XML - # @deprecated - class MissingHeader < RuntimeError - end - - # Raised, if the decrypted header has an unexpected XML structure - # @deprecated - class InvalidHeader < RuntimeError - end - # Raised, if failed to fetch the public key of the sender of the received message class SenderKeyNotFound < RuntimeError end diff --git a/lib/diaspora_federation/salmon/slap.rb b/lib/diaspora_federation/salmon/slap.rb deleted file mode 100644 index 242337d..0000000 --- a/lib/diaspora_federation/salmon/slap.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -module DiasporaFederation - module Salmon - # +Slap+ provides class methods to create unencrypted Slap XML from payload - # data and parse incoming XML into a Slap instance. - # - # A diaspora* flavored magic-enveloped XML message looks like the following: - # - # - # - #
- # {author} - #
- # {magic_envelope} - #
- # - # @example Parsing a Salmon Slap - # entity = Slap.from_xml(slap_xml).payload - # - # @deprecated - class Slap - # Namespaces - NS = {d: Salmon::XMLNS, me: MagicEnvelope::XMLNS}.freeze - - # Parses an unencrypted Salmon XML string and returns a new instance of - # {MagicEnvelope} with the XML data. - # - # @param [String] slap_xml Salmon XML - # - # @return [MagicEnvelope] magic envelope instance with payload and sender - # - # @raise [ArgumentError] if the argument is not a String - # @raise [MissingAuthor] if the +author_id+ element is missing from the XML - # @raise [MissingMagicEnvelope] if the +me:env+ element is missing from the XML - def self.from_xml(slap_xml) - raise ArgumentError unless slap_xml.instance_of?(String) - - doc = Nokogiri::XML(slap_xml) - - author_elem = doc.at_xpath("d:diaspora/d:header/d:author_id", Slap::NS) - raise MissingAuthor if author_elem.nil? || author_elem.content.empty? - - sender = author_elem.content - - MagicEnvelope.unenvelop(magic_env_from_doc(doc), sender) - end - - # Parses the magic envelop from the document. - # - # @param [Nokogiri::XML::Document] doc Salmon XML Document - private_class_method def self.magic_env_from_doc(doc) - doc.at_xpath("d:diaspora/me:env", Slap::NS).tap do |env| - raise MissingMagicEnvelope if env.nil? - end - end - end - end -end diff --git a/spec/controllers/diaspora_federation/receive_controller_spec.rb b/spec/controllers/diaspora_federation/receive_controller_spec.rb index 0d5b684..b0aaaaa 100644 --- a/spec/controllers/diaspora_federation/receive_controller_spec.rb +++ b/spec/controllers/diaspora_federation/receive_controller_spec.rb @@ -5,32 +5,6 @@ module DiasporaFederation routes { DiasporaFederation::Engine.routes } describe "POST #public" do - context "legacy salmon slap" do - it "returns a 422 if no xml is passed" do - post :public - expect(response.code).to eq("422") - end - - it "returns a 422 if no xml is passed with content-type application/x-www-form-urlencoded" do - @request.env["CONTENT_TYPE"] = "application/x-www-form-urlencoded" - post :public - expect(response.code).to eq("422") - end - - it "returns a 202 if queued correctly" do - expect_callback(:queue_public_receive, "", true) - - post :public, params: {xml: ""} - expect(response.code).to eq("202") - end - - it "unescapes the xml before sending it to the callback" do - expect_callback(:queue_public_receive, "", true) - - post :public, params: {xml: CGI.escape("")} - end - end - context "magic envelope" do before do Mime::Type.register("application/magic-envelope+xml", :magic_envelope) @@ -38,7 +12,7 @@ module DiasporaFederation end it "returns a 202 if queued correctly" do - expect_callback(:queue_public_receive, "", false) + expect_callback(:queue_public_receive, "") post :public, body: +"" expect(response.code).to eq("202") @@ -47,39 +21,6 @@ module DiasporaFederation end describe "POST #private" do - context "legacy salmon slap" do - it "return a 404 if not queued successfully (unknown user guid)" do - expect_callback(:queue_private_receive, "any-guid", "", true).and_return(false) - - post :private, params: {guid: "any-guid", xml: ""} - expect(response.code).to eq("404") - end - - it "returns a 422 if no xml is passed" do - post :private, params: {guid: "any-guid"} - expect(response.code).to eq("422") - end - - it "returns a 422 if no xml is passed with content-type application/x-www-form-urlencoded" do - @request.env["CONTENT_TYPE"] = "application/x-www-form-urlencoded" - post :private, params: {guid: "any-guid"} - expect(response.code).to eq("422") - end - - it "returns a 202 if the callback returned true" do - expect_callback(:queue_private_receive, "any-guid", "", true).and_return(true) - - post :private, params: {guid: "any-guid", xml: ""} - expect(response.code).to eq("202") - end - - it "unescapes the xml before sending it to the callback" do - expect_callback(:queue_private_receive, "any-guid", "", true).and_return(true) - - post :private, params: {guid: "any-guid", xml: CGI.escape("")} - end - end - context "encrypted magic envelope" do before do @request.env["CONTENT_TYPE"] = "application/json" @@ -87,7 +28,7 @@ module DiasporaFederation it "return a 404 if not queued successfully (unknown user guid)" do expect_callback( - :queue_private_receive, "any-guid", "{\"aes_key\": \"key\", \"encrypted_magic_envelope\": \"env\"}", false + :queue_private_receive, "any-guid", "{\"aes_key\": \"key\", \"encrypted_magic_envelope\": \"env\"}" ).and_return(false) post :private, @@ -98,7 +39,7 @@ module DiasporaFederation it "returns a 202 if the callback returned true" do expect_callback( - :queue_private_receive, "any-guid", "{\"aes_key\": \"key\", \"encrypted_magic_envelope\": \"env\"}", false + :queue_private_receive, "any-guid", "{\"aes_key\": \"key\", \"encrypted_magic_envelope\": \"env\"}" ).and_return(true) post :private, diff --git a/spec/lib/diaspora_federation/federation/receiver_spec.rb b/spec/lib/diaspora_federation/federation/receiver_spec.rb index b5b2d33..5df17c0 100644 --- a/spec/lib/diaspora_federation/federation/receiver_spec.rb +++ b/spec/lib/diaspora_federation/federation/receiver_spec.rb @@ -23,21 +23,6 @@ module DiasporaFederation described_class.receive_public(data) end - it "parses the entity with legacy slap receiver" do - expect_callback(:fetch_public_key, post.author).and_return(sender_key) - - 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) - expect(entity.author).to eq(post.author) - expect(entity.text).to eq(post.text) - expect(entity.public).to eq("true") - end - - described_class.receive_public(data, true) - end - it "redirects exceptions from the receiver" do expect { described_class.receive_public("") @@ -64,21 +49,6 @@ module DiasporaFederation described_class.receive_private(data, recipient_key, 1234) end - it "parses the entity with legacy slap receiver" do - expect_callback(:fetch_public_key, post.author).and_return(sender_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) - expect(entity.author).to eq(post.author) - expect(entity.text).to eq(post.text) - expect(entity.public).to eq("false") - end - - described_class.receive_private(data, recipient_key, 1234, true) - end - it "raises when recipient private key is not available" do magic_env = Salmon::MagicEnvelope.new(post, post.author).envelop(sender_key) data = Salmon::EncryptedMagicEnvelope.encrypt(magic_env, recipient_key.public_key) diff --git a/spec/lib/diaspora_federation/salmon/encrypted_slap_spec.rb b/spec/lib/diaspora_federation/salmon/encrypted_slap_spec.rb deleted file mode 100644 index 348dcfb..0000000 --- a/spec/lib/diaspora_federation/salmon/encrypted_slap_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -module DiasporaFederation - describe Salmon::EncryptedSlap do - let(:sender) { "user_test@diaspora.example.tld" } - 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) { generate_legacy_encrypted_salmon_slap(payload, sender, privkey, recipient_key.public_key) } - - describe ".from_xml" do - context "sanity" do - it "accepts correct params" do - expect_callback(:fetch_public_key, sender).and_return(privkey.public_key) - - expect { - Salmon::EncryptedSlap.from_xml(slap_xml, recipient_key) - }.not_to raise_error - end - - it "raises an error when the params have a wrong type" do - [1234, false, :symbol, payload, privkey].each do |val| - expect { - Salmon::EncryptedSlap.from_xml(val, val) - }.to raise_error ArgumentError - end - end - - it "verifies the existence of 'encrypted_header'" do - faulty_xml = <<~XML - - - XML - expect { - Salmon::EncryptedSlap.from_xml(faulty_xml, recipient_key) - }.to raise_error Salmon::MissingHeader - end - - it "verifies the existence of a magic envelope" do - faulty_xml = <<~XML - - - - XML - expect(Salmon::EncryptedSlap).to receive(:header_data).and_return(aes_key: "", iv: "", author_id: "") - expect { - Salmon::EncryptedSlap.from_xml(faulty_xml, recipient_key) - }.to raise_error Salmon::MissingMagicEnvelope - end - end - - context "generated instance" do - it_behaves_like "a MagicEnvelope instance" do - subject { Salmon::EncryptedSlap.from_xml(slap_xml, recipient_key) } - end - end - end - end -end diff --git a/spec/lib/diaspora_federation/salmon/slap_spec.rb b/spec/lib/diaspora_federation/salmon/slap_spec.rb deleted file mode 100644 index a79729d..0000000 --- a/spec/lib/diaspora_federation/salmon/slap_spec.rb +++ /dev/null @@ -1,60 +0,0 @@ -# frozen_string_literal: true - -module DiasporaFederation - describe Salmon::Slap do - 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) { generate_legacy_salmon_slap(payload, sender, privkey) } - - describe ".from_xml" do - context "sanity" do - it "accepts salmon xml as param" do - expect_callback(:fetch_public_key, sender).and_return(privkey.public_key) - - expect { - Salmon::Slap.from_xml(slap_xml) - }.not_to raise_error - end - - it "raises an error when the param has a wrong type" do - [1234, false, :symbol, payload, privkey].each do |val| - expect { - Salmon::Slap.from_xml(val) - }.to raise_error ArgumentError - end - end - - it "verifies the existence of an author_id" do - faulty_xml = <<~XML - -
- - XML - expect { - Salmon::Slap.from_xml(faulty_xml) - }.to raise_error Salmon::MissingAuthor - end - - it "verifies the existence of a magic envelope" do - faulty_xml = <<~XML - -
- #{sender} -
-
- XML - expect { - Salmon::Slap.from_xml(faulty_xml) - }.to raise_error Salmon::MissingMagicEnvelope - end - end - - context "generated instance" do - it_behaves_like "a MagicEnvelope instance" do - subject { Salmon::Slap.from_xml(slap_xml) } - end - end - end - end -end