diff --git a/config/initializers/load_libraries.rb b/config/initializers/load_libraries.rb index 86d701d33d8827b6be113648b6469a92548e0113..164f3b83901ef6af17e75830627a2173d0afcd68 100644 --- a/config/initializers/load_libraries.rb +++ b/config/initializers/load_libraries.rb @@ -18,6 +18,5 @@ require 'hydra_wrapper' require 'postzord' require 'publisher' require 'pubsubhubbub' -require 'salmon' require 'stream' require 'account_deleter' diff --git a/lib/salmon.rb b/lib/salmon.rb deleted file mode 100644 index 747190d6004890e692a353a04a9f787862e7ad77..0000000000000000000000000000000000000000 --- a/lib/salmon.rb +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2010-2011, Diaspora Inc. This file is -# licensed under the Affero General Public License version 3 or later. See -# the COPYRIGHT file. - -# Add URL safe Base64 support -module Base64 - module_function - # Returns the Base64-encoded version of +bin+. - # This method complies with ``Base 64 Encoding with URL and Filename Safe - # Alphabet'' in RFC 4648. - # The alphabet uses '-' instead of '+' and '_' instead of '/'. - def urlsafe_encode64(bin) - self.strict_encode64(bin).tr("+/", "-_") - end - - # Returns the Base64-decoded version of +str+. - # This method complies with ``Base 64 Encoding with URL and Filename Safe - # Alphabet'' in RFC 4648. - # The alphabet uses '-' instead of '+' and '_' instead of '/'. - def urlsafe_decode64(str) - self.decode64(str.tr("-_", "+/")) - end -end - -# Verify documents secured with Magic Signatures -module Salmon - require "salmon/slap" - require "salmon/encrypted_slap" - require "salmon/magic_sig_envelope" -end diff --git a/lib/salmon/encrypted_slap.rb b/lib/salmon/encrypted_slap.rb deleted file mode 100644 index 64c16788381ffe29b0b20a3c8ba9694e6b73dfda..0000000000000000000000000000000000000000 --- a/lib/salmon/encrypted_slap.rb +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright (c) 2010-2011, Diaspora Inc. This file is -# licensed under the Affero General Public License version 3 or later. See -# the COPYRIGHT file. - -module Salmon - class EncryptedSlap < Slap - include Diaspora::Logging - - # Construct an encrypted header - # @return [String] Header XML - def header(person) - <<XML - <encrypted_header> - #{person.encrypt(plaintext_header)} - </encrypted_header> -XML - end - - def plaintext_header - header =<<HEADER -<decrypted_header> - <iv>#{iv}</iv> - <aes_key>#{aes_key}</aes_key> - <author_id>#{@author.diaspora_handle}</author_id> -</decrypted_header> -HEADER - end - - # @return [String, Boolean] False if RSAError; XML if no error - def xml_for(person) - begin - super - rescue OpenSSL::PKey::RSAError => e - logger.error "event=invalid_rsa_key identifier=#{person.diaspora_handle}" - false - end - end - - # Takes in a doc of the header and sets the author id - # returns an empty hash - # @return [Hash] - def process_header(doc) - self.author_id = doc.search('author_id').text - self.aes_key = doc.search('aes_key').text - self.iv = doc.search('iv').text - end - - # Decrypts an encrypted magic sig envelope - # @param key_hash [Hash] Contains 'key' (aes) and 'iv' values - # @param user [User] - def parse_data(user) - user.aes_decrypt(super, {'key' => self.aes_key, 'iv' => self.iv}) - end - - # Decrypts and parses out the salmon header - # @return [Nokogiri::Doc] - def salmon_header(doc, user) - header = user.decrypt(doc.search('encrypted_header').text) - Nokogiri::XML(header) - end - - # Encrypt the magic sig - # @return [String] - def self.payload(activity, user, aes_key_hash) - user.person.aes_encrypt(activity, aes_key_hash) - end - end -end diff --git a/lib/salmon/magic_sig_envelope.rb b/lib/salmon/magic_sig_envelope.rb deleted file mode 100644 index 44527513c13929439d1c311f58c3f1695688bc29..0000000000000000000000000000000000000000 --- a/lib/salmon/magic_sig_envelope.rb +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright (c) 2010-2011, Diaspora Inc. This file is -# licensed under the Affero General Public License version 3 or later. See -# the COPYRIGHT file. - -module Salmon - class MagicSigEnvelope - - attr_accessor :data, :data_type, :encoding, :alg, :sig, :author - - # @return [MagicSigEnvelope] - def self.parse(doc) - env = self.new - ns = {'me'=>'http://salmon-protocol.org/ns/magic-env'} - env.encoding = doc.search('//me:env/me:encoding', ns).text.strip - - if env.encoding != 'base64url' - raise ArgumentError, "Magic Signature data must be encoded with base64url, was #{env.encoding}" - end - - env.data = doc.search('//me:env/me:data', ns).text - env.alg = doc.search('//me:env/me:alg', ns).text.strip - - unless 'RSA-SHA256' == env.alg - raise ArgumentError, "Magic Signature data must be signed with RSA-SHA256, was #{env.alg}" - end - - env.sig = doc.search('//me:env/me:sig', ns).text - env.data_type = doc.search('//me:env/me:data', ns).first['type'].strip - - env - end - - # @return [MagicSigEnvelope] - def self.create(user, activity) - env = MagicSigEnvelope.new - env.author = user.person - env.data = Base64.urlsafe_encode64(activity) - env.data_type = env.get_data_type - env.encoding = env.get_encoding - env.alg = env.get_alg - - #TODO: WHY DO WE DOUBLE ENCODE - env.sig = Base64.urlsafe_encode64( - user.encryption_key.sign OpenSSL::Digest::SHA256.new, env.signable_string ) - - env - end - - # @return [String] - def signable_string - [@data, Base64.urlsafe_encode64(@data_type),Base64.urlsafe_encode64(@encoding), Base64.urlsafe_encode64(@alg)].join(".") - end - - # @return [String] - def to_xml - <<ENTRY -<me:env> - <me:data type='#{@data_type}'>#{@data}</me:data> - <me:encoding>#{@encoding}</me:encoding> - <me:alg>#{@alg}</me:alg> - <me:sig>#{@sig}</me:sig> - </me:env> -ENTRY - end - - # @return [String] - def get_encoding - 'base64url' - end - - # @return [String] - def get_data_type - 'application/xml' - end - - # @return [String] - def get_alg - 'RSA-SHA256' - end - end -end diff --git a/lib/salmon/slap.rb b/lib/salmon/slap.rb deleted file mode 100644 index 61ea1499638e4cc88e8680f5081d0e2ebb7445a2..0000000000000000000000000000000000000000 --- a/lib/salmon/slap.rb +++ /dev/null @@ -1,168 +0,0 @@ -# Copyright (c) 2010-2011, Diaspora Inc. This file is -# licensed under the Affero General Public License version 3 or later. See -# the COPYRIGHT file. - -module Salmon - class Slap - attr_accessor :magic_sig, :author, :author_id, :parsed_data - attr_accessor :aes_key, :iv - - delegate :sig, :data_type, :to => :magic_sig - - # @param user [User] - # @param activity [String] A decoded string - # @return [Slap] - def self.create_by_user_and_activity(user, activity) - salmon = self.new - salmon.author = user.person - aes_key_hash = user.person.gen_aes_key - - #additional headers - salmon.aes_key = aes_key_hash['key'] - salmon.iv = aes_key_hash['iv'] - - salmon.magic_sig = MagicSigEnvelope.create(user, self.payload(activity, user, aes_key_hash)) - salmon - end - - def self.from_xml(xml, receiving_user=nil) - slap = self.new - doc = Nokogiri::XML(xml) - - root_doc = doc.search('diaspora') - - ### Header ## - header_doc = slap.salmon_header(doc, receiving_user) - slap.process_header(header_doc) - - ### Envelope ## - slap.magic_sig = MagicSigEnvelope.parse(root_doc) - - slap.parsed_data = slap.parse_data(receiving_user) - - slap - end - - # @return [String] - def self.payload(activity, user=nil, aes_key_hash=nil) - activity - end - - # Takes in a doc of the header and sets the author id - # returns an empty hash - # @return [String] Author id - def process_header(doc) - self.author_id = doc.search('author_id').text - end - - # @return [String] - def parse_data(user=nil) - Slap.decode64url(self.magic_sig.data) - end - - # @return [Nokogiri::Doc] - def salmon_header(doc, user=nil) - doc.search('header') - end - - # @return [String] The constructed salmon, given a person - # note this memoizes the xml, as for every user for unsigned salmon will be the same - def xml_for(person) - @xml =<<ENTRY - <?xml version='1.0' encoding='UTF-8'?> - <diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env"> - #{header(person)} - #{@magic_sig.to_xml} - </diaspora> -ENTRY - end - - # Wraps plaintext header in <header></header> tags - # @return [String] Header XML - def header(person) - "<header>#{plaintext_header}</header>" - end - - # Generate a plaintext salmon header (unencrypted), sans <header></header> tags - # @return [String] Header XML (sans <header></header> tags) - def plaintext_header - header =<<HEADER - <author_id>#{@author.diaspora_handle}</author_id> -HEADER - end - - # @return [Person] Author of the salmon object - def author - if @author.nil? - @author ||= Person.by_account_identifier @author_id - raise "did you remember to async webfinger?" if @author.nil? - end - @author - end - - # Decode URL-safe-Base64. This implements - def self.decode64url(str) - # remove whitespace - sans_whitespace = str.gsub(/\s/, '') - # pad to a multiple of 4 - string = sans_whitespace + '=' * ((4 - sans_whitespace.size) % 4) - # convert to standard Base64 - # string = padded.tr('-','+').tr('_','/') - - # Base64.decode64(string) - Base64.urlsafe_decode64 string - end - - # Check whether this envelope's signature can be verified with the - # provided OpenSSL::PKey::RSA public_key. - # Example: - # - # env.verified_for_key? OpenSSL::PKey::RSA.new(File.open('public_key.pem')) - # # -> true - def verified_for_key?(public_key) - signature = Base64.urlsafe_decode64(self.magic_sig.sig) - signed_data = self.magic_sig.signable_string# Base64.urlsafe_decode64(self.magic_sig.signable_string) - - public_key.verify(OpenSSL::Digest::SHA256.new, signature, signed_data ) - end - - # Decode a string containing URL safe Base64 into an integer - # Example: - # - # MagicSig.b64_to_n('AQAB') - # # -> 645537 - def self.b64_to_n(str) - packed = decode64url(str) - packed.unpack('B*')[0].to_i(2) - end - - # Parse a string containing a magic-public-key into an OpenSSL::PKey::RSA key. - # Example: - # - # key = MagicSig.parse_key('RSA.mVgY8RN6URBTstndvmUUPb4UZTdwvwmddSKE5z_jvKUEK6yk1u3rrC9yN8k6FilGj9K0eeUPe2hf4Pj-5CmHww.AQAB') - # key.n - # # -> 8031283789075196565022891546563591368344944062154100509645398892293433370859891943306439907454883747534493461257620351548796452092307094036643522661681091 - # key.e - # # -> 65537 - def self.parse_key(str) - n,e = str.match(/^RSA.([^.]*).([^.]*)$/)[1..2] - build_key(b64_to_n(n),b64_to_n(e)) - end - - # Take two integers e, n and create a new OpenSSL::PKey::RSA key with them - # Example: - # - # n = 9487834027867356975347184933768917275269369900665861930617802608089634337052392076689226301419587057117740995382286148368168197915234368486155306558161867 - # e = 65537 - # key = MagicSig.build_key(n,e) - # key.public_encrypt(...) # for sending to strangers - # key.public_decrypt(...) # very rarely used - # key.verify(...) # for verifying signatures - def self.build_key(n,e) - key = OpenSSL::PKey::RSA.new - key.n = n - key.e = e - key - end - end -end diff --git a/spec/lib/salmon/encrypted_slap_spec.rb b/spec/lib/salmon/encrypted_slap_spec.rb deleted file mode 100644 index ba297edcfe03dd5eb28e5a36c484776f37b50985..0000000000000000000000000000000000000000 --- a/spec/lib/salmon/encrypted_slap_spec.rb +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright (c) 2010-2011, Diaspora Inc. This file is -# licensed under the Affero General Public License version 3 or later. See -# the COPYRIGHT file. - -require 'spec_helper' - -describe Salmon::EncryptedSlap do - before do - @post = alice.post(:status_message, :text => "hi", :to => alice.aspects.create(:name => "abcd").id) - @created_salmon = Salmon::EncryptedSlap.create_by_user_and_activity(alice, @post.to_diaspora_xml) - end - - describe '#create' do - it 'makes the data in the signature encrypted with that key' do - key_hash = {'key' => @created_salmon.aes_key, 'iv' => @created_salmon.iv} - decoded_string = Salmon::EncryptedSlap.decode64url(@created_salmon.magic_sig.data) - expect(alice.aes_decrypt(decoded_string, key_hash)).to eq(@post.to_diaspora_xml) - end - - it 'sets aes and iv key' do - expect(@created_salmon.aes_key).not_to be_nil - expect(@created_salmon.iv).not_to be_nil - end - end - - describe "#process_header" do - before do - @new_slap = Salmon::EncryptedSlap.new - @new_slap.process_header(Nokogiri::XML(@created_salmon.plaintext_header)) - end - - it 'sets the author id' do - expect(@new_slap.author_id).to eq(alice.diaspora_handle) - end - - it 'sets the aes_key' do - expect(@new_slap.aes_key).to eq(@created_salmon.aes_key) - end - - it 'sets the aes_key' do - expect(@new_slap.iv).to eq(@created_salmon.iv) - end - end - - context 'marshalling' do - let(:xml) {@created_salmon.xml_for(eve.person)} - let(:parsed_salmon) { Salmon::EncryptedSlap.from_xml(xml, alice)} - - it 'should parse out the aes key' do - expect(parsed_salmon.aes_key).to eq(@created_salmon.aes_key) - end - - it 'should parse out the iv' do - expect(parsed_salmon.iv).to eq(@created_salmon.iv) - end - - it 'contains the original data' do - expect(parsed_salmon.parsed_data).to eq(@post.to_diaspora_xml) - end - end - - describe '#xml_for' do - before do - @xml = @created_salmon.xml_for eve.person - end - - it 'has a encrypted header field' do - doc = Nokogiri::XML(@xml) - expect(doc.find("encrypted_header")).not_to be_blank - end - - context "encrypted header" do - before do - doc = Nokogiri::XML(@xml) - decrypted_header = eve.decrypt(doc.search('encrypted_header').text) - @dh_doc = Nokogiri::XML(decrypted_header) - end - - it 'contains the aes key' do - expect(@dh_doc.search('aes_key').map(&:text)).to eq([@created_salmon.aes_key]) - end - - it 'contains the initialization vector' do - expect(@dh_doc.search('iv').map(&:text)).to eq([@created_salmon.iv]) - end - - it 'contains the author id' do - expect(@dh_doc.search('author_id').map(&:text)).to eq([alice.diaspora_handle]) - end - end - end -end - diff --git a/spec/lib/salmon/magic_sig_envelope_spec.rb b/spec/lib/salmon/magic_sig_envelope_spec.rb deleted file mode 100644 index 1c1236a58a3d6d40d03efe40e56219cbbebb41bc..0000000000000000000000000000000000000000 --- a/spec/lib/salmon/magic_sig_envelope_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'spec_helper' - -describe Salmon::MagicSigEnvelope do - -end diff --git a/spec/lib/salmon/slap_spec.rb b/spec/lib/salmon/slap_spec.rb deleted file mode 100644 index d3a6851af82ccf0927c81296c699be14d149822f..0000000000000000000000000000000000000000 --- a/spec/lib/salmon/slap_spec.rb +++ /dev/null @@ -1,110 +0,0 @@ -require 'spec_helper' - -describe Salmon::Slap do - before do - @post = alice.post(:status_message, :text => "hi", :to => alice.aspects.create(:name => "abcd").id) - @created_salmon = Salmon::Slap.create_by_user_and_activity(alice, @post.to_diaspora_xml) - end - - describe '#create' do - it 'has data in the magic envelope' do - expect(@created_salmon.magic_sig.data).not_to be nil - end - - it 'has no parsed_data' do - expect(@created_salmon.parsed_data).to be nil - end - - end - - it 'works' do - salmon_string = @created_salmon.xml_for(nil) - salmon = Salmon::Slap.from_xml(salmon_string) - expect(salmon.author).to eq(alice.person) - expect(salmon.parsed_data).to eq(@post.to_diaspora_xml) - end - - describe '#from_xml' do - it 'procsses the header' do - expect_any_instance_of(Salmon::Slap).to receive(:process_header) - Salmon::Slap.from_xml(@created_salmon.xml_for(eve.person)) - end - end - - describe "#process_header" do - it 'sets the author id' do - slap = Salmon::Slap.new - slap.process_header(Nokogiri::XML(@created_salmon.plaintext_header)) - expect(slap.author_id).to eq(alice.diaspora_handle) - end - end - - describe '#author' do - let(:xml) {@created_salmon.xml_for(eve.person)} - let(:parsed_salmon) { Salmon::Slap.from_xml(xml, alice)} - - it 'should reference a local author' do - expect(parsed_salmon.author).to eq(alice.person) - end - - it 'should fail if no author is found' do - parsed_salmon.author_id = 'tom@tom.joindiaspora.com' - expect { - parsed_salmon.author.public_key - }.to raise_error "did you remember to async webfinger?" - end - end - - context 'marshaling' do - let(:xml) {@created_salmon.xml_for(eve.person)} - let(:parsed_salmon) { Salmon::Slap.from_xml(xml)} - - it 'should parse out the authors diaspora_handle' do - expect(parsed_salmon.author_id).to eq(alice.person.diaspora_handle) - end - - it 'verifies the signature for the sender' do - expect(parsed_salmon.verified_for_key?(alice.public_key)).to be true - end - - it 'verifies the signature for the sender' do - expect(parsed_salmon.verified_for_key?(FactoryGirl.create(:person).public_key)).to be false - end - - it 'contains the original data' do - expect(parsed_salmon.parsed_data).to eq(@post.to_diaspora_xml) - end - end - - describe "#xml_for" do - before do - @xml = @created_salmon.xml_for(eve.person) - end - - it "has diaspora as the root" do - doc = Nokogiri::XML(@xml) - expect(doc.root.name).to eq("diaspora") - end - - it "it has the descrypted header" do - doc = Nokogiri::XML(@xml) - expect(doc.search("header")).not_to be_blank - end - - context "header" do - - it "it has author_id node " do - doc = Nokogiri::XML(@xml) - search = doc.search("header").search("author_id") - expect(search.map(&:text)).to eq([alice.diaspora_handle]) - end - - end - - it "it has the magic envelope " do - doc = Nokogiri::XML(@xml) - expect(doc.find("/me:env")).not_to be_blank - end - end -end -