cryptonite 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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