pivotal-sentry 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/CHANGELOG +58 -0
- data/MIT-LICENSE +20 -0
- data/README +94 -0
- data/RUNNING_UNIT_TESTS +42 -0
- data/Rakefile +192 -0
- data/VERSION +1 -0
- data/init.rb +1 -0
- data/lib/active_record/sentry.rb +94 -0
- data/lib/sentry.rb +46 -0
- data/lib/sentry/asymmetric_sentry.rb +144 -0
- data/lib/sentry/asymmetric_sentry_callback.rb +17 -0
- data/lib/sentry/sha_sentry.rb +41 -0
- data/lib/sentry/symmetric_sentry.rb +79 -0
- data/lib/sentry/symmetric_sentry_callback.rb +17 -0
- data/tasks/sentry.rake +9 -0
- data/test/abstract_unit.rb +44 -0
- data/test/asymmetric_sentry_callback_test.rb +72 -0
- data/test/asymmetric_sentry_test.rb +88 -0
- data/test/database.yml +18 -0
- data/test/fixtures/user.rb +26 -0
- data/test/fixtures/users.yml +10 -0
- data/test/keys/encrypted_private +12 -0
- data/test/keys/encrypted_public +4 -0
- data/test/keys/private +9 -0
- data/test/keys/public +4 -0
- data/test/schema.rb +10 -0
- data/test/sha_sentry_test.rb +35 -0
- data/test/symmetric_sentry_callback_test.rb +38 -0
- data/test/symmetric_sentry_test.rb +37 -0
- data/test/tests.rb +2 -0
- metadata +92 -0
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
|