cryptonite 0.0.4 → 0.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 61b63389dfd44213ac19433d8410d60b9929adc0
4
- data.tar.gz: 92b6e2b12c1587e944dae4697094c58d6ae0e633
3
+ metadata.gz: f702ef8016f18189a7874c4bf8dc927465620aaa
4
+ data.tar.gz: 8e53c002975cb9d83266b29c90d93bc39f395f2b
5
5
  SHA512:
6
- metadata.gz: e8f067fbb753372ae450dc63f318adb03230b3c6253d543fa279e335b6f856de4d2b9f43c0701004d153192b5f2ab68da1cf16133b7fa4338287d952671a4817
7
- data.tar.gz: 30f171edf9098386760dc322a9398342ab48e035d22ad13b5de712a3a5fccca33e180a6be40efdaf3153c2cbdc045d10daf68ad6a596cff85c0ea83db48c3d3a
6
+ metadata.gz: 7917a42f42863c853e9cfc2c5aa8efe94b60960872d787332759ee719545b7b678acdc1d53eeb3b83e422a1e9ade109663598b93c27c9db53324a21a0200bc57
7
+ data.tar.gz: aa4615f0259f2e0b9b3498eec6b65fb802c8d1610f8ae399d58b75adb0eedd292466294fb636a7c2698b116c0d8ac0695f0a6f64daacdd37d73c1fc8afc2ca0c
@@ -0,0 +1,63 @@
1
+ AllCops:
2
+ RunRailsCops: true
3
+ Exclude:
4
+ # Rubocop's default
5
+ - 'vendor/**/*'
6
+ # To ensure smooth `rake rails:update`, we exclude some of the Rails' generated files
7
+ # please add new Rails' generated files to this list after running `rake rails:update` (if any)
8
+ # please add your exclude specific for this project after this section
9
+ - 'bin/**/*'
10
+ - 'config/boot.rb'
11
+ - 'config/application.rb'
12
+ - 'config/environment.rb'
13
+ - 'config/environments/development.rb'
14
+ - 'config/environments/production.rb'
15
+ - 'config/environments/test.rb'
16
+ - 'config/initializers/assets.rb'
17
+ - 'config/initializers/backtrace_silencers.rb'
18
+ - 'config/initializers/cookies_serializer.rb'
19
+ - 'config/initializers/filter_parameter_logging.rb'
20
+ - 'config/initializers/inflections.rb'
21
+ - 'config/initializers/mime_types.rb'
22
+ - 'config/initializers/session_store.rb'
23
+ - 'config/initializers/wrap_parameters.rb'
24
+ - 'db/schema.rb'
25
+ - 'script/**/*'
26
+ # Common generated files (projects independent)
27
+ - 'config/unicorn.rb'
28
+ - 'config/initializers/devise.rb'
29
+ # Add your exclude files specific to this project here
30
+ - 'config/initializers/maps.rb'
31
+
32
+ Style/AlignParameters:
33
+ Enabled: true
34
+ EnforcedStyle: with_fixed_indentation
35
+
36
+ Style/Documentation:
37
+ Enabled: false
38
+
39
+ # If the project still supporting ruby 1.8, uncomment the following lines to force Rubocop
40
+ # to use old hash_rockets syntax, if left commented, Rubocop will force the newer ruby19 syntax
41
+ # Style/HashSyntax:
42
+ # EnforcedStyle: hash_rockets
43
+
44
+ Style/SingleSpaceBeforeFirstArg:
45
+ Enabled: false
46
+
47
+ ##################### Metrics ##################################
48
+
49
+ Metrics/LineLength:
50
+ Max: 120
51
+ AllowURI: true
52
+
53
+ Metrics/MethodLength:
54
+ CountComments: false
55
+ Exclude:
56
+ - 'db/migrate/*'
57
+
58
+
59
+ # Project specific settings
60
+
61
+ Style/BlockComments:
62
+ Exclude:
63
+ - 'spec/spec_helper.rb'
@@ -1,3 +1,4 @@
1
+ sudo: false
1
2
  language: ruby
2
3
  cache:
3
4
  - bundler
