Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Initial revision. Modification of Rick Olson's original sentry plugin…

…. Stills needs more refactoring of Rick's tests and documentation to reflect the new string encryption usage.
  • Loading branch information...
commit 48a2ff0fc77cc66270df852502395b53d3f04c40 0 parents
Aaron Pfeifer obrie authored
58 CHANGELOG
... ... @@ -0,0 +1,58 @@
  1 +*0.3.1* (7 Jan 2006)
  2 +
  3 +* removed useless breakpoint [Solomon White]
  4 +
  5 +*0.3* (29 Oct 2005)
  6 +
  7 +* added rake task for generating asymmetric keys
  8 +* Switch to migrations and schema for testing setup
  9 +
  10 +*0.2.9* (18 Sep 2005)
  11 +
  12 +* First RubyForge release.
  13 +
  14 +*0.2.8* (17 Sep 2005)
  15 +
  16 +* Added Active Record unit tests
  17 +
  18 +*0.2.7* (17 Sep 2005)
  19 +
  20 +* Added rdocs and stubs for AR unit tests
  21 +
  22 +*0.2.6* (2 Aug 2005)
  23 +
  24 +* Fixed generates_crypted so it adds attribute accessors
  25 +
  26 +*0.2.5* (27 Jul 2005)
  27 +
  28 +* Set ActiveRecord callback objects to only encrypt fields when they are not empty.
  29 +
  30 +*0.2.4* (11 Jul 2005)
  31 +
  32 +* Split ActiveRecord callback methods into their own classes.
  33 +* Set AR virtual columns to fail silently on errors.
  34 +
  35 +*0.2.3* (11 Jul 2005)
  36 +
  37 +* Added ActiveRecord callback objects for SymmetricSentry and AsymmetricSentry. +one_way_encrypt+ is depreciated.
  38 +* Readme doc added too
  39 +
  40 +*0.2.1* (9 Jul 2005)
  41 +
  42 +* vastly simplified one_way_encrypt at danp's suggestion. Use this in your model to try it out:
  43 +
  44 + +one_way_encrypt :password*
  45 +
  46 + That generates an SHA hash of model.password to model.crypted_password which is saved in the DB.
  47 + model.password is a virtual field. Continue using validates_confirmation_of for confirmation.
  48 +
  49 +
  50 +*0.2* (9 Jul 2005)
  51 +
  52 +* added ActiveRecord::Base#one_way_encrypt class method to hash passwords with SHA
  53 +* Renamed core classes to SymmetricSentry and AsymmetricSentry
  54 +* Test Suite added
  55 +
  56 +*0.1*
  57 +
  58 +* Initial Import
20 MIT-LICENSE
... ... @@ -0,0 +1,20 @@
  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.
94 README
... ... @@ -0,0 +1,94 @@
  1 += Sentry lib - painless encryption library
  2 +
  3 +Sentry is a simple wrapper around the mostly undocumented OpenSSL encryption classes.
  4 +For now, look at the pseudo test cases in sentry.rb until I can get more examples written out.
  5 +
  6 +== Resources
  7 +
  8 +Install
  9 +
  10 +* gem install sentry
  11 +
  12 +Rubyforge project
  13 +
  14 +* http://rubyforge.org/projects/sentry
  15 +
  16 +RDocs
  17 +
  18 +* http://sentry.rubyforge.org
  19 +
  20 +Subversion
  21 +
  22 +* http://techno-weenie.net/svn/projects/sentry
  23 +
  24 +Collaboa
  25 +
  26 +* http://collaboa.techno-weenie.net/repository/browse/sentry
  27 +
  28 +== Using with ActiveRecord
  29 +
  30 +I wrote this for the purpose of encrypting ActiveRecord attributes. Just <tt>require 'sentry'</tt>, and some new
  31 +class methods will be available to you:
  32 +
  33 +=== generates_crypted
  34 +
  35 + generates_crypted :password, :mode => :sha | :symmetric | :asymmetric
  36 +
  37 +This is the generic class method to use. Default mode is :sha.
  38 +
  39 +=== generates_crypted_hash_of
  40 +
  41 + generates_crypted_hash_of :password
  42 +
  43 +This is a shortcut for using SHA encryption. No different than specifying <tt>generates_crypted :password</tt>. In the above
  44 +example, model.password is a virtual field, and the SHA hash is saved to model.crypted_password
  45 +
  46 +=== asymmetrically_encrypts
  47 +
  48 + asymmetrically_encrypts :password
  49 +
  50 +This is a shortcut for using an asymmetrical algorithm with a private/public key file. To use this, generate a public and
  51 +private key with Sentry::AsymmetricalSentry.save_random_rsa_key(private_key_file, public_key_file). If you want to encrypt the
  52 +private key file with a symmetrical algorithm, pass a secret key (neither the key nor the decrypted value will be stored).
  53 +
  54 + Sentry::AsymmetricSentry.save_random_rsa_key(private_key_file, public_key_file, :key => 'secret_password')
  55 +
  56 +What that does, is requires you to pass in that same secret password when accesing the method.
  57 +
  58 + class Model < ActiveRecord::Base
  59 + generates_crypted :password, :mode => :asymmetric
  60 + end
  61 +
  62 + model.password = '5234523453425'
  63 + model.save # password is encrypted and saved to crypted_password in the database,
  64 + # model.password is cleared and becomes a virtual field.
  65 + model.password('secret_password')
  66 + => '5234523453425'
  67 +
  68 +The public and private key file names can be set in config/environment.rb
  69 +
  70 + Sentry::AsymmetricSentry.default_public_key_file = "#{RAILS_ROOT}/config/public.key"
  71 + Sentry::AsymmetricSentry.default_private_key_file = "#{RAILS_ROOT}/config/private.key"
  72 +
  73 +If the private key was encrypted with the Sentry::AsymmetricalSentry#save_random_rsa_key, you must provide that same key
  74 +when accessing the AR model.
  75 +
  76 +=== symmetrically_encrypts
  77 +
  78 + symmetrically_encrypts :password
  79 +
  80 +This is a shortcut for using a symmetrical algorithm with a secret password to encrypt the field.
  81 +
  82 + class Model < ActiveRecord::Base
  83 + generates_crypted :password, :mode => :symmetric
  84 + end
  85 +
  86 + model.password = '5234523453425'
  87 + model.save # password is encrypted and saved to crypted_password in the database,
  88 + # model.password is cleared and becomes a virtual field.
  89 + model.password
  90 + => '5234523453425'
  91 +
  92 +The secret password can be set in config/environment.rb
  93 +
  94 + Sentry::SymmetricSentry.default_key = "secret_password"
