Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added dynamics to allow for database stored parameters when using encrypted attributes in models #1

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ task :default => :test
desc "Test the #{spec.name} plugin."
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.libs << '.'
t.test_files = spec.test_files
t.verbose = true
end
Expand Down
113 changes: 74 additions & 39 deletions lib/encrypted_strings/asymmetric_cipher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,62 +61,69 @@ class << self
attr_accessor :default_public_key_file
end

# Private key used for decrypting data
# Private key file used for decrypting data
attr_reader :private_key_file

# Public key used for encrypting data
# Public key file used for encrypting data
attr_reader :public_key_file

# Private key used for decrypting data
dynamic_accessor :private_key

# Public key used for encrypting data
dynamic_accessor :public_key

# The algorithm to use if the key files are encrypted themselves
attr_accessor :algorithm
dynamic_accessor :algorithm

# The password used during symmetric decryption of the key files
attr_accessor :password
dynamic_accessor :password

# Creates a new cipher that uses an asymmetric encryption strategy.
#
# Configuration options:
# * <tt>:private_key_file</tt> - Encrypted private key file
# * <tt>:private_key_file</tt> - Private key file (possibly encrypted)
# * <tt>:public_key_file</tt> - Public key file
# * <tt>:private_key</tt> - Private key as String or OpenSSL::PKey::RSA
# * <tt>:public_key</tt> - Public key as String or OpenSSL::PKey::RSA, or :derived to derive it from the private key
# * <tt>:password</tt> - The password to use in the symmetric cipher
# * <tt>:algorithm</tt> - Algorithm to use symmetrically encrypted strings
# * <tt>:encrypt_with_private</tt> - Encrypt with the private instead of the public key
def initialize(options = {})
invalid_options = options.keys - [:private_key_file, :public_key_file, :algorithm, :password]
invalid_options = options.keys - [:private_key_file, :public_key_file, :private_key, :public_key, :algorithm, :password, :encrypt_with_private]
raise ArgumentError, "Unknown key(s): #{invalid_options.join(", ")}" unless invalid_options.empty?

options = {
:private_key_file => AsymmetricCipher.default_private_key_file,
:public_key_file => AsymmetricCipher.default_public_key_file
}.merge(options)

@public_key = @private_key = nil
@public_key = options[:public_key]
@private_key = options[:private_key]
@encrypt_with_private = options[:encrypt_with_private]

self.private_key_file = options[:private_key_file]
self.public_key_file = options[:public_key_file]
raise ArgumentError, 'At least one key file must be specified (:private_key_file or :public_key_file)' unless private_key_file || public_key_file
raise ArgumentError, 'At least one key or key file must be specified (:private_key, :public_key, :private_key_file or :public_key_file)' unless @private_key || @public_key || private_key_file || public_key_file

self.algorithm = options[:algorithm]
self.password = options[:password]

super()
end

# Encrypts the given data. If no public key file has been specified, then
# Encrypts the given data. If no public key has been specified, then
# a NoPublicKeyError will be raised.
def encrypt(data)
raise NoPublicKeyError, "Public key file: #{public_key_file}" unless public?

encrypted_data = public_rsa.public_encrypt(data)
[encrypted_data].pack('m')
k = @encrypt_with_private ? :private : :public
perform k, :encrypt, data
end
# Decrypts the given data. If no private key file has been specified, then

# Decrypts the given data. If no private key has been specified, then
# a NoPrivateKeyError will be raised.
def decrypt(data)
raise NoPrivateKeyError, "Private key file: #{private_key_file}" unless private?

decrypted_data = data.unpack('m')[0]
private_rsa.private_decrypt(decrypted_data)
k = @encrypt_with_private ? :public : :private
perform k, :decrypt, data
end

# Sets the location of the private key and loads it
Expand All @@ -131,55 +138,83 @@ def public_key_file=(file)

# Does this cipher have a public key available?
def public?
return true if @public_key

load_public_key
!@public_key.nil?
!load_public_key.nil?
end

# Does this cipher have a private key available?
def private?
return true if @private_key

load_private_key
!@private_key.nil?
!load_private_key.nil?
end

private
# Loads the private key from the configured file
def load_private_key
@private_rsa = nil

if private_key_file && File.file?(private_key_file)
@private_key = File.read(private_key_file)
@private_key ||= begin
@private_rsa = nil

if private_key_file && File.file?(private_key_file)
File.read(private_key_file)
end
end
end

# Loads the public key from the configured file
def load_public_key
@public_rsa = nil

if public_key_file && File.file?(public_key_file)
@public_key = File.read(public_key_file)
@public_key ||= begin
@public_rsa = nil

if public_key_file && File.file?(public_key_file)
@public_key = File.read(public_key_file)
end
end
end

# Retrieves the private RSA from the private key
def private_rsa
@private_rsa = nil if @private_key.respond_to?(:call)
if password
options = {:password => password}
options[:algorithm] = algorithm if algorithm

