Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
randysecrist committed Mar 3, 2013
2 parents fd9c971 + b1486ed commit af77c5b
Show file tree
Hide file tree
Showing 17 changed files with 231 additions and 121 deletions.
19 changes: 0 additions & 19 deletions lib/ripple-encryption/activation.rb

This file was deleted.

4 changes: 2 additions & 2 deletions lib/ripple-encryption/config.rb
Expand Up @@ -30,8 +30,8 @@ def activate


def validate_path(path) def validate_path(path)
if !File.exists? path if !File.exists? path
raise ConfigError, <<MISSINGFILE raise Ripple::Encryption::ConfigError, <<MISSINGFILE
The config file [usually "config/encryption.yml"] is missing or incorrect. You will The file "config/encryption.yml" is missing or incorrect. You will
need to create this file and populate it with a valid cipher, need to create this file and populate it with a valid cipher,
initialization vector and secret key. An example is provided in initialization vector and secret key. An example is provided in
"config/encryption.yml.example". "config/encryption.yml.example".
Expand Down
3 changes: 1 addition & 2 deletions lib/ripple-encryption/encrypted_json_document.rb
Expand Up @@ -6,8 +6,7 @@ class EncryptedJsonDocumentError < StandardError; end
# Interprets a encapsulation in JSON for encrypted Ripple documents. # Interprets a encapsulation in JSON for encrypted Ripple documents.
# #
# Example usage: # Example usage:
# Ripple::Encryption::JsonDocument.new(@config, @document).encrypt # Ripple::Encryption::JsonDocument.new(@document).encrypt
# Ripple::Encryption::EncryptedJsonDocument.new(@config, @document).decrypt
class EncryptedJsonDocument class EncryptedJsonDocument
# Creates an object that is prepared to decrypt its contents. # Creates an object that is prepared to decrypt its contents.
# @param [String] data json string that was stored in Riak # @param [String] data json string that was stored in Riak
Expand Down
117 changes: 117 additions & 0 deletions lib/ripple-encryption/encrypted_serializer.rb
@@ -0,0 +1,117 @@
module Ripple
module Encryption
# Implements the {Riak::Serializer} API for the purpose of
# encrypting/decrypting Ripple documents.
#
# Example usage:
# ::Riak::Serializers['application/x-json-encrypted'] = EncryptedSerializer.new(OpenSSL::Cipher.new("AES-256"))
# class MyDocument
# include Ripple::Document
# include Riak::Encryption
# end
#
# @see Encryption
class EncryptedSerializer
# @return [String] The Content-Type of the internal format,
# generally "application/json"
attr_accessor :content_type

# @return [OpenSSL::Cipher, OpenSSL::PKey::*] the cipher used to encrypt the object
attr_accessor :cipher

# Cipher-specific settings
# @see OpenSSL::Cipher
attr_accessor :key, :iv, :key_length, :padding

# Serialization Options
# @return [true, false] Is the encrypted text also base64 encoded?
attr_accessor :base64

# Creates a serializer using the provided cipher and internal
# content type. Be sure to set the {#key}, {#iv}, {#key_length},
# {#padding} as appropriate for the cipher before attempting
# (de-)serialization.
# @param [OpenSSL::Cipher] cipher the desired
# encryption/decryption algorithm
# @param [String] content_type the Content-Type of the
# unencrypted contents
def initialize(cipher, content_type='application/json', path)
@cipher, @content_type = cipher, content_type
@config = Ripple::Encryption::Config.new(path)
end

# Serializes and encrypts the Ruby object using the assigned
# cipher and Content-Type.
# @param [Object] object the Ruby object to serialize/encrypt
# @return [String] the serialized, encrypted form of the object
def dump(object)
JsonDocument.new(@config, object).encrypt
end

# Decrypts and deserializes the blob using the assigned cipher
# and Content-Type.
# @param [String] blob the original content from Riak
# @return [Object] the decrypted and deserialized object
def load(object)
# try the v1 way first
begin
internal = decrypt(object)
return ::Riak::Serializers.deserialize('application/json', internal)
# if that doesn't work, try the v2 way
rescue OpenSSL::Cipher::CipherError, MultiJson::DecodeError
return EncryptedJsonDocument.new(@config, object).decrypt
end
end

private

# generates a new iv each call unless a static (less secure)
# iv is used.
def encrypt(object)
old_version = '0.0.1'
result = ''
if cipher.respond_to?(:iv=) and @iv == nil
iv = OpenSSL::Random.random_bytes(cipher.iv_len)
cipher.iv = iv
result << old_version << iv
end

if cipher.respond_to?(:public_encrypt)
result << cipher.public_encrypt(object)
else
cipher_setup :encrypt
result << cipher.update(object) << cipher.final
cipher.reset
end
return result
end

def decrypt(cipher_text)
old_version = '0.0.1'