42 RUNNING_UNIT_TESTS
... ... @@ -0,0 +1,42 @@
  1 +== Creating the test database
  2 +
  3 +The default name for the test databases is "encrypted_strings_plugin_test". If you
  4 +want to use another database name then be sure to update the connection
  5 +adapter setups you want to test with in test/database.yml.
  6 +
  7 +Make sure that you create database objects with the same user that you specified in i
  8 +database.yml otherwise (on Postgres, at least) tests for default values will fail.
  9 +
  10 +== Running with Rake
  11 +
  12 +The easiest way to run the unit tests is through Rake. The default task runs
  13 +the entire test suite for the sqlite adapter. You can also run the suite on just
  14 +one adapter by passing the DB environment variable.
  15 +
  16 + rake test DB=mysql
  17 +
  18 +For more information, checkout the full array of rake tasks with "rake -T"
  19 +
  20 +Rake can be found at http://rake.rubyforge.org
  21 +
  22 +== Running by hand
  23 +
  24 +Unit tests are located in test directory. If you only want to run a single test suite,
  25 +or don't want to bother with Rake, you can do so with something like:
  26 +
  27 + cd test; DB=mysql ruby base_test.rb
  28 +
  29 +That'll run the base suite using the MySQL adapter. Change the adapter
  30 +and test suite name as needed.
  31 +
  32 +== Faster tests
  33 +
  34 +If you are using a database that supports transactions, you can set the
  35 +"AR_TX_FIXTURES" environment variable to "yes" to use transactional fixtures.
  36 +This gives a very large speed boost. With rake:
  37 +
  38 + rake AR_TX_FIXTURES=yes
  39 +
  40 +Or, by hand:
  41 +
  42 + AR_TX_FIXTURES=yes ruby -I connections/native_sqlite3 base_test.rb
32 Rakefile
... ... @@ -0,0 +1,32 @@
  1 +require 'rake'
  2 +require File.join('rake', 'testtask')
  3 +require File.join('rake', 'rdoctask')
  4 +
  5 +desc 'Default: run unit tests.'
  6 +task :default => :test
  7 +
  8 +desc 'Test the encrypted_strings plugin.'
  9 +Rake::TestTask.new(:test) do |t|
  10 + # Dependency on other plugins requires this plugin to exist in an rails
  11 + # application
  12 + if (root_path = ENV['RAILS_ROOT']).nil?
  13 + root_path = File.dirname(File.expand_path(__FILE__))
  14 + while (boot_paths = Dir[File.join(root_path, 'config', 'boot{,.rb}')]).empty?
  15 + root_path = File.dirname(root_path)
  16 + end
  17 + end
  18 + Dir.chdir(root_path)
  19 +
  20 + t.libs << 'lib'
  21 + t.pattern = File.join(File.dirname(__FILE__), 'test', '**', '*_test.rb')
  22 + t.verbose = true
  23 +end
  24 +
  25 +desc 'Generate documentation for the encrypted_strings plugin.'
  26 +Rake::RDocTask.new(:rdoc) do |rdoc|
  27 + rdoc.rdoc_dir = 'rdoc'
  28 + rdoc.title = 'EncryptedStrings'
  29 + rdoc.options << '--line-numbers' << '--inline-source'
  30 + rdoc.rdoc_files.include('README')
  31 + rdoc.rdoc_files.include(File.join('lib', '**', '*.rb'))
  32 +end