@@ -5,3 +6,5 @@ rvm:
5
6
  - 2.1.4
6
7
  - 2.0.0
7
8
  - 1.9.3
9
+ before_script:
10
+ - bundle exec rubocop -D
data/README.md CHANGED
@@ -46,6 +46,35 @@ ActiveRecord methods that operate massively on records do not use the
46
46
  serialization features and so encryption / decryption does not take place
47
47
  there. This is by design.
48
48
 
49
+ *Note: Currently the independency on private key is not fully implemented.*
50
+
51
+ ### Migration Helpers
52
+
53
+ In order to facilitate migration two module methods have been implemented that
54
+ operate independently from the ActiveRecord models. In a migration file the
55
+ `Cryptonite.encrypt_model_attributes` may be used to facilitate upwards
56
+ migration and `Cryptonite.decrypt_model_attributes` for downwards migration.
57
+ The parameters is the ActiveRecord model class and the exact same parameters as
58
+ the `attr_encrypted` method, e.g.
59
+
60
+ class ChangeSecretInUsers < ActiveRecord::Migration
61
+ def up
62
+ change_column :users, :secret, :text, limit: 4096
63
+
64
+ say_with_time "encrypt_user_secrets" do
65
+ Cryptonite.encrypt_model_attributes(User, :secret, keypair: 'private_key.pem', private_key_password: 'test')
66
+ end
67
+ end
68
+
69
+ def down
70
+ say_with_time "decrypt_user_secrets" do
71
+ Cryptonite.decrypt_model_attributes(User, :secret, keypair: 'private_key.pem', private_key_password: 'test')
72
+ end
73
+
74
+ change_column :users, :secret, :string
75
+ end
76
+ end
77
+
49
78
  ## Key Generation
50
79
 
51
80
  Generate a key pair:
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => :spec
6
+ task default: :spec
@@ -4,24 +4,25 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'cryptonite/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "cryptonite"
7
+ spec.name = 'cryptonite'
8
8
  spec.version = Cryptonite::VERSION