if cipher.respond_to?(:iv=) and @iv == nil
version = cipher_text.slice(0, old_version.length)
cipher.iv = cipher_text.slice(old_version.length, cipher.iv_len)
cipher_text = cipher_text.slice(old_version.length + cipher.iv_len, cipher_text.length)
end

if cipher.respond_to?(:private_decrypt)
cipher.private_decrypt(cipher_text)
else
cipher_setup :decrypt
result = cipher.update(cipher_text) << cipher.final
cipher.reset
result
end
end

def cipher_setup(mode)
cipher.send mode
cipher.key = key if key
cipher.iv = iv if iv
cipher.key_length = key_length if key_length
cipher.padding = padding if padding
end
end
end
end
86 changes: 72 additions & 14 deletions lib/ripple-encryption/encryption.rb
@@ -1,20 +1,78 @@
require 'openssl'
require 'ripple'

module Ripple module Ripple
# When mixed into a Ripple::Document class, this will encrypt the
# serialized form before it is stored in Riak. You must register
# a serializer that will perform the encryption.
# @see Serializer
module Encryption module Encryption
# Overrides the internal method to set the content-type to be
# encrypted. # When mixed into a Ripple::Document class, this will encrypt the
def robject # serialized form before it is stored in Riak. You must register
@robject ||= Riak::RObject.new(self.class.bucket, key).tap do |obj| # a serializer that will perform the encryption.
obj.content_type = 'application/x-json-encrypted' # @see EncryptedSerializer
module Encryption
extend ActiveSupport::Concern

@@is_activated = false

included do
@@encrypted_content_type = self.encrypted_content_type = 'application/x-json-encrypted'
end end

module ClassMethods
# @return [String] the content type to be used to indicate the
# proper encryption scheme. Defaults to 'application/x-json-encrypted'
attr_accessor :encrypted_content_type
end

# Overrides the internal method to set the content-type to be
# encrypted.
def update_robject
super
if @@is_activated
robject.content_type = @@encrypted_content_type
end
end

def self.activate(path)
encryptor = nil
unless Riak::Serializers['application/x-json-encrypted']
begin
config = YAML.load_file(path)[ENV['RACK_ENV']]
encryptor = Ripple::Encryption::EncryptedSerializer.new(OpenSSL::Cipher.new(config['cipher']), 'application/x-json-encrypted', path)
rescue Exception => e
handle_invalid_encryption_config(e.message, e.backtrace)
end
encryptor.key = config['key'] if config['key']
encryptor.iv = config['iv'] if config['iv']
Riak::Serializers['application/x-json-encrypted'] = encryptor
@@is_activated = true
end
encryptor
end

def self.activated
@@is_activated
end

end end
def update_robject
robject.key = key if robject.key != key
robject.content_type ||= 'application/x-json-encrypted'
robject.data = attributes_for_persistence
end
end end
end end

def handle_invalid_encryption_config(msg, trace)
puts <<eos
The file "config/encryption.yml" is missing or incorrect. You will
need to create this file and populate it with a valid cipher,
initialization vector and secret key.
An example is provided in "config/encryption.yml.example".
eos

puts "Error Message: " + msg
puts "Error Trace:"
trace.each do |line|
puts line
end

exit 1
end
16 changes: 5 additions & 11 deletions lib/ripple-encryption/encryptor.rb
@@ -1,7 +1,7 @@
module Ripple module Ripple
module Encryption module Encryption
# Generic error class for Encryptor # Generic error class for Encryptor
class EncryptorError < StandardError; end class EncryptorConfigError < StandardError; end


# Implements a simple object that can either encrypt or decrypt arbitrary data. # Implements a simple object that can either encrypt or decrypt arbitrary data.
# #
Expand All @@ -13,7 +13,10 @@ class Encryptor
# Creates an Encryptor that is prepared to encrypt/decrypt a blob. # Creates an Encryptor that is prepared to encrypt/decrypt a blob.
# @param [Hash] config the key/cipher/iv needed to initialize OpenSSL # @param [Hash] config the key/cipher/iv needed to initialize OpenSSL
def initialize(config) def initialize(config)
validate(config) # ensure that we have the required configuration keys
%w(cipher key iv).each do |option|
raise(Ripple::Encryption::EncryptorConfigError, "Missing configuration option '#{option}'.") if config[option].nil?
end
@config = config @config = config
@cipher = OpenSSL::Cipher.new(@config['cipher']) @cipher = OpenSSL::Cipher.new(@config['cipher'])
end end
Expand All @@ -40,15 +43,6 @@ def initialize_cipher_for(mode)
@cipher.key = @config['key'] @cipher.key = @config['key']
@cipher.iv = @config['iv'] @cipher.iv = @config['iv']
end end

# Creates an Encryptor that is prepared to encrypt/decrypt a blob.
# @param [Hash] config the key/cipher/iv needed to initialize OpenSSL
def validate(config)
# ensure that we have the required configuration keys
%w(cipher key iv).each do |option|
raise(Ripple::Encryption::EncryptorError, "Missing configuration option '#{option}'.") if config[option].nil?
end
end
end end
end end
end end
37 changes: 0 additions & 37 deletions lib/ripple-encryption/serializer.rb