5 init.rb
... ... @@ -0,0 +1,5 @@
  1 +require 'encrypted_strings'
  2 +
  3 +class ::Integer #:nodoc:
  4 + include PluginAWeek::CoreExtensions::String::EncryptedStrings
  5 +end
110 lib/encrypted_strings.rb
... ... @@ -0,0 +1,110 @@
  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 +
  25 +require File.join('encrypted_strings', 'encrypted_string')
  26 +require File.join('encrypted_strings', 'symmetrically_encrypted_string')
  27 +require File.join('encrypted_strings', 'asymmetrically_encrypted_string')
  28 +require File.join('encrypted_strings', 'sha_encrypted_string')
  29 +
  30 +class NoKeyError < StandardError
  31 +end
  32 +
  33 +class NoPublicKeyError < StandardError
  34 +end
  35 +
  36 +class NoPrivateKeyError < StandardError
  37 +end
  38 +
  39 +module PluginAWeek #:nodoc:
  40 + module CoreExtensions #:nodoc:
  41 + module String #:nodoc:
  42 + module EncryptedStrings
  43 + #
  44 + #
  45 + def encrypt(mode = :sha, options = {})
  46 + options[:encrypt] = true
  47 +
  48 + case mode
  49 + when :sha
  50 + SHAEncryptedString.new(self, options)
  51 + when :asymmetric, :asymmetrical
  52 + AsymmetricallyEncryptedString(self, options)
  53 + when :symmetric, :symmetrical
  54 + SymmetricallyEncryptedString.new(self, options)
  55 + else
  56 + raise ArgumentError, "Invalid encryption mode: #{mode}"
  57 + end
  58 + end
  59 + end
  60 + end
  61 + end
  62 +
  63 + module Encrypts #:nodoc:
  64 + def self.included(base) #:nodoc:
  65 + base.extend(MacroMethods)
  66 + end
  67 +
  68 + module MacroMethods
  69 + #
  70 + #
  71 + def encrypts(attr_name, options = {})
  72 + options.reverse_merge!(
  73 + :mode => :sha
  74 + )
  75 +
  76 + klass = case options.delete(:mode)
  77 + when :sha
  78 + SHAEncryptedString
  79 + when :asymmetric, :asymmetrically
  80 + AsymmetricallyEncryptedString
  81 + when :symmetric, :symmetrically
  82 + SymmetricallyEncryptedString
  83 + end
  84 +
  85 + var_name = "@#{attr_name}"
  86 +
  87 + # Define the reader
  88 + reader_options = options.dup
  89 + reader_options[:encrypt] = false
  90 + define_method(attr_name) do
  91 + if (data = read_attribute(attr_name)) && !data.is_a?(klass)
  92 + data = instance_variable_get(var_name) || instance_variable_set(var_name, klass.new(data, reader_options))
  93 + end
  94 +
  95 + data
  96 + end
  97 +
  98 + # Define the writer
  99 + define_method("#{attr_name}=") do |data|
  100 + unless data.is_a?(EncryptedString)
  101 + data = klass.new(data, options)
  102 + end
  103 +
  104 + write_attribute(attr_name, data)
  105 + instance_variable_set(var_name, klass.new(data, options))
  106 + end
  107 + end
  108 + end
  109 + end
  110 +end
