Moving to salmon

This commit is contained in:
Raphael 2010-09-08 18:29:39 -07:00
parent 061f9de668
commit 4fd0853e71
9 changed files with 204 additions and 43 deletions

View file

@ -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?

View file

@ -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

View file

@ -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
View 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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