9
- spec.authors = ["GaggleAMP"]
10
- spec.email = ["info@gaggleamp.com"]
11
- spec.summary = %q{Enables the encryption of specific ActiveRecord attributes.}
12
- spec.description = %q{Enables the encryption of specific ActiveRecord attributes.}
13
- spec.homepage = "https://github.com/GaggleAMP/cryptonite"
14
- spec.license = "MIT"
9
+ spec.authors = ['GaggleAMP']
10
+ spec.email = ['info@gaggleamp.com']
11
+ spec.summary = 'Enables the encryption of specific ActiveRecord attributes.'
12
+ spec.description = 'Enables the encryption of specific ActiveRecord attributes.'
13
+ spec.homepage = 'https://github.com/GaggleAMP/cryptonite'
14
+ spec.license = 'MIT'
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0")
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
17
+ spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(/^(test|spec|features)\//)
19
+ spec.require_paths = ['lib']
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.6"
22
- spec.add_development_dependency "rake"
23
- spec.add_development_dependency "rspec", "~> 3.1"
24
- spec.add_development_dependency "sqlite3"
25
- spec.add_dependency "activerecord", ">= 4.0", "< 4.2"
26
- spec.add_dependency "activesupport", ">= 4.0", "< 4.2"
21
+ spec.add_development_dependency 'bundler', '~> 1.6'
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'rspec', '~> 3.1'
24
+ spec.add_development_dependency 'sqlite3'
25
+ spec.add_development_dependency 'rubocop'
26
+ spec.add_dependency 'activerecord', '>= 4.0', '< 4.2'
27
+ spec.add_dependency 'activesupport', '>= 4.0', '< 4.2'
27
28
  end
@@ -1,7 +1,7 @@
1
1
  require 'cryptonite/version'
2
2
 
3
- require 'openssl'
4
- require 'base64'
3
+ require 'cryptonite/coder'
4
+ require 'cryptonite/key_extractor'
5
5
 
6
6
  require 'active_support/concern'
7
7
  require 'active_support/lazy_load_hooks'
@@ -18,75 +18,77 @@ module Cryptonite
18
18
  end
19
19
 
20
20
  module ClassMethods
21
+ include Cryptonite::KeyExtractor
22
+
21
23
  # Attributes listed as encrypted will be transparently encrypted and
22
24
  # decrypted in database operations.
23
25
  def attr_encrypted(*attributes)
24
26
  options = attributes.extract_options!
25
27
 
26
- @public_key = get_rsa_key(options[:public_key] || options[:key_pair] || ENV['PUBLIC_KEY'])
27
- @private_key = get_rsa_key(options[:private_key] || options[:key_pair] || ENV['PRIVATE_KEY'], options[:private_key_password] || ENV['PRIVATE_KEY_PASSWORD'])
28
+ public_key = extract_public_key(options)
29
+ private_key = extract_private_key(options)
28
30
 
29
- for attribute in attributes do
30
- serialize attribute, Coder.new(@private_key || @public_key)
31
- end
31
+ serialize_attributes_with_coder(attributes, private_key || public_key)
32
32
 
33
- self._attr_encrypted = Set.new(attributes.map { |a| a.to_s }) + (self._attr_encrypted || [])
33
+ self._attr_encrypted = Set.new(attributes.map(&:to_s)) + (_attr_encrypted || [])
34
34
  end
35
35
 
36
36
  # Returns an array of all the attributes that have been specified as encrypted.
37
37
  def encrypted_attributes
38
- self._attr_encrypted
38
+ _attr_encrypted
39
39
  end
40
40
 
41
- private
42
- # Retrives an RSA key with multiple ways.
43
- def get_rsa_key(key, password = nil)
44
- return nil unless key
41
+ private
45
42
 
46
- if key.is_a?(Proc)
47
- key = key.call
43
+ # Serializes all attributes with encryption coder.
44
+ def serialize_attributes_with_coder(attributes, key)
45
+ attributes.each do |attribute|
46
+ serialize attribute, Coder.new(key)
48
47
  end
48
+ end
49
+ end
49
50
 
50
- if key.is_a?(Symbol)
51
- key = @instance.send(key)
52
- end
51
+ module_function
53
52
 
54
- return key if key.is_a?(::OpenSSL::PKey::RSA)
53
+ # Encrypts attributes of a specific model. This method is indended for migration purposes. It takes the same arguments
54
+ # as the `attr_encrypted` class method.
55
+ def encrypt_model_attributes(model, *attributes) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
56
+ fail ArgumentError, "ActiveRecord::Base expected, got #{model.inspect}" unless model <= ActiveRecord::Base
55
57
 
56
- if key.respond_to?(:read)
57
- key = key.read
58
- elsif key !~ /^-+BEGIN .* KEY-+$/
59
- key = File.read(key)
60
- end
58
+ options = attributes.extract_options!
59
+ encrypted_attributes = attributes.map(&:to_s) & model.column_names
60
+ coder = Coder.new extract_public_key(options)
61
61
 
62
- if password.nil?
63
- ::OpenSSL::PKey::RSA.new(key)
64
- else
65
- ::OpenSSL::PKey::RSA.new(key, password.to_s)
66
- end
62
+ model.find_each do |record|
63
+ updated_columns =
64
+ encrypted_attributes.each_with_object({}) do |attribute, values|
65
+ values[attribute] = coder.encrypt(record.typecasted_attribute_value attribute)
66
+ end
67
+
68
+ record.update_columns(updated_columns)
67
69
  end
68
70
  end
69
71
 
70
- class Coder # :nodoc:
71
- def initialize(key)
72
- raise ArgumentError unless key.is_a?(::OpenSSL::PKey::RSA)
73
- @key = key
74
- end
72
+ # Decrypts attributes of a specific model. This method is indended for migration purposes. It takes the same arguments
73
+ # as the `attr_encrypted` class method. It requires a private key to decrypt the data.
74
+ def decrypt_model_attributes(model, *attributes) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
75
+ fail ArgumentError, "ActiveRecord::Base expected, got #{model.inspect}" unless model <= ActiveRecord::Base
75
76
 
76
- # Encrypts a value with public key encryption. Keys should be defined in
77
- # environment.
78
- def encrypt(value)
79
- Base64.encode64(@key.public_encrypt(value)) if value
80
- end
81
- alias :dump :encrypt
77
+ options = attributes.extract_options!
78
+ encrypted_attributes = attributes.map(&:to_s) & model.column_names
79
+ coder = Coder.new extract_private_key(options)
80
+
81
+ model.find_each do |record|
82
+ updated_columns =
83
+ encrypted_attributes.each_with_object({}) do |attribute, values|
84
+ values[attribute] = coder.decrypt(record.read_attribute_before_type_cast attribute)
85
+ end
82
86
 
83
- # Decrypts a value with public key encryption. Keys should be defined in
84
- # environment.
85
- def decrypt(value)
86
- @key.private_decrypt(Base64.decode64(value)) if value
87
+ record.update_columns(updated_columns)
87
88
  end
88
- alias :load :decrypt
89
89
  end
90
+
91
+ extend KeyExtractor
90
92
  end
91
93
 
92
94
  ActiveSupport.on_load :active_record do
@@ -0,0 +1,37 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ module Cryptonite
5
+ class Coder # :nodoc:
6
+ HEADER = "Cryptonite #{VERSION}: "
7
+ BASE64_REGEXP = %r{([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)}
8
+ SEMVER_REGEXP = /
9
+ \bv?(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.
10
+ (?:0|[1-9][0-9]*)(?:-[\da-z\-]+(?:\.[\da-z\-]+)*)?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?\b
11
+ /ix
12
+ REGEXP = /^Cryptonite #{SEMVER_REGEXP}: (?<value>#{BASE64_REGEXP})$/
13
+
14
+ def initialize(key)
15
+ fail ArgumentError unless key.is_a?(::OpenSSL::PKey::RSA)
16
+ @key = key
17
+ end
18
+
19
+ # Encrypts a value with public key encryption. Keys should be defined in
20
+ # environment.
21
+ def encrypt(value)
22
+ return unless value
23
+ fail ArgumentError, 'Value is already encrypted' if value.match(REGEXP)
24
+ HEADER + Base64.strict_encode64(@key.public_encrypt(value))
25
+ end
26
+ alias_method :dump, :encrypt
27
+
28
+ # Decrypts a value with public key encryption. Keys should be defined in
29
+ # environment.
30
+ def decrypt(value)
31
+ return unless value
32
+ fail ArgumentError, 'Value is not encrypted' unless value.match(REGEXP)
33
+ @key.private_decrypt(Base64.strict_decode64(Regexp.last_match(:value)))
34
+ end
35
+ alias_method :load, :decrypt
36
+ end
37
+ end
@@ -0,0 +1,50 @@
1
+ module Cryptonite
2
+ module KeyExtractor # :nodoc:
3
+ private
4
+
5
+ # Extracts public key from options or the environment.
6
+ def extract_public_key(options)
7
+ extract_key(options[:public_key] || options[:key_pair] || ENV['PUBLIC_KEY'])
8
+ end
9
+
10
+ # Extracts private key from options or the environment.
11
+ def extract_private_key(options)
12
+ extract_key(
13
+ options[:private_key] || options[:key_pair] || ENV['PRIVATE_KEY'],
14
+ options[:private_key_password] || ENV['PRIVATE_KEY_PASSWORD']
15
+ )
16
+ end
17
+
18
+ # Retrives an RSA key with multiple ways.
19
+ def extract_key(key, password = nil)
20
+ return nil unless key
21
+
22
+ case key
23
+ when Proc then extract_key_from_proc(key, password)
24
+ when Symbol then extract_key_from_method(key, password)
25
+ when ::OpenSSL::PKey::RSA then key
26
+ else
27
+ key = retrieve_key_string_from_stream(key)
28
+ return ::OpenSSL::PKey::RSA.new(key) if password.nil?
29
+ ::OpenSSL::PKey::RSA.new(key, password.to_s)
30
+ end
31
+ end
32
+
33
+ # Retrives an RSA key with a `proc` block.
34
+ def extract_key_from_proc(proc, password = nil)
35
+ extract_key(proc.call, password)
36
+ end
37
+
38
+ # Retrives an RSA key with a method symbol.
39
+ def extract_key_from_method(method, password = nil)
40
+ extract_key(@instance.send(method), password)
41
+ end
42
+
43
+ # Retrives a key string from a stream.
44
+ def retrieve_key_string_from_stream(stream)
45
+ return stream.read if stream.respond_to?(:read)
46
+ return File.read(stream) if stream.to_s !~ /^-+BEGIN .* KEY-+$/
47
+ stream
48
+ end
49
+ end
50
+ end
@@ -1,3 +1,3 @@
1
1
  module Cryptonite
2
- VERSION = "0.0.4"
2
+ VERSION = '0.1.0'
3
3
  end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ require 'cryptonite/coder'
4
+
5
+ describe Cryptonite::Coder do
6
+ context 'with private key' do
7
+ subject do
8
+ Cryptonite::Coder.new(PRIVATE_FIXTURE_KEY)
9
+ end
10
+
11
+ it 'encrypts a value' do
12
+ value = SecureRandom.hex(16)
13
+
14
+ expect(subject.encrypt value).not_to eq(value)
15
+ end
16
+
17
+ it 'decrypts a value' do
18
+ value = SecureRandom.hex(16)
19
+ encrypted_value = Cryptonite::Coder::HEADER + Base64.strict_encode64(PUBLIC_FIXTURE_KEY.public_encrypt(value))
20
+
21
+ expect(subject.decrypt encrypted_value).to eq(value)
22
+ end
23
+
24
+ it 'handles nil values' do
25
+ expect(subject.encrypt nil).to be_nil
26
+ expect(subject.decrypt nil).to be_nil
27
+ end
28
+ end
29
+
30
+ context 'with public key only' do
31
+ subject do
32
+ Cryptonite::Coder.new(PUBLIC_FIXTURE_KEY)
33
+ end
34
+
35
+ it 'encrypts a value' do
36
+ value = SecureRandom.hex(16)
37
+
38
+ expect(subject.encrypt value).not_to eq(value)
39
+ end
40
+
41
+ it 'cannot decrypt a value' do
42
+ value = SecureRandom.hex(16)
43
+ encrypted_value = Cryptonite::Coder::HEADER + Base64.strict_encode64(PUBLIC_FIXTURE_KEY.public_encrypt(value))
44
+
45
+ expect { subject.decrypt encrypted_value }.to raise_error(OpenSSL::PKey::RSAError)
46
+ end
47
+
48
+ it 'handles nil values' do
49
+ expect(subject.encrypt nil).to be_nil
50
+ expect(subject.decrypt nil).to be_nil
51
+ end
52
+ end
53
+ end
@@ -12,21 +12,22 @@ describe Cryptonite do
12
12
  database: ':memory:')
13
13
 
14
14
  ::ActiveRecord::Schema.define do
15
- create_table :sensitive_data, :force => true do |t|
15
+ create_table :sensitive_data, force: true do |t|
16
16
  t.column :secret, :text
17
+ t.column :another_secret, :text
17
18
  end
18
19
  end
19
20
  end
20
21
 
21
- subject {
22
+ subject do
22
23
  Class.new(ActiveRecord::Base) do
23
24
  def self.table_name
24
- "sensitive_data"
25
- end
25
+ 'sensitive_data'
26
+ end
26
27
  end
27
- }
28
+ end
28
29
 
29
- context "with private key" do
30
+ context 'with private key' do
30
31
  before do
31
32
  subject.tap { |obj| obj.attr_encrypted :secret, key_pair: PRIVATE_FIXTURE_KEY }
32
33
  end
@@ -35,9 +36,7 @@ describe Cryptonite do
35
36
  secret = SecureRandom.hex(16)
36
37
 
37
38
  subject.new(secret: secret).tap do |instance|
38
- expect(
39
- instance.instance_variable_get(:@attributes).send(:fetch, 'secret').serialized_value
40
- ).not_to eq(secret)
39
+ expect(instance.typecasted_attribute_value 'secret').not_to eq(secret)
41
40
  end
42
41
  end
43
42
 
@@ -45,23 +44,21 @@ describe Cryptonite do
45
44
  secret = SecureRandom.hex(16)
46
45
 
47
46
  subject.new.tap do |instance|
48
- instance.instance_variable_get(:@attributes).send(:fetch, 'secret').value = Base64.encode64(PUBLIC_FIXTURE_KEY.public_encrypt(secret))
47
+ instance.raw_write_attribute('secret', Cryptonite::Coder.new(PUBLIC_FIXTURE_KEY).encrypt(secret))
49
48
 
50
- expect(instance.secret).to eq(secret)
49
+ expect(instance.read_attribute_before_type_cast 'secret').to eq(secret)
51
50
  end
52
51
  end
53
52
 
54
53
  it 'handles nil values' do
55
54
  subject.new(secret: nil).tap do |instance|
56
- expect(
57
- instance.instance_variable_get(:@attributes).send(:fetch, 'secret').serialized_value
58
- ).to be_nil
55
+ expect(instance.typecasted_attribute_value 'secret').to be_nil
59
56
  end
60
57
 
61
58
  subject.new.tap do |instance|
62
- instance.instance_variable_get(:@attributes).send(:fetch, 'secret').value = nil
59
+ instance.raw_write_attribute('secret', nil)
63
60
 
64
- expect(instance.secret).to be_nil
61
+ expect(instance.read_attribute_before_type_cast 'secret').to be_nil
65
62
  end
66
63
  end
67
64
 
@@ -69,16 +66,13 @@ describe Cryptonite do
69
66
  secret = SecureRandom.hex(16)
70
67
 
71
68
  subject.create(secret: secret).reload.tap do |instance|
72
- expect(
73
- instance.instance_variable_get(:@attributes).send(:fetch, 'secret').serialized_value
74
- ).not_to eq(secret)
75
-
76
- expect(instance.secret).to eq(secret)
69
+ expect(instance.typecasted_attribute_value 'secret').not_to eq(secret)
70
+ expect(instance.read_attribute_before_type_cast 'secret').to eq(secret)
77
71
  end
78
72
  end
79
73
  end
80
74
 
81
- context "with public key only" do
75
+ context 'with public key only' do
82
76
  before do
83
77
  subject.tap { |obj| obj.attr_encrypted :secret, public_key: PUBLIC_FIXTURE_KEY }
84
78
  end
@@ -87,9 +81,7 @@ describe Cryptonite do
87
81
  secret = SecureRandom.hex(16)
88
82
 
89
83
  subject.new(secret: secret).tap do |instance|
90
- expect(
91
- instance.instance_variable_get(:@attributes).send(:fetch, 'secret').serialized_value
92
- ).not_to eq(secret)
84
+ expect(instance.typecasted_attribute_value 'secret').not_to eq(secret)
93
85
  end
94
86
  end
95
87
 
@@ -97,24 +89,54 @@ describe Cryptonite do
97
89
  secret = SecureRandom.hex(16)
98
90
 
99
91
  subject.new.tap do |instance|
100
- instance.instance_variable_get(:@attributes).send(:fetch, 'secret').value = Base64.encode64(PUBLIC_FIXTURE_KEY.public_encrypt(secret))
92
+ instance.raw_write_attribute('secret', Cryptonite::Coder.new(PUBLIC_FIXTURE_KEY).encrypt(secret))
101
93
 
102
- expect{ instance.secret }.to raise_error OpenSSL::PKey::RSAError
94
+ expect { instance.read_attribute_before_type_cast 'secret' }.to raise_error OpenSSL::PKey::RSAError
103
95
  end
104
96
  end
105
97
 
106
98
  it 'handles nil values' do
107
99
  subject.new(secret: nil).tap do |instance|
108
- expect(
109
- instance.instance_variable_get(:@attributes).send(:fetch, 'secret').serialized_value
110
- ).to be_nil
100
+ expect(instance.typecasted_attribute_value 'secret').to be_nil
111
101
  end
112
102
 
113
103
  subject.new.tap do |instance|
114
- instance.instance_variable_get(:@attributes).send(:fetch, 'secret').value = nil
104
+ instance.raw_write_attribute('secret', nil)
115
105
 
116
- expect(instance.secret).to be_nil
106
+ expect(instance.read_attribute_before_type_cast 'secret').to be_nil
117
107
  end
118
108
  end
119
109
  end
110
+
111
+ context 'during upwards migration' do
112
+ before do
113
+ @secret = SecureRandom.hex(16)
114
+ subject.create(secret: @secret)
115
+
116
+ subject.tap { |obj| obj.attr_encrypted :secret, key_pair: PRIVATE_FIXTURE_KEY }
117
+ end
118
+
119
+ it 'encrypts field in database' do
120
+ expect { subject.last.read_attribute_before_type_cast 'secret' }.to raise_error ArgumentError
121
+
122
+ Cryptonite.encrypt_model_attributes(subject, :secret, public_key: PUBLIC_FIXTURE_KEY)
123
+
124
+ expect(subject.last.read_attribute_before_type_cast 'secret').to eq(@secret)
125
+ end
126
+ end
127
+
128
+ context 'during downwards migration' do
129
+ before do
130
+ @secret = SecureRandom.hex(16)
131
+ subject.dup.tap { |obj| obj.attr_encrypted :secret, key_pair: PRIVATE_FIXTURE_KEY }.create(secret: @secret)
132
+ end
133
+
134
+ it 'decrypts field in database' do
135
+ expect(subject.last.read_attribute 'secret').not_to eq(@secret)
136
+
137
+ Cryptonite.decrypt_model_attributes(subject, :secret, key_pair: PRIVATE_FIXTURE_KEY)
138
+
139
+ expect(subject.last.read_attribute 'secret').to eq(@secret)
140
+ end
141
+ end
120
142
  end
@@ -88,6 +88,8 @@ RSpec.configure do |config|
88
88
  =end
89
89
 
90
90
  # Configure public key encryption for the Cryptonite concern.
91
- ::PUBLIC_FIXTURE_KEY = OpenSSL::PKey::RSA.new(File.read(File.expand_path('../fixtures/keys/public.pem', __FILE__)))
92
- ::PRIVATE_FIXTURE_KEY = OpenSSL::PKey::RSA.new(File.read(File.expand_path('../fixtures/keys/private.pem', __FILE__)), 'test')
91
+ ::PUBLIC_FIXTURE_KEY =
92
+ OpenSSL::PKey::RSA.new(File.read(File.expand_path('../fixtures/keys/public.pem', __FILE__)))
93
+ ::PRIVATE_FIXTURE_KEY =
94
+ OpenSSL::PKey::RSA.new(File.read(File.expand_path('../fixtures/keys/private.pem', __FILE__)), 'test')
93
95
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cryptonite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GaggleAMP
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-16 00:00:00.000000000 Z
11
+ date: 2015-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: activerecord
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -115,6 +129,7 @@ extra_rdoc_files: []
115
129
  files:
116
130
  - ".gitignore"
117
131
  - ".rspec"
132
+ - ".rubocop.yml"
118
133
  - ".travis.yml"
119
134
  - Gemfile
120
135
  - LICENSE.txt
@@ -122,7 +137,10 @@ files:
122
137
  - Rakefile
123
138
  - cryptonite.gemspec
124
139
  - lib/cryptonite.rb
140
+ - lib/cryptonite/coder.rb
141
+ - lib/cryptonite/key_extractor.rb
125
142
  - lib/cryptonite/version.rb
143
+ - spec/cryptonite/coder_spec.rb
126
144
  - spec/cryptonite_spec.rb
127
145
  - spec/fixtures/keys/private.pem
128
146
  - spec/fixtures/keys/public.pem
@@ -152,6 +170,7 @@ signing_key:
152
170
  specification_version: 4
153
171
  summary: Enables the encryption of specific ActiveRecord attributes.
154
172
  test_files:
173
+ - spec/cryptonite/coder_spec.rb
155
174
  - spec/cryptonite_spec.rb
156
175
  - spec/fixtures/keys/private.pem
157
176
  - spec/fixtures/keys/public.pem