112 lib/encrypted_strings/asymmetrically_encrypted_string.rb
... ... @@ -0,0 +1,112 @@
  1 +#
  2 +#
  3 +class AsymmetricallyEncryptedString < EncryptedString
  4 + #
  5 + @@default_private_key_file = nil
  6 + cattr_accessor :default_private_key_file
  7 +
  8 + #
  9 + @@default_public_key_file = nil
  10 + cattr_accessor :default_public_key_file
  11 +
  12 + #
  13 + @@default_symmetric_algorithm = nil
  14 + cattr_accessor :default_symmetric_algorithm
  15 +
  16 + attr_reader :private_key_file
  17 + attr_reader :public_key_file
  18 + attr_accessor :symmetric_algorithm
  19 +
  20 + # Configuration options:
  21 + # * <tt>private_key_file</tt> - encrypted private key file
  22 + # * <tt>public_key_file</tt> - public key file
  23 + # * <tt>symmetric_algorithm</tt> - algorithm to use for SymmetricSentry
  24 + #
  25 + def initialize(data, options = {})
  26 + options = options.symbolize_keys
  27 + options.assert_valid_keys(
  28 + :private_key_file,
  29 + :public_key_file,
  30 + :symmetric_algorithm,
  31 + :encrypt
  32 + )
  33 + options.reverse_merge!(
  34 + :private_key_file => @@default_private_key_file,
  35 + :public_key_file => @@default_public_key_file,
  36 + :symmetric_algorithm => @@default_symmetric_algorithm,
  37 + :encrypt => true
  38 + )
  39 +
  40 + @public_key = @private_key = nil
  41 + private_key_file = options[:private_key_file]
  42 + public_key_file = options[:public_key_file]
  43 +
  44 + super(encrypt(data))
  45 + end
  46 +
  47 + def decrypt
  48 + raise NoPrivateKeyError unless private?
  49 +
  50 + data = Base64.decode64(to_s)
  51 + private_rsa(@key).private_decrypt(data)
  52 + end
  53 +
  54 + def private_key_file=(file)
  55 + @private_key_file = file and load_private_key
  56 + end
  57 +
  58 + def public_key_file=(file)
  59 + @public_key_file = file and load_public_key
  60 + end
  61 +
  62 + # Is this string encrypted using a public key?
  63 + def public?
  64 + return true unless @public_key.nil?
  65 + load_public_key and return @public_key
  66 + end
  67 +
  68 + # Is this string encrypted using a private key?
  69 + def private?
  70 + return true unless @private_key.nil?
  71 + load_private_key and return @private_key
  72 + end
  73 +
  74 + private
  75 + def encryptor
  76 + @encryptor ||= SymmetricSentry.new(:algorithm => @symmetric_algorithm)
  77 + end
  78 +
  79 + def encrypt(data)
  80 + raise NoPublicKeyError unless public?
  81 +
  82 + data = public_rsa.public_encrypt(data)
  83 + Base64.encode64(data)
  84 + end
  85 +
  86 + def load_private_key #:nodoc:
  87 + @private_rsa = nil
  88 + @private_key_file ||= @@default_private_key_file
  89 + if @private_key_file and File.file?(@private_key_file)
  90 + @private_key = File.open(@private_key_file) { |f| f.read }
  91 + end
  92 + end
  93 +
  94 + def load_public_key #:nodoc:
  95 + @public_rsa = nil
  96 + @public_key_file ||= @@default_public_key_file
  97 + if @public_key_file and File.file?(@public_key_file)
  98 + @public_key = File.open(@public_key_file) { |f| f.read }
  99 + end
  100 + end
  101 +
  102 + # retrieves private rsa from encrypted private key
  103 + def private_rsa(key = nil)
  104 + return @private_rsa ||= OpenSSL::PKey::RSA.new(@private_key) unless key
  105 + OpenSSL::PKey::RSA.new(encryptor.decrypt_from_base64(@private_key, key))
  106 + end
  107 +
  108 + # retrieves public rsa
  109 + def public_rsa
  110 + @public_rsa ||= OpenSSL::PKey::RSA.new(@public_key)
  111 + end
  112 +end
13 lib/encrypted_strings/encrypted_string.rb
... ... @@ -0,0 +1,13 @@
  1 +#
  2 +#
  3 +class EncryptedString < String
  4 + #
  5 + #
  6 + def ==(other)
  7 + if other.class == String
  8 + to_s == encrypt(other)
  9 + else
  10 + super
  11 + end
  12 + end
  13 +end
79 lib/encrypted_strings/sentry.rb
... ... @@ -0,0 +1,79 @@
  1 +#module ActiveRecord # :nodoc:
  2 +# module Sentry
  3 +# def self.included(base) # :nodoc:
  4 +# base.extend ClassMethods
  5 +# end
  6 +#
  7 +# module ClassMethods
  8 +# def generates_crypted(attr_name, options = {})
  9 +# mode = options[:mode] || :sha
  10 +# case mode
  11 +# when :sha
  12 +# generates_crypted_hash_of(attr_name)
  13 +# when :asymmetric, :asymmetrical
  14 +# asymmetrically_encrypts(attr_name)
  15 +# when :symmetric, :symmetrical
  16 +# symmetrically_encrypts(attr_name)
  17 +# end
  18 +# end
  19 +#
  20 +# def generates_crypted_hash_of(attribute)
  21 +# before_validation ::Sentry::ShaSentry.new(attribute)
  22 +# attr_accessor attribute
  23 +# end
  24 +#
  25 +# def asymmetrically_encrypts(attr_name)
  26 +# temp_sentry = ::Sentry::AsymmetricSentryCallback.new(attr_name)
  27 +# before_validation temp_sentry
  28 +# after_save temp_sentry
  29 +#
  30 +# define_method(attr_name) do |*optional|
  31 +# send("#{attr_name}!", *optional) rescue nil
  32 +# end
  33 +#
  34 +# define_method("#{attr_name}!") do |*optional|
  35 +# return decrypted_values[attr_name] unless decrypted_values[attr_name].nil?
  36 +# return nil if send("crypted_#{attr_name}").nil?
  37 +# key = optional.shift
  38 +# ::Sentry::AsymmetricSentry.decrypt_from_base64(send("crypted_#{attr_name}"), key)
  39 +# end
  40 +#
  41 +# define_method("#{attr_name}=") do |value|
  42 +# decrypted_values[attr_name] = value
  43 +# nil
  44 +# end
  45 +#
  46 +# private
  47 +# define_method(:decrypted_values) do
  48 +# @decrypted_values ||= {}
  49 +# end
  50 +# end
  51 +#
  52 +# def symmetrically_encrypts(attr_name)
  53 +# temp_sentry = ::Sentry::SymmetricSentryCallback.new(attr_name)
  54 +# before_validation temp_sentry
  55 +# after_save temp_sentry
  56 +#
  57 +# define_method(attr_name) do
  58 +# send("#{attr_name}!") rescue nil
  59 +# end
  60 +#
  61 +# define_method("#{attr_name}!") do
  62 +# return decrypted_values[attr_name] unless decrypted_values[attr_name].nil?
  63 +# return nil if send("crypted_#{attr_name}").nil?
  64 +# ::Sentry::SymmetricSentry.decrypt_from_base64(send("crypted_#{attr_name}"))
  65 +# end
  66 +#
  67 +# define_method("#{attr_name}=") do |value|
  68 +# decrypted_values[attr_name] = value
  69 +# nil
  70 +# end
  71 +#
  72 +# private
  73 +# define_method(:decrypted_values) do
  74 +# @decrypted_values ||= {}
  75 +# end
  76 +# end
  77 +# end
  78 +# end
  79 +#end
