ripple-encryption 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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