private_key = @private_key.decrypt(:symmetric, options)
OpenSSL::PKey::RSA.new(private_key)
pkey = @private_key.decrypt(:symmetric, options)
OpenSSL::PKey::RSA.new(pkey)
else
@private_rsa ||= OpenSSL::PKey::RSA.new(@private_key)
@private_rsa ||= make_key(private_key)
end
end

# Retrieves the public RSA
def public_rsa
@public_rsa ||= OpenSSL::PKey::RSA.new(@public_key)
@public_rsa = nil if @public_key.respond_to?(:call) or
(@public_key == :derived and @private_key.respond_to?(:call))
@public_rsa ||= if @public_key == :derived
private_rsa.public_key
else
make_key(public_key)
end
end

def make_key(key)
if key.is_a?(OpenSSL::PKey::RSA)
key
else
OpenSSL::PKey::RSA.new(key)
end
end

def perform(type, crypt, data)
data = data.unpack('m')[0] if crypt == :decrypt
result= get_key(type).send("#{type}_#{crypt}", data)
crypt == :encrypt ? [result].pack('m') : result
end

def get_key(type)
unless send("#{type}?")
file = send("#{type}_key_file")
err = EncryptedStrings.const_get("No#{type.capitalize}KeyError")
raise err, "#{type.capitalize} key file: #{file}"
end
send("#{type}_rsa")
end
end
end
9 changes: 9 additions & 0 deletions lib/encrypted_strings/cipher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ module EncryptedStrings
# assumed to be able to decrypt strings. Note, however, that certain
# encryption algorithms do not allow decryption.
class Cipher
def self.dynamic_accessor(name)
eval <<-CODE
attr_writer :#{name}
def #{name}
@#{name}.respond_to?(:call) ? @#{name}[] : @#{name}
end
CODE
end

# Can this string be decrypted? Default is true.
def can_decrypt?
true
Expand Down
6 changes: 3 additions & 3 deletions lib/encrypted_strings/sha_cipher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ class << self
@default_builder = lambda {|data, salt| "#{data}#{salt}"}

# The algorithm to use for encryption/decryption
attr_accessor :algorithm
dynamic_accessor :algorithm

# The salt value to use for encryption
attr_accessor :salt
dynamic_accessor :salt

# The function to use to build the value that gets hashed
attr_accessor :builder
Expand Down Expand Up @@ -118,7 +118,7 @@ def encrypt(data)
# * String
# * Object that responds to :salt
def salt_value(value)
if value.is_a?(Proc)
if value.respond_to?(:call)
value.call
elsif value.respond_to?(:salt)
value.salt
Expand Down
9 changes: 5 additions & 4 deletions lib/encrypted_strings/symmetric_cipher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ class << self
@default_algorithm = 'DES-EDE3-CBC'

# The algorithm to use for encryption/decryption
attr_accessor :algorithm
dynamic_accessor :algorithm

# The password that generates the key/initialization vector for the
# algorithm
attr_accessor :password
dynamic_accessor :password

# Creates a new cipher that uses a symmetric encryption strategy.
#
Expand All @@ -74,7 +74,7 @@ def initialize(options = {})

self.algorithm = options[:algorithm]
self.password = options[:password]
raise NoPasswordError if password.nil?
raise NoPasswordError unless options[:password]

super()
end
Expand All @@ -90,10 +90,11 @@ def encrypt(data)
cipher = build_cipher(:encrypt)
[cipher.update(data) + cipher.final].pack('m')
end

private
def build_cipher(type) #:nodoc:
cipher = OpenSSL::Cipher::Cipher.new(algorithm).send(type)
raise NoPasswordError if password.nil?
cipher.pkcs5_keyivgen(password)
cipher
end
Expand Down
2 changes: 2 additions & 0 deletions nbproject/private/private.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
file.reference.encrypted_strings-lib=/srv/data/home/mklaus/Projects/encrypted_strings/lib
file.reference.encrypted_strings-test=/srv/data/home/mklaus/Projects/encrypted_strings/test
4 changes: 4 additions & 0 deletions nbproject/private/private.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-private xmlns="http://www.netbeans.org/ns/project-private/1">
<editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1"/>
</project-private>
5 changes: 5 additions & 0 deletions nbproject/project.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
main.file=
platform.active=Ruby_0
source.encoding=UTF-8
src.dir=${file.reference.encrypted_strings-lib}
test.src.dir=${file.reference.encrypted_strings-test}
15 changes: 15 additions & 0 deletions nbproject/project.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://www.netbeans.org/ns/project/1">
<type>org.netbeans.modules.ruby.rubyproject</type>
<configuration>
<data xmlns="http://www.netbeans.org/ns/ruby-project/1">
<name>encrypted_strings</name>
<source-roots>
<root id="src.dir"/>
</source-roots>
<test-roots>
<root id="test.src.dir"/>
</test-roots>
</data>
</configuration>
</project>
Loading