33 lib/encrypted_strings/sha_encrypted_string.rb
... ... @@ -0,0 +1,33 @@
  1 +require 'digest/sha1'
  2 +
  3 +class SHAEncryptedString < EncryptedString
  4 + @@salt = 'salt'
  5 + cattr_accessor :salt
  6 +
  7 + attr_accessor :salt
  8 +
  9 + def initialize(data, options = {})
  10 + options = options.symbolize_keys
  11 + options.assert_valid_keys(
  12 + :salt,
  13 + :encrypt
  14 + )
  15 + options.reverse_merge!(
  16 + :salt => @@salt,
  17 + :encrypt => true
  18 + )
  19 + @salt = options[:salt]
  20 +
  21 + super(options[:encrypt] ? encrypt(data) : data)
  22 + end
  23 +
  24 + #
  25 + def decrypt
  26 + raise NotImplementedError, 'Cannot decrypt an SHA-Encrypted String'
  27 + end
  28 +
  29 + private
  30 + def encrypt(data)
  31 + Digest::SHA1.hexdigest(data + @salt)
  32 + end
  33 +end
62 lib/encrypted_strings/symmetrically_encrypted_string.rb
... ... @@ -0,0 +1,62 @@
  1 +#
  2 +#
  3 +class SymmetricallyEncryptedString < EncryptedString
  4 + #
  5 + @@default_algorithm = 'DES-EDE3-CBC'
  6 + cattr_accessor :default_algorithm
  7 +
  8 + #
  9 + @@default_key = nil
  10 + cattr_accessor :default_key
  11 +
  12 + attr_accessor :algorithm
  13 + attr_accessor :key
  14 +
  15 + #
  16 + #
  17 + def initialize(data, options = {})
  18 + options = options.symbolize_keys
  19 + options.assert_valid_keys(
  20 + :algorithm,
  21 + :key,
  22 + :encrypt
  23 + )
  24 + options.reverse_merge!(
  25 + :algorithm => @@default_algorithm,
  26 + :key => @@default_key,
  27 + :encrypt => false
  28 + )
  29 +
  30 + @key = options[:key]
  31 + raise NoKeyError if @key.nil?
  32 +
  33 + @algorithm = options[:algorithm]
  34 +
  35 + super(options[:encrypt] ? encrypt(data) : data)
  36 + end
  37 +
  38 + #
  39 + #
  40 + def decrypt
  41 + des = encryptor
  42 + des.decrypt(@key)
  43 + text = des.update(Base64.decode64(to_s))
  44 + text << des.final
  45 + end
  46 +
  47 + private
  48 + def encryptor #:nodoc:
  49 + @encryptor ||= OpenSSL::Cipher::Cipher.new(@algorithm)
  50 + end
  51 +
  52 + #
  53 + #
  54 + def encrypt(data)
  55 + des = encryptor
  56 + des.encrypt(@key)
  57 + data = des.update(data)
  58 + data << des.final
  59 +
  60 + Base64.encode64(data)
  61 + end
  62 +end
9 tasks/encrypted_strings.rake
... ... @@ -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 :encryption_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
33 test/abstract_unit.rb
... ... @@ -0,0 +1,33 @@
  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/binding_of_caller'
  8 +require 'active_support/breakpoint'
  9 +require "#{File.dirname(__FILE__)}/../lib/sentry"
  10 +
  11 +config_location = File.dirname(__FILE__) + '/database.yml'
  12 +
  13 +config = YAML::load(IO.read(config_location))
  14 +ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
  15 +ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite'])
  16 +
  17 +load(File.dirname(__FILE__) + "/schema.rb")
  18 +
  19 +Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
  20 +Test::Unit::TestCase.use_instantiated_fixtures = false
  21 +Test::Unit::TestCase.use_transactional_fixtures = (ENV['AR_TX_FIXTURES'] == "yes")
  22 +$LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path)
  23 +
  24 +class Test::Unit::TestCase #:nodoc:
  25 + def create_fixtures(*table_names)
  26 + if block_given?
  27 + Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield }
  28 + else
  29 + Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names)
  30 + end
  31 + end
  32 +end
  33 +