This file was deleted.

2 changes: 1 addition & 1 deletion test/fixtures/test_document/some_other_data.encrypted.riak
@@ -1 +1 @@
{"version":"0.1.0","iv":"ABYLnUHWE/fIwE2gKYC6hg==\n","data":"KYtsnoDZ85AMR/eZAVBtEXe88gB/UNagMpl4oV7FLxUgtqw5BvPCbLChrmdg\nsRQas2VZ8/FkIx5CiMeJYoi9Ag==\n"} {"version":"0.0.3","iv":"ABYLnUHWE/fIwE2gKYC6hg==\n","data":"KYtsnoDZ85AMR/eZAVBtEXe88gB/UNagMpl4oV7FLxUgtqw5BvPCbLChrmdg\nsRQas2VZ8/FkIx5CiMeJYoi9Ag==\n"}
1 change: 1 addition & 0 deletions test/fixtures/test_document/v0_doc.riak
@@ -0,0 +1 @@
{"message":"this is unencrypted data", "_type":"TestDocument"}
1 change: 1 addition & 0 deletions test/fixtures/test_document/v1_doc.riak
@@ -0,0 +1 @@
0� �d�>^��|���`f���p�#ʼl_�*�
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/test_document/v2_doc.riak
@@ -0,0 +1 @@
{"version":"0.0.2","iv":"ABYLnUHWE/fIwE2gKYC6hg==\n","data":"MK0LuGThPhde4t0NfKSbhAvPjTuFmykhWVGNxPG++40=\n"}
30 changes: 13 additions & 17 deletions test/helper.rb
Expand Up @@ -17,24 +17,24 @@
client = Riak::Client.new(:nodes => [riak_config]) client = Riak::Client.new(:nodes => [riak_config])
bucket = client.bucket("#{riak_config[:namespace].to_s}test") bucket = client.bucket("#{riak_config[:namespace].to_s}test")
object = bucket.get_or_new("test") object = bucket.get_or_new("test")
rescue RuntimeError => e rescue RuntimeError
raise e raise RuntimeError, "Could not connect to the Riak test node."
end end

# define test Ripple Documents
# activate the library Ripple::Encryption::Encryption.activate ENV['ENCRYPTION']
Ripple::Encryption::Activation.new ENV['ENCRYPTION']

class TestDocument class TestDocument
include Ripple::Document include Ripple::Document
include Ripple::Encryption include Ripple::Encryption::Encryption
property :message, String property :message, String


def self.bucket_name def self.bucket_name
"#{Ripple.config[:namespace]}#{super}" "#{Ripple.config[:namespace]}#{super}"
end end
end end


# load Riak fixtures the raw way TestDocument.bucket.get_index('$bucket', '_').each {|k| TestDocument.bucket.delete(k)}

# load Riak fixtures
FileList[File.expand_path(File.join('..','fixtures','*'),__FILE__)].each do |f| FileList[File.expand_path(File.join('..','fixtures','*'),__FILE__)].each do |f|
if Dir.exists? f if Dir.exists? f
fixture_type = File.basename(f) fixture_type = File.basename(f)
Expand All @@ -43,15 +43,11 @@ def self.bucket_name
rescue NameError rescue NameError
raise NameError, "Is a Ripple Document of type '#{fixture_type.classify}' defined for that fixture file?" raise NameError, "Is a Ripple Document of type '#{fixture_type.classify}' defined for that fixture file?"
end end
# unencrypted jsons FileList[File.join(f,'*.riak')].each do |r|
FileList[File.join(f,'*.unencrypted.riak')].each do |r| key = File.basename(r,'.riak')
key = File.basename(r,'.unencrypted.riak') content_type = (key == 'v0_doc' ? 'application/json' : 'application/x-json-encrypted')
`curl -s -H 'content-type: application/json' -XPUT http://#{Ripple.config[:host]}:#{Ripple.config[:http_port]}/buckets/#{Ripple.config[:namespace]}#{fixture_type.pluralize}/keys/#{key} --data-binary @#{r}` `curl -s -H 'content-type: #{content_type}' -XPUT http://#{Ripple.config[:host]}:#{Ripple.config[:http_port]}/buckets/#{Ripple.config[:namespace]}#{fixture_type.pluralize}/keys/#{key} --data-binary @#{r}`
end
# encrypted jsons
FileList[File.join(f,'*.encrypted.riak')].each do |r|
key = File.basename(r,'.encrypted.riak')
`curl -s -H 'content-type: application/x-json-encrypted' -XPUT http://#{Ripple.config[:host]}:#{Ripple.config[:http_port]}/buckets/#{Ripple.config[:namespace]}#{fixture_type.pluralize}/keys/#{key} --data-binary @#{r}`
end end
end end
end end

0 comments on commit af77c5b

Please sign in to comment.