ripple-encryption 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ripple-contrib.gemspec
4
+ gemspec
5
+
6
+ gem 'riak-client', '~> 1.1.1'
7
+ gem 'ripple', :git => 'git://github.com/basho/ripple.git', :ref => '913806aa2942db5a3b61d1432d2c9be200338f50'
8
+
9
+ group :development, :test do
10
+ gem 'linecache19', :git => 'git://github.com/mark-moseley/linecache'
11
+ gem 'ruby-debug-base19x', '~> 0.11.30.pre4'
12
+ gem "ruby-debug19", "0.11.6"
13
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,80 @@
1
+ GIT
2
+ remote: git://github.com/basho/ripple.git
3
+ revision: 913806aa2942db5a3b61d1432d2c9be200338f50
4
+ ref: 913806aa2942db5a3b61d1432d2c9be200338f50
5
+ specs:
6
+ ripple (1.0.0.beta2)
7
+ activemodel (>= 3.0.0, < 3.3.0)
8
+ activesupport (>= 3.0.0, < 3.3.0)
9
+ riak-client (~> 1.1.0)
10
+ tzinfo
11
+
12
+ GIT
13
+ remote: git://github.com/mark-moseley/linecache
14
+ revision: 869c6a65155068415925067e480741bd0a71527e
15
+ specs:
16
+ linecache19 (0.5.12)
17
+ ruby_core_source (>= 0.1.4)
18
+
19
+ PATH
20
+ remote: .
21
+ specs:
22
+ ripple-encryption (0.0.3)
23
+ riak-client
24
+ ripple
25
+
26
+ GEM
27
+ remote: https://rubygems.org/
28
+ specs:
29
+ activemodel (3.2.12)
30
+ activesupport (= 3.2.12)
31
+ builder (~> 3.0.0)
32
+ activesupport (3.2.12)
33
+ i18n (~> 0.6)
34
+ multi_json (~> 1.0)
35
+ archive-tar-minitar (0.5.2)
36
+ beefcake (0.3.7)
37
+ builder (3.0.4)
38
+ columnize (0.3.6)
39
+ i18n (0.6.4)
40
+ innertube (1.0.2)
41
+ mini_shoulda (0.5.0)
42
+ minitest (> 2.1.0)
43
+ minitest (3.3.0)
44
+ multi_json (1.6.1)
45
+ rake (0.9.2.2)
46
+ riak-client (1.1.1)
47
+ beefcake (~> 0.3.7)
48
+ builder (>= 2.1.2)
49
+ i18n (>= 0.4.0)
50
+ innertube (~> 1.0.2)
51
+ multi_json (~> 1.0)
52
+ ruby-debug-base19 (0.11.25)
53
+ columnize (>= 0.3.1)
54
+ linecache19 (>= 0.5.11)
55
+ ruby_core_source (>= 0.1.4)
56
+ ruby-debug-base19x (0.11.30.pre10)
57
+ columnize (>= 0.3.1)
58
+ linecache19 (>= 0.5.11)
59
+ rake (>= 0.8.1)
60
+ ruby_core_source (>= 0.1.4)
61
+ ruby-debug19 (0.11.6)
62
+ columnize (>= 0.3.1)
63
+ linecache19 (>= 0.5.11)
64
+ ruby-debug-base19 (>= 0.11.19)
65
+ ruby_core_source (0.1.5)
66
+ archive-tar-minitar (>= 0.5.2)
67
+ tzinfo (0.3.35)
68
+
69
+ PLATFORMS
70
+ ruby
71
+
72
+ DEPENDENCIES
73
+ linecache19!
74
+ mini_shoulda
75
+ rake
76
+ riak-client (~> 1.1.1)
77
+ ripple!
78
+ ripple-encryption!
79
+ ruby-debug-base19x (~> 0.11.30.pre4)
80
+ ruby-debug19 (= 0.11.6)
data/LICENSE ADDED
@@ -0,0 +1,16 @@
1
+ Copyright (c) 2012 Basho Technologies, Inc.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
14
+
15
+ All of the files in this project are under the project-wide license
16
+ unless they are otherwise marked.
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # Ripple::Encryption
2
+
3
+ The ripple-encryption gem provides encryption and decryption for Ripple documents.
4
+ [riak-ruby](https://github.com/basho/riak-ruby-client) [ripple](https://github.com/basho/ripple)
5
+
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'ripple-encryption'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install ripple-encryption
20
+
21
+ ## Overview
22
+
23
+ You call the activation, which initializes a global serializer within
24
+ Ripple. Any object that gets saved with content-type 'application/x-json-encrypted'
25
+ then goes through the Encryption::Serializer, which loads or unloads the
26
+ data from Riak through the JsonDocument and EncryptedJsonDocument,
27
+ respectively. Both of these have a dependency on Encryption::Encrypter,
28
+ which makes the actual calls to OpenSSL.
29
+
30
+ JsonDocument stores the encrypted data wrapped in JSON encapsulation so
31
+ that you can still introspect the Riak object and see which version of
32
+ this gem was used to encrypt it.
33
+
34
+ There is also a Rake file to convert between encrypted and decrypted
35
+ JSON objects.
36
+
37
+ ## Usage
38
+
39
+ Include the gem in your Gemfile. Activate it somewhere in your
40
+ application initialization by pointing it to your encryption config file
41
+ like so:
42
+
43
+ Ripple::Encryption.activate PATH_TO_CONFIG_FILE
44
+
45
+ Then include the Ripple::Encryption module in your document class:
46
+
47
+ class SomeDocument
48
+ include Ripple::Document
49
+ include Ripple::Encryption
50
+ property :message, String
51
+ end
52
+
53
+ These documents will now be stored encrypted.
54
+
55
+ ## Running the Tests
56
+
57
+ Adjust the 'test/fixtures/ripple.yml' to point to a test riak database.
58
+
59
+ bundle exec rake
60
+
61
+ ## Contributing
62
+
63
+ 1. Fork it
64
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
65
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
66
+ 4. Push to the branch (`git push origin my-new-feature`)
67
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rake'
5
+ require 'rake/testtask'
6
+
7
+ Rake::TestTask.new(:test) do |t|
8
+ t.libs << "test"
9
+ t.test_files = FileList['test/**/test_*.rb']
10
+ t.verbose = true
11
+ end
12
+
13
+ namespace :test do
14
+ desc "Test everything"
15
+ task :all => [:test]
16
+ end
17
+
18
+ task :default => :test
19
+
20
+ # Connect to Riak and test the client connection.
21
+
22
+ namespace :migrate do
23
+ desc "Read in all unencrypted files, and save them to encrypted encoding."
24
+ task :encrypt do
25
+ Ripple::Encrytion::Migrate.new.convert(:encrypt)
26
+ end
27
+
28
+ desc "Read in all encrypted files, and save them unencrypted."
29
+ task :decrypt do
30
+ Ripple::Encrytion::Migrate.new.convert(:decrypt)
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ # AES-256-CBC requires a 32-byte key and 16 byte iv
2
+
3
+ # remove iv if a random generated iv is desired
4
+
5
+ development:
6
+ cipher: AES-256-CBC
7
+ key: fantasticobscurekeygoesherenowty
8
+ iv: !binary |
9
+ ABYLnUHWE/fIwE2gKYC6hg==
10
+
11
+ test:
12
+ cipher: AES-256-CBC
13
+ key: fantasticobscurekeygoesherenowty
14
+ iv: !binary |
15
+ ABYLnUHWE/fIwE2gKYC6hg==
16
+
17
+ prod:
18
+ cipher: AES-256-CBC
19
+ key: fantasticobscurekeygoesherenowty
20
+ base64: true
@@ -0,0 +1,9 @@
1
+ development:
2
+ host: 127.0.0.1
3
+ http_port: 8098
4
+ namespace: local_ns~
5
+
6
+ test:
7
+ host: 127.0.0.1
8
+ http_port: 9000
9
+ namespace: test_ns~
@@ -0,0 +1,82 @@
1
+ module Ripple
2
+ module Encryption
3
+ class Migration
4
+ # create log files in the tmp dir
5
+ def initialize
6
+ relative_root = File.expand_path(File.join('..','..','..'),__FILE__)
7
+ require File.join(relative_root,'lib','ripple-encryption.rb')
8
+ tmp_dir = File.join(relative_root,'tmp')
9
+ output_dir = File.join(tmp_dir,Time.now.strftime("%m-%d-%Y-%I%M%p"))
10
+ Dir.mkdir(tmp_dir) unless File.exists?(tmp_dir)
11
+ Dir.mkdir(output_dir) unless File.exists?(output_dir)
12
+ @fetched_file = File.open(File.join(output_dir,'fetched.log'),'w')
13
+ @stored_file = File.open(File.join(output_dir,'stored.log'),'w')
14
+ @error_file = File.open(File.join(output_dir,'error.log'),'w')
15
+ end
16
+
17
+ # finde only the encryptable models
18
+ def models
19
+ Objects.constants.map{|c| "#{c}".constantize}.select{|c| c.include?(Ripple::Encryption)}
20
+ end
21
+
22
+ # cycle through all objects and save them
23
+ def convert(type)
24
+ # the difference between encryption or decryption is
25
+ # simply changing the content-type of the object so
26
+ # that ripple knows what way to serialize it
27
+ case type
28
+ when :encrypt
29
+ content_type = 'application/x-json-encrypted'
30
+ when :decrypt
31
+ content_type = 'application/json'
32
+ end
33
+
34
+ # we don't need no stinking warnings :-)
35
+ Riak.disable_list_keys_warnings = true
36
+
37
+ # cycle through each key in the database and
38
+ # read it, then save it
39
+ print 'Processing buckets: '
40
+ models.each do |model|
41
+ success = nil
42
+ count = 0
43
+ bucket_name = model.bucket_name
44
+ model.bucket.keys do |streaming_keys|
45
+ streaming_keys.each do |key|
46
+ begin
47
+ object = model.find key
48
+ log :fetched, "/buckets/#{bucket_name}/keys/#{key}"
49
+ object.robject.content_type = content_type
50
+ object.save!
51
+ log :stored, "/buckets/#{bucket_name}/keys/#{key}"
52
+ count += 1
53
+ rescue => e
54
+ log :error, "/buckets/#{bucket_name}/keys/#{key} #{e}".force_encoding('UTF-8')
55
+ success = 'E, '
56
+ end
57
+ end
58
+ end
59
+ print success || "#{count}, "
60
+ end
61
+ puts ' Done.'
62
+
63
+ # warn us. please. :-)
64
+ Riak.disable_list_keys_warnings = false
65
+ end
66
+
67
+ # log the object action
68
+ def log(type, object, error=nil)
69
+ case type
70
+ when :fetched
71
+ @fetched_file.puts object
72
+ when :stored
73
+ @stored_file.puts object
74
+ when :error
75
+ @error_file.write object
76
+ @error_file.write error
77
+ @error_file.write "\n"
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,10 @@
1
+ require 'openssl'
2
+ require 'ripple'
3
+
4
+ module Ripple
5
+ module Encryption
6
+ end
7
+ end
8
+
9
+ # Include all of the support files.
10
+ FileList[File.expand_path(File.join('..','ripple-encryption','*.rb'),__FILE__)].each{|f| require f}
@@ -0,0 +1,75 @@
1
+ require 'openssl'
2
+ require 'ripple'
3
+
4
+ module Ripple
5
+ module Encryption
6
+
7
+ # When mixed into a Ripple::Document class, this will encrypt the
8
+ # serialized form before it is stored in Riak. You must register
9
+ # a serializer that will perform the encryption.
10
+ # @see Serializer
11
+ extend ActiveSupport::Concern
12
+
13
+ @@is_activated = false
14
+
15
+ included do
16
+ @@encrypted_content_type = self.encrypted_content_type = 'application/x-json-encrypted'
17
+ end
18
+
19
+ module ClassMethods
20
+ # @return [String] the content type to be used to indicate the
21
+ # proper encryption scheme. Defaults to 'application/x-json-encrypted'
22
+ attr_accessor :encrypted_content_type
23
+ end
24
+
25
+ # Overrides the internal method to set the content-type to be
26
+ # encrypted.
27
+ def update_robject
28
+ super
29
+ if @@is_activated
30
+ robject.content_type = @@encrypted_content_type
31
+ end
32
+ end
33
+
34
+ def self.activate(path)
35
+ encryptor = nil
36
+ unless Riak::Serializers['application/x-json-encrypted']
37
+ begin
38
+ config = YAML.load_file(path)[ENV['RACK_ENV']]
39
+ encryptor = Ripple::Encryption::Serializer.new(OpenSSL::Cipher.new(config['cipher']), 'application/x-json-encrypted', path)
40
+ rescue Exception => e
41
+ handle_invalid_encryption_config(e.message, e.backtrace)
42
+ end
43
+ encryptor.key = config['key'] if config['key']
44
+ encryptor.iv = config['iv'] if config['iv']
45
+ Riak::Serializers['application/x-json-encrypted'] = encryptor
46
+ @@is_activated = true
47
+ end
48
+ encryptor
49
+ end
50
+
51
+ def self.activated
52
+ @@is_activated
53
+ end
54
+
55
+ end
56
+ end
57
+
58
+ def handle_invalid_encryption_config(msg, trace)
59
+ puts <<eos
60
+
61
+ The file "config/encryption.yml" is missing or incorrect. You will
62
+ need to create this file and populate it with a valid cipher,
63
+ initialization vector and secret key.
64
+
65
+ An example is provided in "config/encryption.yml.example".
66
+ eos
67
+
68
+ puts "Error Message: " + msg
69
+ puts "Error Trace:"
70
+ trace.each do |line|
71
+ puts line
72
+ end
73
+
74
+ exit 1
75
+ end
@@ -0,0 +1,43 @@
1
+ require 'openssl'
2
+
3
+ module Ripple
4
+ module Encryption
5
+ # Generic error class for Config
6
+ class ConfigError < StandardError; end
7
+
8
+ # Handles the configuration information for the Encryptor.
9
+ #
10
+ # Example usage:
11
+ # Ripple::Encryption::Config.defaults
12
+ # Ripple::Encryption::Config.new(:iv => "SOMEIV").to_h
13
+ class Config
14
+ # Initializes the config from our yml file.
15
+ # @param [String] path to yml file
16
+ def initialize(path)
17
+ validate_path(path)
18
+ @config = YAML.load_file(path)[ENV['RACK_ENV']]
19
+ end
20
+
21
+ # Return the options in the hash expected by Encryptor.
22
+ def to_h
23
+ @config
24
+ end
25
+
26
+ # Return either the default initialization vector, or create a new one.
27
+ def activate
28
+ @config['iv'] ||= OpenSSL::Random.random_bytes(16)
29
+ end
30
+
31
+ def validate_path(path)
32
+ if !File.exists? path
33
+ raise Ripple::Encryption::ConfigError, <<MISSINGFILE
34
+ The file "config/encryption.yml" is missing or incorrect. You will
35
+ need to create this file and populate it with a valid cipher,
36
+ initialization vector and secret key. An example is provided in
37
+ "config/encryption.yml.example".
38
+ MISSINGFILE
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,29 @@
1
+ module Ripple
2
+ module Encryption
3
+ # Generic error class for Encryptor
4
+ class EncryptedJsonDocumentError < StandardError; end
5
+
6
+ # Interprets a encapsulation in JSON for encrypted Ripple documents.
7
+ #
8
+ # Example usage:
9
+ # Ripple::Encryption::JsonDocument.new(@document).encrypt
10
+ class EncryptedJsonDocument
11
+ # Creates an object that is prepared to decrypt its contents.
12
+ # @param [String] data json string that was stored in Riak
13
+ def initialize(config, data)
14
+ @config = config.to_h.clone
15
+ @json = JSON.parse data
16
+ raise(EncryptedJsonDocumentError, "Missing 'iv' for decryption") unless @json['iv']
17
+ iv = Base64.decode64 @json['iv']
18
+ @config.merge!('iv' => iv)
19
+
20
+ @decryptor = Ripple::Encryption::Encryptor.new @config
21
+ end
22
+
23
+ # Returns the original data from the stored encrypted format
24
+ def decrypt
25
+ JSON.load @decryptor.decrypt Base64.decode64 @json['data']
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,48 @@
1
+ module Ripple
2
+ module Encryption
3
+ # Generic error class for Encryptor
4
+ class EncryptorConfigError < StandardError; end
5
+
6
+ # Implements a simple object that can either encrypt or decrypt arbitrary data.
7
+ #
8
+ # Example usage:
9
+ # encryptor = Ripple::Encryption::Encryptor.new Ripple::Encryption::Config.defaults
10
+ # encryptor.encrypt stuff
11
+ # encryptor.decrypt stuff
12
+ class Encryptor
13
+ # Creates an Encryptor that is prepared to encrypt/decrypt a blob.
14
+ # @param [Hash] config the key/cipher/iv needed to initialize OpenSSL
15
+ def initialize(config)
16
+ # ensure that we have the required configuration keys
17
+ %w(cipher key iv).each do |option|
18
+ raise(Ripple::Encryption::EncryptorConfigError, "Missing configuration option '#{option}'.") if config[option].nil?
19
+ end
20
+ @config = config
21
+ @cipher = OpenSSL::Cipher.new(@config['cipher'])
22
+ end
23
+
24
+ # Encrypt stuff.
25
+ # @param [Object] blob the data to encrypt
26
+ def encrypt(blob)
27
+ initialize_cipher_for :encrypt
28
+ "#{@cipher.update blob}#{@cipher.final}"
29
+ end
30
+
31
+ # Decrypt stuff.
32
+ # @param [Object] blob the encrypted data to decrypt
33
+ def decrypt(blob)
34
+ initialize_cipher_for :decrypt
35
+ "#{@cipher.update blob}#{@cipher.final}"
36
+ end
37
+
38
+ private
39
+ # This sets the mode so OpenSSL knows to encrypt or decrypt, etc.
40
+ # @param [Symbol] mode either :encrypt or :decrypt
41
+ def initialize_cipher_for(mode)
42
+ @cipher.send mode
43
+ @cipher.key = @config['key']
44
+ @cipher.iv = @config['iv']
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,24 @@
1
+ module Ripple
2
+ module Encryption
3
+ # Implements an encapsulation in JSON for encrypted Ripple documents.
4
+ #
5
+ # Example usage:
6
+ # Ripple::Encryption::JsonDocument.new(@document).encrypt
7
+ class JsonDocument
8
+ # Creates an object that is prepared to encrypt its contents.
9
+ # @param [String] data object to store
10
+ def initialize(config, data)
11
+ config.activate
12
+ @config = config.to_h
13
+ @data = JSON.dump(data)
14
+ @encryptor = Ripple::Encryption::Encryptor.new @config
15
+ end
16
+
17
+ # Converts the data into the encrypted format
18
+ def encrypt
19
+ encrypted_data = @encryptor.encrypt @data
20
+ JSON.dump({:version => Ripple::Encryption::VERSION, :iv => Base64.encode64(@config['iv']), :data => Base64.encode64(encrypted_data)})
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,117 @@
1
+ module Ripple
2
+ module Encryption
3
+ # Implements the {Riak::Serializer} API for the purpose of
4
+ # encrypting/decrypting Ripple documents.
5
+ #
6
+ # Example usage:
7
+ # ::Riak::Serializers['application/x-json-encrypted'] = EncryptedSerializer.new(OpenSSL::Cipher.new("AES-256"))
8
+ # class MyDocument
9
+ # include Ripple::Document
10
+ # include Riak::Encryption
11
+ # end
12
+ #
13
+ # @see Encryption
14
+ class Serializer
15
+ # @return [String] The Content-Type of the internal format,
16
+ # generally "application/json"
17
+ attr_accessor :content_type
18
+
19
+ # @return [OpenSSL::Cipher, OpenSSL::PKey::*] the cipher used to encrypt the object
20
+ attr_accessor :cipher
21
+
22
+ # Cipher-specific settings
23
+ # @see OpenSSL::Cipher
24
+ attr_accessor :key, :iv, :key_length, :padding
25
+
26
+ # Serialization Options
27
+ # @return [true, false] Is the encrypted text also base64 encoded?
28
+ attr_accessor :base64
29
+
30
+ # Creates a serializer using the provided cipher and internal
31
+ # content type. Be sure to set the {#key}, {#iv}, {#key_length},
32
+ # {#padding} as appropriate for the cipher before attempting
33
+ # (de-)serialization.
34
+ # @param [OpenSSL::Cipher] cipher the desired
35
+ # encryption/decryption algorithm
36
+ # @param [String] content_type the Content-Type of the
37
+ # unencrypted contents
38
+ def initialize(cipher, content_type='application/json', path)
39
+ @cipher, @content_type = cipher, content_type
40
+ @config = Ripple::Encryption::Config.new(path)
41
+ end
42
+
43
+ # Serializes and encrypts the Ruby object using the assigned
44
+ # cipher and Content-Type.
45
+ # @param [Object] object the Ruby object to serialize/encrypt
46
+ # @return [String] the serialized, encrypted form of the object
47
+ def dump(object)
48
+ JsonDocument.new(@config, object).encrypt
49
+ end
50
+
51
+ # Decrypts and deserializes the blob using the assigned cipher
52
+ # and Content-Type.
53
+ # @param [String] blob the original content from Riak
54
+ # @return [Object] the decrypted and deserialized object
55
+ def load(object)
56
+ # try the v1 way first
57
+ begin
58
+ internal = decrypt(object)
59
+ return ::Riak::Serializers.deserialize('application/json', internal)
60
+ # if that doesn't work, try the v2 way
61
+ rescue OpenSSL::Cipher::CipherError, MultiJson::DecodeError
62
+ return EncryptedJsonDocument.new(@config, object).decrypt
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ # generates a new iv each call unless a static (less secure)
69
+ # iv is used.
70
+ def encrypt(object)
71
+ old_version = '0.0.1'
72
+ result = ''
73
+ if cipher.respond_to?(:iv=) and @iv == nil
74
+ iv = OpenSSL::Random.random_bytes(cipher.iv_len)
75
+ cipher.iv = iv
76
+ result << old_version << iv
77
+ end
78
+
79
+ if cipher.respond_to?(:public_encrypt)
80
+ result << cipher.public_encrypt(object)
81
+ else
82
+ cipher_setup :encrypt
83
+ result << cipher.update(object) << cipher.final
84
+ cipher.reset
85
+ end
86
+ return result
87
+ end
88
+
89
+ def decrypt(cipher_text)
90
+ old_version = '0.0.1'
91
+
92
+ if cipher.respond_to?(:iv=) and @iv == nil
93
+ version = cipher_text.slice(0, old_version.length)
94
+ cipher.iv = cipher_text.slice(old_version.length, cipher.iv_len)
95
+ cipher_text = cipher_text.slice(old_version.length + cipher.iv_len, cipher_text.length)
96
+ end
97
+
98
+ if cipher.respond_to?(:private_decrypt)
99
+ cipher.private_decrypt(cipher_text)
100
+ else
101
+ cipher_setup :decrypt
102
+ result = cipher.update(cipher_text) << cipher.final
103
+ cipher.reset
104
+ result
105
+ end
106
+ end
107
+
108
+ def cipher_setup(mode)
109
+ cipher.send mode
110
+ cipher.key = key if key
111
+ cipher.iv = iv if iv
112
+ cipher.key_length = key_length if key_length
113
+ cipher.padding = padding if padding
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,5 @@
1
+ module Ripple
2
+ module Encryption
3
+ VERSION = "0.0.3"
4
+ end
5
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/ripple-encryption/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Randy Secrist", "Casey Rosenthal"]
6
+ gem.email = ["rsecrist@basho.com", "casey@basho.com"]
7
+ gem.description = %q{Easily encrypt data at rest with minimal changes to existing ripple models.}
8
+ gem.summary = %q{A simple encryption library for objects stored in riak.}
9
+ gem.homepage = "http://github.com/basho/ripple-encryption"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "ripple-encryption"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Ripple::Encryption::VERSION
17
+
18
+ gem.add_dependency 'riak-client'
19
+ gem.add_dependency 'ripple'
20
+
21
+ # Test Dependencies
22
+ gem.add_development_dependency 'rake'
23
+ gem.add_development_dependency 'mini_shoulda'
24
+ end
@@ -0,0 +1,5 @@
1
+ test:
2
+ cipher: AES-256-CBC
3
+ key: fantasticobscurekeygoesherenowty
4
+ iv: !binary |
5
+ ABYLnUHWE/fIwE2gKYC6hg==
@@ -0,0 +1,3 @@
1
+ test:
2
+ cipher: AES-256-CBC
3
+ key: fantasticobscurekeygoesherenowty
@@ -0,0 +1,4 @@
1
+ test:
2
+ host: 127.0.0.1
3
+ http_port: 8098
4
+ namespace: test_ns~
@@ -0,0 +1 @@
1
+ {"message":"this is unencrypted data", "_type":"TestDocument"}
@@ -0,0 +1 @@
1
+ {"version":"0.0.3","iv":"ABYLnUHWE/fIwE2gKYC6hg==\n","data":"KYtsnoDZ85AMR/eZAVBtEXe88gB/UNagMpl4oV7FLxUgtqw5BvPCbLChrmdg\nsRQas2VZ8/FkIx5CiMeJYoi9Ag==\n"}
@@ -0,0 +1 @@
1
+ {"message":"this is unencrypted data", "_type":"TestDocument"}
@@ -0,0 +1 @@
1
+ 0� �d�>^��
@@ -0,0 +1 @@
1
+ {"version":"0.0.2","iv":"ABYLnUHWE/fIwE2gKYC6hg==\n","data":"MK0LuGThPhde4t0NfKSbhAvPjTuFmykhWVGNxPG++40=\n"}
data/test/helper.rb ADDED
@@ -0,0 +1,53 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+
4
+ require 'minitest/autorun'
5
+ require 'mini_shoulda'
6
+
7
+ require 'ripple-encryption'
8
+
9
+ ENV['RACK_ENV'] = 'test'
10
+ ENV['RIPPLE'] = File.expand_path(File.join('..','fixtures','ripple.yml'),__FILE__)
11
+ ENV['ENCRYPTION'] = File.expand_path(File.join('..','fixtures','encryption.yml'),__FILE__)
12
+
13
+ # connect to a local Riak test node
14
+ begin
15
+ Ripple.load_configuration ENV['RIPPLE'], ['test']
16
+ riak_config = Hash[YAML.load_file(ENV['RIPPLE'])['test'].map{|k,v| [k.to_sym, v]}]
17
+ client = Riak::Client.new(:nodes => [riak_config])
18
+ bucket = client.bucket("#{riak_config[:namespace].to_s}test")
19
+ object = bucket.get_or_new("test")
20
+ rescue RuntimeError
21
+ raise RuntimeError, "Could not connect to the Riak test node."
22
+ end
23
+ # define test Ripple Documents
24
+ Ripple::Encryption.activate ENV['ENCRYPTION']
25
+ class TestDocument
26
+ include Ripple::Document
27
+ include Ripple::Encryption
28
+ property :message, String
29
+
30
+ def self.bucket_name
31
+ "#{Ripple.config[:namespace]}#{super}"
32
+ end
33
+ end
34
+
35
+ TestDocument.bucket.get_index('$bucket', '_').each {|k| TestDocument.bucket.delete(k)}
36
+
37
+ # load Riak fixtures
38
+ FileList[File.expand_path(File.join('..','fixtures','*'),__FILE__)].each do |f|
39
+ if Dir.exists? f
40
+ fixture_type = File.basename(f)
41
+ begin
42
+ klass = fixture_type.classify.constantize
43
+ rescue NameError
44
+ raise NameError, "Is a Ripple Document of type '#{fixture_type.classify}' defined for that fixture file?"
45
+ end
46
+ FileList[File.join(f,'*.riak')].each do |r|
47
+ key = File.basename(r,'.riak')
48
+ content_type = (key == 'v0_doc' ? 'application/json' : 'application/x-json-encrypted')
49
+ `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}`
50
+ end
51
+ end
52
+ end
53
+
@@ -0,0 +1,11 @@
1
+ require 'helper'
2
+
3
+ class TestConfig < MiniTest::Spec
4
+ context "Ripple::Encryption::Config" do
5
+ should "raise heck if the config file isn't found" do
6
+ assert_raises Ripple::Encryption::ConfigError do
7
+ config = Ripple::Encryption::Config.new('nowhere')
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,42 @@
1
+ require 'helper'
2
+
3
+ class TestEncryptor < MiniTest::Spec
4
+ context "Ripple::Encryption::Encryptor" do
5
+ setup do
6
+ config = Ripple::Encryption::Config.new ENV['ENCRYPTION']
7
+ @encryptor = Ripple::Encryption::Encryptor.new config.to_h
8
+ # example text
9
+ @text = "This is some nifty text."
10
+ # this is the example text encrypted
11
+ @blob = "Vfn\xC3\xF1a\xB9\x89\x16\xCA\xD4w\xC4\xAF\x16\xA0c\xF7\xD0\x88\xA3;d\xC8Y\x91\xA8\x05W+)\xC8"
12
+ end
13
+
14
+ should "convert text to an encrypted blob" do
15
+ assert_equal @blob, @encryptor.encrypt(@text), "Encryption failed."
16
+ end
17
+
18
+ should "convert encrypted blob to text" do
19
+ assert_equal @text, @encryptor.decrypt(@blob), "Decryption failed."
20
+ end
21
+ end
22
+
23
+ context "Ripple::Encryption::Encryptor with missing parameter" do
24
+ should "raise an error if key is missing" do
25
+ assert_raises Ripple::Encryption::EncryptorConfigError do
26
+ Ripple::Encryption::Encryptor.new(:iv => 'iv', :cipher => 'AES-256-CBC')
27
+ end
28
+ end
29
+
30
+ should "raise an error if iv is missing" do
31
+ assert_raises Ripple::Encryption::EncryptorConfigError do
32
+ Ripple::Encryption::Encryptor.new(:key => 'key', :cipher => 'AES-256-CBC')
33
+ end
34
+ end
35
+
36
+ should "raise an error if cipher is missing" do
37
+ assert_raises Ripple::Encryption::EncryptorConfigError do
38
+ Ripple::Encryption::Encryptor.new(:key => 'key', :iv => 'iv')
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,42 @@
1
+ require 'helper'
2
+
3
+ class TestJsonDocument < MiniTest::Spec
4
+ context "Ripple::Encryption::JsonDocument" do
5
+ setup do
6
+ # get some encryption going
7
+ @config = Ripple::Encryption::Config.new ENV['ENCRYPTION']
8
+ encryptor = Ripple::Encryption::Encryptor.new @config.to_h
9
+
10
+ # this is the data package that we want
11
+ @document = {'some' => 'data goes here'}
12
+
13
+ # this is how we want that data package to actually be stored
14
+ encrypted_value = encryptor.encrypt JSON.dump @document
15
+ @encrypted_document = JSON.dump({:version => Ripple::Encryption::VERSION, :iv => Base64.encode64(@config.to_h['iv']), :data => Base64.encode64(encrypted_value)})
16
+ end
17
+
18
+ should "convert a document to our desired JSON format" do
19
+ assert_equal @encrypted_document, Ripple::Encryption::JsonDocument.new(@config, @document).encrypt, 'Did not get the JSON format expected.'
20
+ end
21
+
22
+ should "interpret our JSON format into a document" do
23
+ assert_equal @document, Ripple::Encryption::EncryptedJsonDocument.new(@config, @encrypted_document).decrypt, 'Did not get the JSON format expected.'
24
+ end
25
+ end
26
+
27
+ context "Ripple::Encryption::JsonDocument with no initialization vector" do
28
+ setup do
29
+ # this is the data package that we want
30
+ @document = {'some' => 'data goes here'}
31
+
32
+ # rig a JsonDocument without an iv
33
+ @config = Ripple::Encryption::Config.new File.expand_path(File.join('..','fixtures','encryption_no_iv.yml'),__FILE__)
34
+ @json_document = Ripple::Encryption::JsonDocument.new(@config, @document)
35
+ end
36
+
37
+ should "convert a document to our desired JSON format and back again" do
38
+ encrypted_document = @json_document.encrypt
39
+ assert_equal @document, Ripple::Encryption::EncryptedJsonDocument.new(@config, encrypted_document).decrypt, 'Did not get the JSON format expected.'
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,27 @@
1
+ require 'helper'
2
+
3
+ class TestRipple < MiniTest::Spec
4
+ context "TestDocument" do
5
+ should "write the ripple document" do
6
+ document = TestDocument.new
7
+ document.message = 'here is some new data'
8
+ document.save
9
+ same_document = TestDocument.find(document.key)
10
+ assert_equal document.message, same_document.message
11
+
12
+ # read the document back out
13
+ read_doc = TestDocument.find(document.key)
14
+ assert_equal 'here is some new data', read_doc.message
15
+ end
16
+
17
+ should "write the ripple document raw confirmation" do
18
+ document = TestDocument.new
19
+ document.message = 'here is some new data'
20
+ document.save
21
+ expected_data = 'VpQTfX23xKdMK4Kprp/xgwDh4UFFSYC8q4OeOhK2zPn0l5huFO+vsoBrq8pT\nd5Z3EdgPx3k8VpL0QNH1FM6m4g==\n'
22
+ expected_doc_data = "{\"version\":\"#{Ripple::Encryption::VERSION}\",\"iv\":\"ABYLnUHWE/fIwE2gKYC6hg==\\n\",\"data\":\"#{expected_data}\"}"
23
+ raw_data = `curl -s http://#{Ripple.config[:host]}:#{Ripple.config[:http_port]}/buckets/#{TestDocument.bucket_name}/keys/#{document.key}`
24
+ assert_equal expected_doc_data, raw_data
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ require 'helper'
2
+
3
+ class TestMigrationV1ToV2 < MiniTest::Spec
4
+ context "unencrypted GenericModel" do
5
+ setup do
6
+ end
7
+
8
+ should "read unencrypted document type" do
9
+ assert v0 = TestDocument.find('v0_doc')
10
+ assert_equal 'this is unencrypted data', v0.message
11
+ end
12
+
13
+ should "write unencrypted document type when content-type is plain" do
14
+ document = TestDocument.new
15
+ document.message = 'here is some new data'
16
+ Ripple::Encryption.class_variable_set(:@@is_activated, false)
17
+ document.robject.content_type = 'application/json'
18
+ document.save
19
+ expected_v2_data = '{"message":"here is some new data","_type":"TestDocument"}'
20
+ raw_data = `curl -s -XGET http://#{Ripple.config[:host]}:#{Ripple.config[:http_port]}/buckets/#{TestDocument.bucket_name}/keys/#{document.key}`
21
+ assert_equal expected_v2_data, raw_data
22
+ end
23
+ end
24
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ripple-encryption
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Randy Secrist
9
+ - Casey Rosenthal
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-03-03 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: riak-client
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: ripple
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: mini_shoulda
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :development
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ description: Easily encrypt data at rest with minimal changes to existing ripple models.
80
+ email:
81
+ - rsecrist@basho.com
82
+ - casey@basho.com
83
+ executables: []
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - Gemfile
88
+ - Gemfile.lock
89
+ - LICENSE
90
+ - README.md
91
+ - Rakefile
92
+ - config/encryption.yml.example
93
+ - config/ripple.yml.example
94
+ - lib/rake/migrate.rb
95
+ - lib/ripple-encryption.rb
96
+ - lib/ripple-encryption/activation.rb
97
+ - lib/ripple-encryption/config.rb
98
+ - lib/ripple-encryption/encrypted_json_document.rb
99
+ - lib/ripple-encryption/encryptor.rb
100
+ - lib/ripple-encryption/json_document.rb
101
+ - lib/ripple-encryption/serializer.rb
102
+ - lib/ripple-encryption/version.rb
103
+ - ripple-encryption.gemspec
104
+ - test/fixtures/encryption.yml
105
+ - test/fixtures/encryption_no_iv.yml
106
+ - test/fixtures/ripple.yml
107
+ - test/fixtures/test_document/some_data.unencrypted.riak
108
+ - test/fixtures/test_document/some_other_data.encrypted.riak
109
+ - test/fixtures/test_document/v0_doc.riak
110
+ - test/fixtures/test_document/v1_doc.riak
111
+ - test/fixtures/test_document/v2_doc.riak
112
+ - test/helper.rb
113
+ - test/test_config.rb
114
+ - test/test_encryptor.rb
115
+ - test/test_json_document.rb
116
+ - test/test_ripple.rb
117
+ - test/test_unencrypted_document.rb
118
+ homepage: http://github.com/basho/ripple-encryption
119
+ licenses: []
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ! '>='
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ none: false
132
+ requirements:
133
+ - - ! '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 1.8.23
139
+ signing_key:
140
+ specification_version: 3
141
+ summary: A simple encryption library for objects stored in riak.
142
+ test_files:
143
+ - test/fixtures/encryption.yml
144
+ - test/fixtures/encryption_no_iv.yml
145
+ - test/fixtures/ripple.yml
146
+ - test/fixtures/test_document/some_data.unencrypted.riak
147
+ - test/fixtures/test_document/some_other_data.encrypted.riak
148
+ - test/fixtures/test_document/v0_doc.riak
149
+ - test/fixtures/test_document/v1_doc.riak
150
+ - test/fixtures/test_document/v2_doc.riak
151
+ - test/helper.rb
152
+ - test/test_config.rb
153
+ - test/test_encryptor.rb
154
+ - test/test_json_document.rb
155
+ - test/test_ripple.rb
156
+ - test/test_unencrypted_document.rb