88 test/asymmetric_string_test.rb
... ... @@ -0,0 +1,88 @@
  1 +require 'abstract_unit'
  2 +
  3 +class AsymmetricSentryTest < Test::Unit::TestCase
  4 + def setup
  5 + @str = 'sentry'
  6 + @key = 'secret'
  7 + @public_key_file = File.dirname(__FILE__) + '/keys/public'
  8 + @private_key_file = File.dirname(__FILE__) + '/keys/private'
  9 + @encrypted_public_key_file = File.dirname(__FILE__) + '/keys/encrypted_public'
  10 + @encrypted_private_key_file = File.dirname(__FILE__) + '/keys/encrypted_private'
  11 + @sentry = Sentry::AsymmetricSentry.new
  12 +
  13 + @orig = 'sentry'
  14 + @data = "vYfMxtVB8ezXmQKSNqTC9sPgi8TbsYRxWd7DVbpprzyuEdZ7gftJ/0IXsbXm\nXCU08bTAl0uEFm7dau+eJMXEJg==\n"
  15 + @encrypted_data = "q2obYAITmK93ylzVS01mJx1jSlnmylMX15nFpb4uKesVgnqvtzBRHZ/SK+Nm\nEzceIoAcJc3DHosVa4VUE/aK/A==\n"
  16 + Sentry::AsymmetricSentry.default_public_key_file = nil
  17 + Sentry::AsymmetricSentry.default_private_key_file = nil
  18 + end
  19 +
  20 + def test_should_decrypt_files
  21 + set_key_files @public_key_file, @private_key_file
  22 + assert_equal @orig, @sentry.decrypt_from_base64(@data)
  23 + end
  24 +
  25 + def test_should_decrypt_files_with_encrypted_key
  26 + set_key_files @encrypted_public_key_file, @encrypted_private_key_file
  27 + assert_equal @orig, @sentry.decrypt_from_base64(@encrypted_data, @key)
  28 + end
  29 +
  30 + def test_should_read_key_files
  31 + assert !@sentry.public?
  32 + assert !@sentry.private?
  33 + set_key_files @public_key_file, @private_key_file
  34 + end
  35 +
  36 + def test_should_read_encrypted_key_files
  37 + assert !@sentry.public?
  38 + assert !@sentry.private?
  39 + set_key_files @encrypted_public_key_file, @encrypted_private_key_file
  40 + end
  41 +
  42 + def test_should_decrypt_files_with_default_key
  43 + set_default_key_files @public_key_file, @private_key_file
  44 + assert_equal @orig, @sentry.decrypt_from_base64(@data)
  45 + end
  46 +
  47 + def test_should_decrypt_files_with_default_encrypted_key
  48 + set_default_key_files @encrypted_public_key_file, @encrypted_private_key_file
  49 + assert_equal @orig, @sentry.decrypt_from_base64(@encrypted_data, @key)
  50 + end
  51 +
  52 + def test_should_decrypt_files_with_default_key_using_class_method
  53 + set_default_key_files @public_key_file, @private_key_file
  54 + assert_equal @orig, Sentry::AsymmetricSentry.decrypt_from_base64(@data)
  55 + end
  56 +
  57 + def test_should_decrypt_files_with_default_encrypted_key_using_class_method
  58 + set_default_key_files @encrypted_public_key_file, @encrypted_private_key_file
  59 + assert_equal @orig, Sentry::AsymmetricSentry.decrypt_from_base64(@encrypted_data, @key)
  60 + end
  61 +
  62 + def test_should_read_key_files_with_default_key
  63 + assert !@sentry.public?
  64 + assert !@sentry.private?
  65 + set_default_key_files @public_key_file, @private_key_file
  66 + end
  67 +
  68 + def test_should_read_encrypted_key_files_with_default_key
  69 + assert !@sentry.public?
  70 + assert !@sentry.private?
  71 + set_default_key_files @encrypted_public_key_file, @encrypted_private_key_file
  72 + end
  73 +
  74 + private
  75 + def set_key_files(public_key, private_key)
  76 + @sentry.public_key_file = public_key
  77 + @sentry.private_key_file = private_key
  78 + assert @sentry.private?
  79 + assert @sentry.public?
  80 + end
  81 +
  82 + def set_default_key_files(public_key, private_key)
  83 + Sentry::AsymmetricSentry.default_public_key_file = public_key
  84 + Sentry::AsymmetricSentry.default_private_key_file = private_key
  85 + assert @sentry.private?
  86 + assert @sentry.public?
  87 + end
  88 +end
