Moving to salmon
This commit is contained in:
parent
061f9de668
commit
4fd0853e71
9 changed files with 204 additions and 43 deletions
|
|
@ -1,6 +1,7 @@
|
|||
class Person
|
||||
include MongoMapper::Document
|
||||
include ROXML
|
||||
include Encryptor::Public
|
||||
|
||||
xml_accessor :_id
|
||||
xml_accessor :email
|
||||
|
|
@ -37,6 +38,13 @@ class Person
|
|||
def real_name
|
||||
"#{profile.first_name.to_s} #{profile.last_name.to_s}"
|
||||
end
|
||||
def owns?(post)
|
||||
self.id == post.person.id
|
||||
end
|
||||
|
||||
def receive_url
|
||||
"#{self.url}receive/users/#{self.id}/"
|
||||
end
|
||||
|
||||
def encryption_key
|
||||
OpenSSL::PKey::RSA.new( serialized_key )
|
||||
|
|
@ -51,6 +59,10 @@ class Person
|
|||
Base64.encode64 OpenSSL::Digest::SHA256.new(self.exported_key).to_s
|
||||
end
|
||||
|
||||
def public_key
|
||||
encryption_key.public_key
|
||||
end
|
||||
|
||||
def exported_key
|
||||
encryption_key.public_key.export
|
||||
end
|
||||
|
|
@ -60,16 +72,34 @@ class Person
|
|||
@serialized_key = new_key
|
||||
end
|
||||
|
||||
def owns?(post)
|
||||
self.id == post.person.id
|
||||
end
|
||||
|
||||
def receive_url
|
||||
"#{self.url}receive/users/#{self.id}/"
|
||||
end
|
||||
|
||||
def self.by_webfinger( identifier )
|
||||
Person.first(:email => identifier.gsub('acct:', ''))
|
||||
local_person = Person.first(:email => identifier.gsub('acct:', ''))
|
||||
if local_person
|
||||
local_person
|
||||
elsif !identifier.include?("localhost")
|
||||
begin
|
||||
f = Redfinger.finger(identifier)
|
||||
rescue SocketError => e
|
||||
raise "Diaspora server for #{identifier} not found" if e.message =~ /Name or service not known/
|
||||
end
|
||||
#raise "No diaspora user found at #{identifier}"
|
||||
Person.from_webfinger_profile(identifier, f )
|
||||
end
|
||||
end
|
||||
|
||||
def self.from_webfinger_profile( identifier, profile)
|
||||
public_key = profile.links.select{|x| x.rel == 'diaspora-public-key'}.first.href
|
||||
new_person = Person.new
|
||||
new_person.exported_key = Base64.decode64 public_key
|
||||
new_person.email = identifier
|
||||
receive_url = profile.links.select{ |l| l.rel == 'http://joindiaspora.com/seed_location'}.first.href
|
||||
new_person.url = receive_url.split('receive').first
|
||||
new_person.profile = Profile.new(:first_name => "Anon", :last_name => "ymous")
|
||||
if new_person.save!
|
||||
new_person
|
||||
else
|
||||
cry
|
||||
end
|
||||
end
|
||||
|
||||
def remote?
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
require 'lib/diaspora/user/friending.rb'
|
||||
require 'lib/salmon/salmon'
|
||||
|
||||
class User
|
||||
include MongoMapper::Document
|
||||
include Diaspora::UserModules::Friending
|
||||
include Encryptor::Private
|
||||
|
||||
devise :database_authenticatable, :registerable,
|
||||
:recoverable, :rememberable, :trackable, :validatable
|
||||
key :username, :unique => true
|
||||
|
|
@ -128,6 +131,10 @@ class User
|
|||
post.push_to( target_people )
|
||||
end
|
||||
|
||||
def salmon( post, opts = {} )
|
||||
Salmon::SalmonSlap.create(self, post.encrypted_xml_for(opts[:to]))
|
||||
end
|
||||
|
||||
def visible_posts( opts = {} )
|
||||
if opts[:by_members_of]
|
||||
return raw_visible_posts if opts[:by_members_of] == :all
|
||||
|
|
@ -184,6 +191,13 @@ class User
|
|||
end
|
||||
|
||||
###### Receiving #######
|
||||
def receive_salmon xml
|
||||
salmon = Salmon::SalmonSlap.parse xml
|
||||
if salmon.verified_for_key?(salmon.author.public_key)
|
||||
self.receive(decrypt(salmon.data))
|
||||
end
|
||||
end
|
||||
|
||||
def receive xml
|
||||
object = Diaspora::Parser.from_xml(xml)
|
||||
Rails.logger.debug("Receiving object for #{self.real_name}:\n#{object.inspect}")
|
||||
|
|
@ -316,11 +330,4 @@ class User
|
|||
}
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
|
||||
def self.generate_key
|
||||
OpenSSL::PKey::RSA::generate 1024
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -28,5 +28,10 @@
|
|||
Rails.logger.debug("Signing #{signable_string}")
|
||||
Base64.encode64(key.sign "SHA", signable_string)
|
||||
end
|
||||
|
||||
def encrypted_xml_for(person)
|
||||
person.encrypt self.to_diaspora_xml
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
|
|||
61
lib/encryptor.rb
Normal file
61
lib/encryptor.rb
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
module Encryptor
|
||||
module Public
|
||||
def encrypt cleartext
|
||||
aes_key = gen_aes_key
|
||||
ciphertext = aes_encrypt(cleartext, aes_key)
|
||||
encrypted_key = encrypt_aes_key aes_key
|
||||
cipher_hash = {:aes_key => encrypted_key, :ciphertext => ciphertext}
|
||||
Base64.encode64( cipher_hash.to_json )
|
||||
end
|
||||
|
||||
def gen_aes_key
|
||||
cipher = OpenSSL::Cipher.new('AES-256-CBC')
|
||||
key = cipher.random_key
|
||||
iv = cipher.random_iv
|
||||
{'key' => Base64.encode64(key), 'iv' => Base64.encode64(iv)}
|
||||
end
|
||||
|
||||
def aes_encrypt(txt, key)
|
||||
cipher = OpenSSL::Cipher.new('AES-256-CBC')
|
||||
cipher.encrypt
|
||||
cipher.key = Base64.decode64 key['key']
|
||||
cipher.iv = Base64.decode64 key['iv']
|
||||
ciphertext = ''
|
||||
ciphertext << cipher.update(txt)
|
||||
ciphertext << cipher.final
|
||||
Base64.encode64 ciphertext
|
||||
end
|
||||
|
||||
def encrypt_aes_key key
|
||||
Base64.encode64 encryption_key.public_encrypt( key.to_json )
|
||||
end
|
||||
end
|
||||
|
||||
module Private
|
||||
def decrypt cipher_json
|
||||
json = JSON.parse(Base64.decode64 cipher_json)
|
||||
aes_key = get_aes_key json['aes_key']
|
||||
aes_decrypt(json['ciphertext'], aes_key)
|
||||
end
|
||||
|
||||
def get_aes_key encrypted_key
|
||||
clear_key = encryption_key.private_decrypt( Base64.decode64 encrypted_key )
|
||||
JSON::parse(clear_key)
|
||||
end
|
||||
|
||||
def aes_decrypt(ciphertext, key)
|
||||
cipher = OpenSSL::Cipher.new('AES-256-CBC')
|
||||
cipher.decrypt
|
||||
cipher.key = Base64.decode64 key['key']
|
||||
cipher.iv = Base64.decode64 key['iv']
|
||||
txt = ''
|
||||
txt << cipher.update(Base64.decode64 ciphertext)
|
||||
txt << cipher.final
|
||||
txt
|
||||
end
|
||||
|
||||
def self.generate_key
|
||||
OpenSSL::PKey::RSA::generate 4096
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -36,7 +36,7 @@ end
|
|||
# Verify documents secured with Magic Signatures
|
||||
module Salmon
|
||||
class SalmonSlap
|
||||
attr_accessor :magic_sig, :user, :data, :data_type, :sig
|
||||
attr_accessor :magic_sig, :author, :author_email, :data, :data_type, :sig
|
||||
def self.parse(xml)
|
||||
slap = self.new
|
||||
doc = Nokogiri::XML(xml)
|
||||
|
|
@ -57,15 +57,15 @@ module Salmon
|
|||
|
||||
raise ArgumentError, "Magic Signature data must be signed with RSA-SHA256, was #{slap.magic_sig.alg}" unless 'RSA-SHA256' == slap.magic_sig.alg
|
||||
|
||||
uri = doc.search('uri').text
|
||||
slap.author_email = uri.split("acct:").last
|
||||
slap
|
||||
end
|
||||
|
||||
|
||||
|
||||
def self.create(user, activity)
|
||||
salmon = self.new
|
||||
salmon.user = user
|
||||
salmon.magic_sig = MagicSigEnvelope.create(user, activity)
|
||||
salmon.author = user.person
|
||||
salmon.magic_sig = MagicSigEnvelope.create(user , activity)
|
||||
salmon
|
||||
end
|
||||
|
||||
|
|
@ -74,8 +74,8 @@ module Salmon
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<entry xmlns='http://www.w3.org/2005/Atom'>
|
||||
<author>
|
||||
<name>#{@user.real_name}</name>
|
||||
<uri>acct:#{@user.email}</uri>
|
||||
<name>#{@author.real_name}</name>
|
||||
<uri>acct:#{@author.email}</uri>
|
||||
</author>
|
||||
#{@magic_sig.to_xml}
|
||||
</entry>
|
||||
|
|
@ -83,6 +83,14 @@ ENTRY
|
|||
|
||||
end
|
||||
|
||||
def author
|
||||
if @author
|
||||
@author
|
||||
else
|
||||
Person.by_webfinger @author_email
|
||||
end
|
||||
end
|
||||
|
||||
# Decode URL-safe-Base64. This implements
|
||||
def self.decode64url(str)
|
||||
# remove whitespace
|
||||
|
|
@ -161,7 +169,7 @@ ENTRY
|
|||
end
|
||||
|
||||
class MagicSigEnvelope
|
||||
attr_accessor :data, :data_type, :encoding, :alg, :sig, :user
|
||||
attr_accessor :data, :data_type, :encoding, :alg, :sig, :author
|
||||
def self.parse(doc)
|
||||
env = self.new
|
||||
ns = {'me'=>'http://salmon-protocol.org/ns/magic-env'}
|
||||
|
|
@ -175,7 +183,7 @@ ENTRY
|
|||
|
||||
def self.create(user, activity)
|
||||
env = MagicSigEnvelope.new
|
||||
env.user = user
|
||||
env.author = user.person
|
||||
env.data = Base64.urlsafe_encode64(activity)
|
||||
env.data_type = env.get_data_type
|
||||
env.encoding = env.get_encoding
|
||||
|
|
|
|||
|
|
@ -9,33 +9,50 @@ include Salmon
|
|||
|
||||
|
||||
describe Salmon do
|
||||
it 'should verify the signature on a roundtrip' do
|
||||
before do
|
||||
|
||||
@user = Factory.create :user
|
||||
@post = @user.post :status_message, :message => "hi", :to => @user.group(:name => "sdg").id
|
||||
x = Salmon::SalmonSlap.create(@user, @post.to_diaspora_xml)
|
||||
|
||||
z = Salmon::SalmonSlap.parse x.to_xml
|
||||
@sent_salmon = Salmon::SalmonSlap.create(@user, @post.to_diaspora_xml)
|
||||
@parsed_salmon = Salmon::SalmonSlap.parse @sent_salmon.to_xml
|
||||
end
|
||||
|
||||
x.magic_sig.data.should == z.magic_sig.data
|
||||
it 'should verify the signature on a roundtrip' do
|
||||
|
||||
x.magic_sig.sig.should == z.magic_sig.sig
|
||||
x.magic_sig.signable_string.should == z.magic_sig.signable_string
|
||||
@sent_salmon.magic_sig.data.should == @parsed_salmon.magic_sig.data
|
||||
|
||||
@sent_salmon.magic_sig.sig.should == @parsed_salmon.magic_sig.sig
|
||||
@sent_salmon.magic_sig.signable_string.should == @parsed_salmon.magic_sig.signable_string
|
||||
|
||||
|
||||
x.verified_for_key?(OpenSSL::PKey::RSA.new(@user.exported_key)).should be true
|
||||
z.verified_for_key?(OpenSSL::PKey::RSA.new(@user.exported_key)).should be true
|
||||
@parsed_salmon.verified_for_key?(OpenSSL::PKey::RSA.new(@user.exported_key)).should be true
|
||||
@sent_salmon.verified_for_key?(OpenSSL::PKey::RSA.new(@user.exported_key)).should be true
|
||||
end
|
||||
|
||||
|
||||
it 'should return the data so it can be "received"' do
|
||||
@user = Factory.create :user
|
||||
@post = @user.post :status_message, :message => "hi", :to => @user.group(:name => "sdg").id
|
||||
x = Salmon::SalmonSlap.create(@user, @post.to_diaspora_xml)
|
||||
|
||||
z = Salmon::SalmonSlap.parse x.to_xml
|
||||
|
||||
xml = @post.to_diaspora_xml
|
||||
|
||||
z.data.should == xml
|
||||
@parsed_salmon.data.should == xml
|
||||
end
|
||||
|
||||
it 'should parse out the author email' do
|
||||
@parsed_salmon.author_email.should == @user.person.email
|
||||
end
|
||||
|
||||
it 'should reference a local author' do
|
||||
@parsed_salmon.author.should == @user.person
|
||||
end
|
||||
|
||||
it 'should reference a remote author' do
|
||||
@parsed_salmon.author_email = 'tom@tom.joindiaspora.com'
|
||||
@parsed_salmon.author.public_key.should_not be_nil
|
||||
end
|
||||
|
||||
it 'should fail to reference a nonexistent remote author' do
|
||||
@parsed_salmon.author_email = 'idsfug@difgubhpsduh.rgd'
|
||||
proc {@parsed_salmon.author.real_name}.should raise_error /not found/
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,10 +7,20 @@ describe Post do
|
|||
end
|
||||
|
||||
describe 'xml' do
|
||||
it 'should serialize to xml with its person' do
|
||||
message = Factory.create(:status_message, :person => @user.person)
|
||||
message.to_xml.to_s.include?(@user.person.email).should == true
|
||||
before do
|
||||
@message = Factory.create(:status_message, :person => @user.person)
|
||||
end
|
||||
|
||||
it 'should serialize to xml with its person' do
|
||||
@message.to_xml.to_s.include?(@user.person.email).should == true
|
||||
end
|
||||
|
||||
it 'should serialize to encrypted xml' do
|
||||
enc_xml = @message.encrypted_xml_for(@user.person)
|
||||
enc_xml.include?(@message.to_diaspora_xml).should be false
|
||||
@user.decrypt(enc_xml).include?(@message.to_diaspora_xml).should be true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'deletion' do
|
||||
|
|
|
|||
|
|
@ -165,4 +165,16 @@ describe User do
|
|||
@user3.visible_person_by_id(commenter_id).should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe 'salmon' do
|
||||
before do
|
||||
@post = @user.post :status_message, :message => "hello", :to => @group.id
|
||||
@salmon = @user.salmon( @post, :to => @user2.person )
|
||||
end
|
||||
|
||||
it 'should receive a salmon for a post' do
|
||||
@user2.receive_salmon( @salmon.to_xml )
|
||||
@user2.visible_post_ids.include?(@post.id).should be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -59,6 +59,17 @@ describe 'user encryption' do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'encryption' do
|
||||
before do
|
||||
@message = @user.post :status_message, :message => "hi", :to => @group.id
|
||||
end
|
||||
it 'should encrypt large messages' do
|
||||
ciphertext = @user.encrypt @message.to_diaspora_xml
|
||||
ciphertext.include?(@message.to_diaspora_xml).should be false
|
||||
@user.decrypt(ciphertext).include?(@message.to_diaspora_xml).should be true
|
||||
end
|
||||
end
|
||||
|
||||
describe 'signing and verifying' do
|
||||
|
||||
it 'should sign a message on create' do
|
||||
|
|
|
|||
Loading…
Reference in a new issue