pivotal-sentry 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/sentry.rb ADDED
@@ -0,0 +1,46 @@
1
+ #Copyright (c) 2005 Rick Olson
2
+ #
3
+ #Permission is hereby granted, free of charge, to any person obtaining
4
+ #a copy of this software and associated documentation files (the
5
+ #"Software"), to deal in the Software without restriction, including
6
+ #without limitation the rights to use, copy, modify, merge, publish,
7
+ #distribute, sublicense, and/or sell copies of the Software, and to
8
+ #permit persons to whom the Software is furnished to do so, subject to
9
+ #the following conditions:
10
+ #
11
+ #The above copyright notice and this permission notice shall be
12
+ #included in all copies or substantial portions of the Software.
13
+ #
14
+ #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ #EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ #MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ #NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ #LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ #OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ #WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'openssl'
23
+ require 'base64'
24
+ require 'sentry/symmetric_sentry'
25
+ require 'sentry/asymmetric_sentry'
26
+ require 'sentry/sha_sentry'
27
+ require 'sentry/symmetric_sentry_callback'
28
+ require 'sentry/asymmetric_sentry_callback'
29
+
30
+ module Sentry
31
+ class NoKeyError < StandardError
32
+ end
33
+ class NoPublicKeyError < StandardError
34
+ end
35
+ class NoPrivateKeyError < StandardError
36
+ end
37
+ end
38
+
39
+ begin
40
+ require 'active_record/sentry'
41
+ ActiveRecord::Base.class_eval do
42
+ include ActiveRecord::Sentry
43
+ end
44
+ rescue NameError
45
+ nil
46
+ end
@@ -0,0 +1,144 @@
1
+ module Sentry
2
+ class AsymmetricSentry
3
+ attr_reader :private_key_file
4
+ attr_reader :public_key_file
5
+ attr_accessor :symmetric_algorithm
6
+ @@default_private_key_file = nil
7
+ @@default_public_key_file = nil
8
+ @@default_symmetric_algorithm = nil
9
+
10
+ # available options:
11
+ # * <tt>:private_key_file</tt> - encrypted private key file
12
+ # * <tt>:public_key_file</tt> - public key file
13
+ # * <tt>:symmetric_algorithm</tt> - algorithm to use for SymmetricSentry
14
+ def initialize(options = {})
15
+ @public_key = @private_key = nil
16
+ private_key_file = options[:private_key_file]
17
+ public_key_file = options[:public_key_file] || @@default_public_key_file
18
+ @symmetric_algorithm = options[:symmetric_algorithm] || @@default_symmetric_algorithm
19
+ end
20
+
21
+ def encrypt(data)
22
+ raise NoPublicKeyError unless public?
23
+ public_rsa.public_encrypt(data)
24
+ end
25
+
26
+ def encrypt_to_base64(data)
27
+ Base64.encode64(encrypt(data))
28
+ end
29
+
30
+ def decrypt(data, key = nil)
31
+ raise NoPrivateKeyError unless private?
32
+ private_rsa(key).private_decrypt(data)
33
+ end
34
+
35
+ def decrypt_from_base64(data, key = nil)
36
+ decrypt(Base64.decode64(data), key)
37
+ end
38
+
39
+ def private_key_file=(file)
40
+ @private_key_file = file and load_private_key
41
+ end
42
+
43
+ def public_key_file=(file)
44
+ @public_key_file = file and load_public_key
45
+ end
46
+
47
+ def public?
48
+ return true unless @public_key.nil?
49
+ load_public_key and return @public_key
50
+ end
51
+
52
+ def private?
53
+ return true unless @private_key.nil?
54
+ load_private_key and return @private_key
55
+ end
56
+
57
+ class << self
58
+ # * <tt>:key</tt> - secret password
59
+ # * <tt>:symmetric_algorithm</tt> - symmetrical algorithm to use
60
+ def save_random_rsa_key(private_key_file, public_key_file, options = {})
61
+ rsa = OpenSSL::PKey::RSA.new(512)
62
+ public_key = rsa.public_key
63
+ private_key = options[:key].to_s.empty? ?
64
+ rsa.to_s :
65
+ SymmetricSentry.new(:algorithm => options[:symmetric_algorithm]).encrypt_to_base64(rsa.to_s, options[:key])
66
+ File.open(public_key_file, 'w') { |f| f.write(public_key) }
67
+ File.open(private_key_file, 'w') { |f| f.write(private_key) }
68
+ end
69
+
70
+ def encrypt(data)
71
+ self.new.encrypt(data)
72
+ end
73
+
74
+ def encrypt_to_base64(data)
75
+ self.new.encrypt_to_base64(data)
76
+ end
77
+
78
+ def decrypt(data, key = nil)
79
+ self.new.decrypt(data, key)
80
+ end
81
+
82
+ def decrypt_from_base64(data, key = nil)
83
+ self.new.decrypt_from_base64(data, key)
84
+ end
85
+
86
+ # cattr_accessor would be lovely
87
+ def default_private_key_file
88
+ @@default_private_key_file
89
+ end
90
+
91
+ def default_private_key_file=(value)
92
+ @@default_private_key_file = value
93
+ end
94
+
95
+ def default_public_key_file
96
+ @@default_public_key_file
97
+ end
98
+
99
+ def default_public_key_file=(value)
100
+ @@default_public_key_file = value
101
+ end
102
+
103
+ def default_symmetric_algorithm
104
+ @@default_symmetric_algorithm
105
+ end
106
+
107
+ def default_symmetric_algorithm=(value)
108
+ @@default_symmetric_algorithm = value
109
+ end
110
+ end
111
+
112
+ private
113
+ def encryptor
114
+ @encryptor ||= SymmetricSentry.new(:algorithm => @symmetric_algorithm)
115
+ end
116
+
117
+ def load_private_key
118
+ @private_rsa = nil
119
+ @private_key_file ||= @@default_private_key_file
120
+ if @private_key_file and File.file?(@private_key_file)
121
+ @private_key = File.open(@private_key_file) { |f| f.read }
122
+ end
123
+ end
124
+
125
+ def load_public_key
126
+ @public_rsa = nil
127
+ @public_key_file ||= @@default_public_key_file
128
+ if @public_key_file and File.file?(@public_key_file)
129
+ @public_key = File.open(@public_key_file) { |f| f.read }
130
+ end
131
+ end
132
+
133
+ # retrieves private rsa from encrypted private key
134
+ def private_rsa(key = nil)
135
+ return @private_rsa ||= OpenSSL::PKey::RSA.new(@private_key) unless key
136
+ OpenSSL::PKey::RSA.new(encryptor.decrypt_from_base64(@private_key, key))
137
+ end
138
+
139
+ # retrieves public rsa
140
+ def public_rsa
141
+ @public_rsa ||= OpenSSL::PKey::RSA.new(@public_key)
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,17 @@
1
+ module Sentry
2
+ class AsymmetricSentryCallback
3
+ def initialize(attr_name)
4
+ @attr_name = attr_name
5
+ end
6
+
7
+ # Performs encryption on before_validation Active Record callback
8
+ #def before_validation(model)
9
+ # return if model.send(@attr_name).blank?
10
+ # model.send("crypted_#{@attr_name}=", AsymmetricSentry.encrypt_to_base64(model.send(@attr_name)))
11
+ #end
12
+
13
+ #def after_save(model)
14
+ # model.send("#{@attr_name}=", nil)
15
+ #end
16
+ end
17
+ end
@@ -0,0 +1,41 @@
1
+ require 'digest/sha1'
2
+ module Sentry
3
+ class ShaSentry
4
+ @@salt = 'salt'
5
+ attr_accessor :salt
6
+
7
+ # Encrypts data using SHA.
8
+ def encrypt(data)
9
+ self.class.encrypt(data + salt.to_s)
10
+ end
11
+
12
+ # Initialize the class.
13
+ # Used by ActiveRecord::Base#generates_crypted to set up as a callback object for a model
14
+ def initialize(attribute = nil)
15
+ @attribute = attribute
16
+ end
17
+
18
+ # Performs encryption on before_validation Active Record callback
19
+ def before_validation(model)
20
+ return unless model.send(@attribute)
21
+ model.send("crypted_#{@attribute}=", encrypt(model.send(@attribute)))
22
+ end
23
+
24
+ class << self
25
+ # Gets the class salt value used when encrypting
26
+ def salt
27
+ @@salt
28
+ end
29
+
30
+ # Sets the class salt value used when encrypting
31
+ def salt=(value)
32
+ @@salt = value
33
+ end
34
+
35
+ # Encrypts the data
36
+ def encrypt(data)
37
+ Digest::SHA1.hexdigest(data + @@salt)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,79 @@
1
+ module Sentry
2
+ class SymmetricSentry
3
+ @@default_algorithm = 'DES-EDE3-CBC'
4
+ @@default_key = nil
5
+ attr_accessor :algorithm
6
+ def initialize(options = {})
7
+ @algorithm = options[:algorithm] || @@default_algorithm
8
+ end
9
+
10
+ def encrypt(data, key = nil)
11
+ key = check_for_key!(key)
12
+ des = encryptor
13
+ des.encrypt(key)
14
+ data = des.update(data)
15
+ data << des.final
16
+ end
17
+
18
+ def encrypt_to_base64(text, key = nil)
19
+ Base64.encode64(encrypt(text, key))
20
+ end
21
+
22
+ def decrypt(data, key = nil)
23
+ key = check_for_key!(key)
24
+ des = encryptor
25
+ des.decrypt(key)
26
+ text = des.update(data)
27
+ text << des.final
28
+ end
29
+
30
+ def decrypt_from_base64(text, key = nil)
31
+ decrypt(Base64.decode64(text), key)
32
+ end
33
+
34
+ class << self
35
+ def default_algorithm
36
+ @@default_algorithm
37
+ end
38
+
39
+ def default_algorithm=(value)
40
+ @@default_algorithm = value
41
+ end
42
+
43
+ def default_key
44
+ @@default_key
45
+ end
46
+
47
+ def default_key=(value)
48
+ @@default_key = value
49
+ end
50
+
51
+ def encrypt(data, key = nil)
52
+ self.new.encrypt(data, key)
53
+ end
54
+
55
+ def encrypt_to_base64(text, key = nil)
56
+ self.new.encrypt_to_base64(text, key)
57
+ end
58
+
59
+ def decrypt(data, key = nil)
60
+ self.new.decrypt(data, key)
61
+ end
62
+
63
+ def decrypt_from_base64(text, key = nil)
64
+ self.new.decrypt_from_base64(text, key)
65
+ end
66
+ end
67
+
68
+ private
69
+ def encryptor
70
+ @encryptor ||= OpenSSL::Cipher::Cipher.new(@algorithm)
71
+ end
72
+
73
+ def check_for_key!(key)
74
+ valid_key = key || @@default_key
75
+ raise Sentry::NoKeyError if valid_key.nil?
76
+ valid_key
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,17 @@
1
+ module Sentry
2
+ class SymmetricSentryCallback
3
+ def initialize(attr_name)
4
+ @attr_name = attr_name
5
+ end
6
+
7
+ ## Performs encryption on before_validation Active Record callback
8
+ #def before_validation(model)
9
+ # return if model.send(@attr_name).blank?
10
+ # model.send("crypted_#{@attr_name}=", SymmetricSentry.encrypt_to_base64(model.send(@attr_name)))
11
+ #end
12
+
13
+ #def after_save(model)
14
+ # #model.send("#{@attr_name}=", nil)
15
+ #end
16
+ end
17
+ end
data/tasks/sentry.rake ADDED
@@ -0,0 +1,9 @@
1
+ require 'sentry'
2
+
3
+ desc "Creates a private/public key for asymmetric encryption: rake sentry_key PUB=/path/to/public.key PRIV=/path/to/priv.key [KEY=secret]"
4
+ task :sentry_key do
5
+ Sentry::AsymmetricSentry.save_random_rsa_key(
6
+ ENV['PRIV'] || 'private.key',
7
+ ENV['PUB'] || 'public.key',
8
+ :key => ENV['KEY'])
9
+ end
@@ -0,0 +1,44 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'rubygems'
4
+ require 'test/unit'
5
+ require 'active_record'
6
+ require 'active_record/fixtures'
7
+ require 'active_support/test_case'
8
+ #require 'active_support/binding_of_caller'
9
+ #require 'active_support/breakpoint'
10
+ require "#{File.dirname(__FILE__)}/../lib/sentry"
11
+
12
+ config_location = File.dirname(__FILE__) + '/database.yml'
13
+
14
+ config = YAML::load(IO.read(config_location))
15
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
16
+ ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'mysql'])
17
+ ActiveRecord::Base.configurations["test"] = "lolcatz"
18
+
19
+ load(File.dirname(__FILE__) + "/schema.rb")
20
+
21
+ class ActiveSupport::TestCase #:nodoc:
22
+ include ActiveRecord::TestFixtures
23
+ #def create_fixtures(*table_names)
24
+ # if block_given?
25
+ # Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, table_names) { yield }
26
+ # else
27
+ # Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, table_names)
28
+ # end
29
+ #end
30
+
31
+ self.use_instantiated_fixtures = false
32
+ self.use_transactional_fixtures = true
33
+ end
34
+
35
+ def create_fixtures(*table_names, &block)
36
+ Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, table_names, {}, &block)
37
+ end
38
+
39
+
40
+
41
+ ActiveSupport::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
42
+ ActiveSupport::TestCase.use_instantiated_fixtures = true
43
+ ActiveSupport::TestCase.use_transactional_fixtures = (ENV['AR_TX_FIXTURES'] == "yes")
44
+ $LOAD_PATH.unshift(ActiveSupport::TestCase.fixture_path)
@@ -0,0 +1,72 @@
1
+ require 'abstract_unit'
2
+ require 'fixtures/user'
3
+
4
+ class AsymmetricSentryCallbackTest < ActiveSupport::TestCase
5
+ fixtures :users
6
+
7
+ def setup
8
+ super
9
+ @str = 'sentry'
10
+ @key = 'secret'
11
+ @public_key_file = File.dirname(__FILE__) + '/keys/public'
12
+ @private_key_file = File.dirname(__FILE__) + '/keys/private'
13
+ @encrypted_public_key_file = File.dirname(__FILE__) + '/keys/encrypted_public'
14
+ @encrypted_private_key_file = File.dirname(__FILE__) + '/keys/encrypted_private'
15
+
16
+ @orig = 'sentry'
17
+ Sentry::AsymmetricSentry.default_public_key_file = @public_key_file
18
+ Sentry::AsymmetricSentry.default_private_key_file = @private_key_file
19
+ Sentry::SymmetricSentry.default_key = @key
20
+ end
21
+
22
+ def test_should_encrypt_creditcard
23
+ u = User.create :login => 'jones'
24
+ u.creditcard = @orig
25
+ assert u.save
26
+ assert !u.crypted_creditcard.empty?
27
+ end
28
+
29
+ def test_should_deal_with_before_typecast
30
+ u = User.create :login => 'jones'
31
+ u.creditcard = "123123"
32
+ assert_equal "123123", u.creditcard_before_type_cast
33
+ assert u.save
34
+ u.reload
35
+ assert_equal "123123", u.creditcard_before_type_cast
36
+ end
37
+
38
+ def test_should_decrypt_creditcard
39
+ assert_equal @orig, users(:user_1).creditcard
40
+ end
41
+
42
+ def test_should_not_decrypt_encrypted_creditcard_with_invalid_key
43
+ assert_nil users(:user_2).creditcard
44
+ assert_nil users(:user_2).creditcard(@key)
45
+ use_encrypted_keys
46
+ assert_nil users(:user_1).creditcard
47
+ end
48
+
49
+ def test_should_not_decrypt_encrypted_creditcard
50
+ use_encrypted_keys
51
+ assert_nil users(:user_2).creditcard
52
+ assert_nil users(:user_2).creditcard('other secret')
53
+ end
54
+
55
+ def test_should_encrypt_encrypted_creditcard
56
+ use_encrypted_keys
57
+ u = User.create :login => 'jones'
58
+ u.creditcard = @orig
59
+ assert u.save
60
+ assert !u.crypted_creditcard.empty?
61
+ end
62
+
63
+ def test_should_decrypt_encrypted_creditcard
64
+ use_encrypted_keys
65
+ assert_equal @orig, users(:user_2).creditcard(@key)
66
+ end
67
+
68
+ def use_encrypted_keys
69
+ Sentry::AsymmetricSentry.default_public_key_file = @encrypted_public_key_file
70
+ Sentry::AsymmetricSentry.default_private_key_file = @encrypted_private_key_file
71
+ end
72
+ end