18 test/database.yml
... ... @@ -0,0 +1,18 @@
  1 +sqlite:
  2 + :adapter: sqlite
  3 + :dbfile: sentry_plugin.sqlite.db
  4 +sqlite3:
  5 + :adapter: sqlite3
  6 + :dbfile: sentry_plugin.sqlite3.db
  7 +postgresql:
  8 + :adapter: postgresql
  9 + :username: postgres
  10 + :password: postgres
  11 + :database: sentry_plugin_test
  12 + :min_messages: ERROR
  13 +mysql:
  14 + :adapter: mysql
  15 + :host: localhost
  16 + :username: rails
  17 + :password:
  18 + :database: sentry_plugin_test
25 test/fixtures/user.rb
... ... @@ -0,0 +1,25 @@
  1 +class User < ActiveRecord::Base
  2 + generates_crypted :creditcard, :mode => :asymmetric
  3 +
  4 + def self.validates_password
  5 + validates_presence_of :crypted_password
  6 + validates_presence_of :password, :on => :create
  7 + validates_length_of :password, :in => 4..40
  8 + end
  9 +end
  10 +
  11 +class ShaUser < User
  12 + validates_password
  13 + validates_confirmation_of :password
  14 + generates_crypted :password # sha is used by default
  15 +end
  16 +
  17 +class DangerousUser < User # no password confirmation
  18 +# validates_password
  19 + generates_crypted :password
  20 +end
  21 +
  22 +class SymmetricUser < User
  23 + validates_password
  24 + generates_crypted :password, :mode => :symmetric
  25 +end
11 test/fixtures/users.yml
... ... @@ -0,0 +1,11 @@
  1 +user_1:
  2 + id: 1
  3 + login: bob
  4 + crypted_password: "0XlmUuNpE2k=\n"
  5 + crypted_creditcard: "vYfMxtVB8ezXmQKSNqTC9sPgi8TbsYRxWd7DVbpprzyuEdZ7gftJ/0IXsbXm\nXCU08bTAl0uEFm7dau+eJMXEJg==\n"
  6 + type: SymmetricUser
  7 +user_2:
  8 + id: 2
  9 + login: fred
  10 + crypted_creditcard: "q2obYAITmK93ylzVS01mJx1jSlnmylMX15nFpb4uKesVgnqvtzBRHZ/SK+Nm\nEzceIoAcJc3DHosVa4VUE/aK/A==\n"
  11 +
12 test/keys/encrypted_private
... ... @@ -0,0 +1,12 @@
  1 +OBNa1q8kbx8pyZZjIpr/pZV0oulE2czh5JlPW/13XsBvoz+A2zxA9gchhi6c
  2 +3yvfqgcZdojcsep+IiTqeg3gOPB2xNbedpP1lm+9tEfgdb9r1CLzRcURh7Hg
  3 +ufWgyEkS0lloz/YLy4hg9YDKetFNF9fnrk3xVwZPwFVuk4l/Unw1FTXLHsrq
  4 +KG27cR8mvNOow4bk4LVhk/avFSM85m3ITySEnyJsQQDzsI/RrWcQ7Js+8Ynv
  5 +esN51E/T0CYtkMEne2zSaD5qUTJlQ7Qtn4UUeZkpYjn4xQZPxw4OjL6zofg7
  6 +lsqElSv1/qP3QI8aKcQQklVsHRc5AgsxOFX4J6g6lo4kOGOwn0Ex8IRDfOej
  7 +pq4SUDh9IXz+6FBieQrObB/xEsKysVwRSzXre6ObHlPFsigg5ekFPyCv5ZTz
  8 +0iP8+xe/FJRrYdR3r3F5pRkOy0pw9EqlrLjmOx3/fgxhLq8FWmcSBbH3h3SG
  9 +GkJlfHNjF77FTJjnHKzRS+5VpdW4IHbsjL+NlI1z9Ol//czYvSGv85NdJvkq
  10 +PmH3o0+uYdwY5PeSMOPV21nJ3dwiKlm5IMFasL3C5yVJNVTVZTS7vWdcgZ4U
  11 +XfWQ9Y266ibbqXPluv4nxt1+kgjxmPbjPdYrlB5t7a2+unzT3oE3f4VGOG+k
  12 +YqFg0ErHN+fu
4 test/keys/encrypted_public
... ... @@ -0,0 +1,4 @@
  1 +-----BEGIN RSA PUBLIC KEY-----
  2 +MEgCQQCvktJgveIcgTH98hAhMjo0g6/GVMJaYdUh+/zQn4RBWASRmwEfJqggsfKT
  3 +pSNendZQMD8kKS8J1YTBr60ToM25AgMBAAE=
  4 +-----END RSA PUBLIC KEY-----
