diff --git a/lib/diaspora_federation/salmon/exceptions.rb b/lib/diaspora_federation/salmon/exceptions.rb index 349c7b2..9dd0f96 100644 --- a/lib/diaspora_federation/salmon/exceptions.rb +++ b/lib/diaspora_federation/salmon/exceptions.rb @@ -33,6 +33,10 @@ module DiasporaFederation class InvalidSignature < RuntimeError end + # Raised, if the parsed Magic Envelope specifies an unhandled data type. + class InvalidDataType < RuntimeError + end + # Raised, if the parsed Magic Envelope specifies an unhandled algorithm. class InvalidAlgorithm < RuntimeError end diff --git a/lib/diaspora_federation/salmon/magic_envelope.rb b/lib/diaspora_federation/salmon/magic_envelope.rb index dcd9822..be1979c 100644 --- a/lib/diaspora_federation/salmon/magic_envelope.rb +++ b/lib/diaspora_federation/salmon/magic_envelope.rb @@ -110,19 +110,20 @@ module DiasporaFederation # @raise [ArgumentError] if any of the arguments is of invalid type # @raise [InvalidEnvelope] if the envelope XML structure is malformed # @raise [InvalidSignature] if the signature can't be verified - # @raise [InvalidEncoding] if the data is wrongly encoded - # @raise [InvalidAlgorithm] if the algorithm used doesn't match + # @raise [InvalidDataType] if the data is missing or unsupported + # @raise [InvalidEncoding] if the data is wrongly encoded or encoding is missing + # @raise [InvalidAlgorithm] if the algorithm is missing or doesn't match def self.unenvelop(magic_env, sender=nil, cipher_params=nil) raise ArgumentError unless magic_env.instance_of?(Nokogiri::XML::Element) - raise InvalidEnvelope unless envelope_valid?(magic_env) + validate_envelope(magic_env) + validate_type(magic_env) + validate_encoding(magic_env) + validate_algorithm(magic_env) sender ||= sender(magic_env) raise InvalidSignature unless signature_valid?(magic_env, sender) - raise InvalidEncoding unless encoding_valid?(magic_env) - raise InvalidAlgorithm unless algorithm_valid?(magic_env) - data = read_and_decrypt_data(magic_env, cipher_params) logger.debug "unenvelop message from #{sender}:\n#{data}" @@ -165,11 +166,20 @@ module DiasporaFederation end # @param [Nokogiri::XML::Element] env magic envelope XML - private_class_method def self.envelope_valid?(env) - (env.instance_of?(Nokogiri::XML::Element) && - env.name == "env" && - !env.at_xpath("me:data").content.empty? && - !env.at_xpath("me:sig").content.empty?) + # @raise [InvalidEnvelope] if the envelope XML structure is malformed + private_class_method def self.validate_envelope(env) + raise InvalidEnvelope unless env.instance_of?(Nokogiri::XML::Element) && env.name == "env" + validate_element(env, "me:data") + validate_element(env, "me:sig") + end + + # @param [Nokogiri::XML::Element] env magic envelope XML + # @param [String] xpath the element to validate + # @raise [InvalidEnvelope] if the element is missing or empty + private_class_method def self.validate_element(env, xpath) + element = env.at_xpath(xpath) + raise InvalidEnvelope, "missing #{xpath}" unless element + raise InvalidEnvelope, "empty #{xpath}" if element.content.empty? end # @param [Nokogiri::XML::Element] env magic envelope XML @@ -207,15 +217,27 @@ module DiasporaFederation end # @param [Nokogiri::XML::Element] magic_env magic envelope XML - # @return [Boolean] - private_class_method def self.encoding_valid?(magic_env) - magic_env.at_xpath("me:encoding").content == ENCODING + # @raise [InvalidDataType] if the data is missing or unsupported + private_class_method def self.validate_type(magic_env) + type = magic_env.at_xpath("me:data")["type"] + raise InvalidDataType, "missing data type" if type.nil? + raise InvalidDataType, "invalid data type: #{type}" unless type == DATA_TYPE end # @param [Nokogiri::XML::Element] magic_env magic envelope XML - # @return [Boolean] - private_class_method def self.algorithm_valid?(magic_env) - magic_env.at_xpath("me:alg").content == ALGORITHM + # @raise [InvalidEncoding] if the data is wrongly encoded or encoding is missing + private_class_method def self.validate_encoding(magic_env) + enc = magic_env.at_xpath("me:encoding") + raise InvalidEncoding, "missing encoding" unless enc + raise InvalidEncoding, "invalid encoding: #{enc.content}" unless enc.content == ENCODING + end + + # @param [Nokogiri::XML::Element] magic_env magic envelope XML + # @raise [InvalidAlgorithm] if the algorithm is missing or doesn't match + private_class_method def self.validate_algorithm(magic_env) + alg = magic_env.at_xpath("me:alg") + raise InvalidAlgorithm, "missing algorithm" unless alg + raise InvalidAlgorithm, "invalid algorithm: #{alg.content}" unless alg.content == ALGORITHM end # @param [Nokogiri::XML::Element] magic_env magic envelope XML diff --git a/spec/lib/diaspora_federation/salmon/magic_envelope_spec.rb b/spec/lib/diaspora_federation/salmon/magic_envelope_spec.rb index 1d1c72e..b7b305d 100644 --- a/spec/lib/diaspora_federation/salmon/magic_envelope_spec.rb +++ b/spec/lib/diaspora_federation/salmon/magic_envelope_spec.rb @@ -110,11 +110,6 @@ module DiasporaFederation ).and_return(privkey.public_key) 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 - it "works with sane input" do expect { Salmon::MagicEnvelope.unenvelop(envelope.envelop(privkey), sender) @@ -135,6 +130,14 @@ module DiasporaFederation }.to raise_error Salmon::InvalidEnvelope end + it "raises if missing signature" do + bad_env = envelope.envelop(privkey) + bad_env.at_xpath("me:sig").remove + expect { + Salmon::MagicEnvelope.unenvelop(bad_env, sender) + }.to raise_error Salmon::InvalidEnvelope, "missing me:sig" + end + it "verifies the signature" do other_sender = FactoryGirl.generate(:diaspora_id) other_key = OpenSSL::PKey::RSA.generate(512) @@ -146,22 +149,60 @@ module DiasporaFederation }.to raise_error Salmon::InvalidSignature end + it "raises if missing data" do + bad_env = envelope.envelop(privkey) + bad_env.at_xpath("me:data").remove + expect { + Salmon::MagicEnvelope.unenvelop(bad_env, sender) + }.to raise_error Salmon::InvalidEnvelope, "missing me:data" + end + + it "raises if missing encoding" do + bad_env = envelope.envelop(privkey) + bad_env.at_xpath("me:encoding").remove + expect { + Salmon::MagicEnvelope.unenvelop(bad_env, sender) + }.to raise_error Salmon::InvalidEncoding, "missing encoding" + end + it "verifies the encoding" do bad_env = envelope.envelop(privkey) bad_env.at_xpath("me:encoding").content = "invalid_enc" - re_sign(bad_env, privkey) expect { Salmon::MagicEnvelope.unenvelop(bad_env, sender) - }.to raise_error Salmon::InvalidEncoding + }.to raise_error Salmon::InvalidEncoding, "invalid encoding: invalid_enc" + end + + it "raises if missing algorithm" do + bad_env = envelope.envelop(privkey) + bad_env.at_xpath("me:alg").remove + expect { + Salmon::MagicEnvelope.unenvelop(bad_env, sender) + }.to raise_error Salmon::InvalidAlgorithm, "missing algorithm" end it "verifies the algorithm" do bad_env = envelope.envelop(privkey) bad_env.at_xpath("me:alg").content = "invalid_alg" - re_sign(bad_env, privkey) expect { Salmon::MagicEnvelope.unenvelop(bad_env, sender) - }.to raise_error Salmon::InvalidAlgorithm + }.to raise_error Salmon::InvalidAlgorithm, "invalid algorithm: invalid_alg" + end + + it "raises if missing data type" do + bad_env = envelope.envelop(privkey) + bad_env.at_xpath("me:data").attributes["type"].remove + expect { + Salmon::MagicEnvelope.unenvelop(bad_env, sender) + }.to raise_error Salmon::InvalidDataType, "missing data type" + end + + it "verifies the data type" do + bad_env = envelope.envelop(privkey) + bad_env.at_xpath("me:data")["type"] = "invalid_type" + expect { + Salmon::MagicEnvelope.unenvelop(bad_env, sender) + }.to raise_error Salmon::InvalidDataType, "invalid data type: invalid_type" end end