9 test/keys/private
... ... @@ -0,0 +1,9 @@
  1 +-----BEGIN RSA PRIVATE KEY-----
  2 +MIIBOwIBAAJBAL/xeY6aqFx6z1ThNOwgPgxv3tsonTlCj8VkN3Ikumg6SzBuLxlV
  3 +i9gFQZ7K9Pv9o/7+xUTYODqBpVhwgLBeu2cCAwEAAQJAHyjFMfg7Yp/xLndMzxRA
  4 +3mX+yJckRtpeWo31TktWE3syks1r9OrfmxKiStM9kFRubeBHTihZrW92TYkROLxh
  5 +uQIhAPuftVTJZFDNxeYDKIMIMqwR8KZgtuf25cv4pTxYwPqLAiEAw0gNwDJHBkvo
  6 +da4402pZNQmBA6qCSf0svDXqoEoaShUCIGBma340Oe6LJ0pb42Vv+pnZtazIWMq9
  7 +2IQwmn1oM2bJAiEAhgP869mVRIzzi091UCG79tn+4DU0FPLasI+P5VD1mcECIQDb
  8 +3ndvbPcElVvdJgabxyWJJsNtBBNZYPsuc6NrQyShOw==
  9 +-----END RSA PRIVATE KEY-----
4 test/keys/public
... ... @@ -0,0 +1,4 @@
  1 +-----BEGIN RSA PUBLIC KEY-----
  2 +MEgCQQC/8XmOmqhces9U4TTsID4Mb97bKJ05Qo/FZDdyJLpoOkswbi8ZVYvYBUGe
  3 +yvT7/aP+/sVE2Dg6gaVYcICwXrtnAgMBAAE=
  4 +-----END RSA PUBLIC KEY-----
10 test/schema.rb
... ... @@ -0,0 +1,10 @@
  1 +ActiveRecord::Schema.define(:version => 1) do
  2 +
  3 + create_table "users", :force => true do |t|
  4 + t.column :crypted_password, :string, :limit => 255
  5 + t.column :crypted_creditcard, :string, :limit => 255
  6 + t.column :login, :string, :limit => 50
  7 + t.column :type, :string, :limit => 20
  8 + end
  9 +
  10 +end
31 test/sha_string_test.rb
... ... @@ -0,0 +1,31 @@
  1 +require 'abstract_unit'
  2 +require 'fixtures/user'
  3 +
  4 +class ShaSentryTest < Test::Unit::TestCase
  5 + def setup
  6 + Sentry::ShaSentry.salt = 'salt'
  7 + end
  8 +
  9 + def test_should_encrypt
  10 + assert_equal 'f438229716cab43569496f3a3630b3727524b81b', Sentry::ShaSentry.encrypt('test')
  11 + end
  12 +
  13 + def test_should_encrypt_with_salt
  14 + Sentry::ShaSentry.salt = 'different salt'
  15 + assert_equal '18e3256d71529db8fa65b2eef24a69ddad7070f3', Sentry::ShaSentry.encrypt('test')
  16 + end
  17 +
  18 + def test_should_encrypt_user_password
  19 + u = ShaUser.new :login => 'bob'
  20 + u.password = u.password_confirmation = 'test'
  21 + assert u.save
  22 + assert u.crypted_password = 'f438229716cab43569496f3a3630b3727524b81b'
  23 + end
  24 +
  25 + def test_should_encrypt_user_password_without_confirmation
  26 + u = DangerousUser.new :login => 'bob'
  27 + u.password = 'test'
  28 + assert u.save
  29 + assert u.crypted_password = 'f438229716cab43569496f3a3630b3727524b81b'
  30 + end
  31 +end
37 test/symmetric_string_test.rb
... ... @@ -0,0 +1,37 @@
  1 +require 'abstract_unit'
  2 +
  3 +class SymmetricSentryTest < Test::Unit::TestCase
  4 + def setup
  5 + @str = 'sentry'
  6 + @key = 'secret'
  7 + @encrypted = "0XlmUuNpE2k=\n"
  8 + @sentry = Sentry::SymmetricSentry.new
  9 + Sentry::SymmetricSentry.default_key = nil
  10 + end
  11 +
  12 + def test_should_encrypt
  13 + assert_equal @encrypted, @sentry.encrypt_to_base64(@str, @key)
  14 + end
  15 +
  16 + def test_should_decrypt
  17 + assert_equal @str, @sentry.decrypt_from_base64(@encrypted, @key)
  18 + end
  19 +
  20 + def test_should_encrypt_with_default_key
  21 + Sentry::SymmetricSentry.default_key = @key
  22 + assert_equal @encrypted, @sentry.encrypt_to_base64(@str)
  23 + end
  24 +
  25 + def test_should_decrypt_with_default_key
  26 + Sentry::SymmetricSentry.default_key = @key
  27 + assert_equal @str, @sentry.decrypt_from_base64(@encrypted)
  28 + end
  29 +
  30 + def test_should_raise_error_when_encrypt_with_no_key
  31 + assert_raises(Sentry::NoKeyError) { @sentry.encrypt_to_base64(@str) }
  32 + end
  33 +
  34 + def test_should_raise_error_when_decrypt_with_no_key
  35 + assert_raises(Sentry::NoKeyError) { @sentry.decrypt_from_base64(@str) }
  36 + end
  37 +end
2  test/tests.rb
... ... @@ -0,0 +1,2 @@
  1 +$:.unshift "../lib"
  2 +Dir["**/*_test.rb"].each { |f| load f }

0 comments on commit 48a2ff0

Please sign in to comment.
Something went wrong with that request. Please try again.