提交 f1d6a0e4 编写于 作者: J Jeremy Kemper

Introduce SecretKeyGenerator for more secure session secrets than...

Introduce SecretKeyGenerator for more secure session secrets than CGI::Session's pseudo-random id generator. Consider extracting to Active Support later. Closes #10286.


git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@8229 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
上级 fa866765
*SVN*
* Introduce SecretKeyGenerator for more secure session secrets than CGI::Session's pseudo-random id generator. Consider extracting to Active Support later. #10286 [Hongli Lai]
* RAILS_GEM_VERSION may be set to any valid gem version specifier. #10057 [Chad Woolley, Chu Yeow]
* Load config/preinitializer.rb, if present, before loading the environment. #9943 [Chad Woolley]
......
require 'rbconfig'
require 'digest/md5'
require 'rails_generator/secret_key_generator'
class AppGenerator < Rails::Generator::Base
DEFAULT_SHEBANG = File.join(Config::CONFIG['bindir'],
......@@ -33,6 +34,9 @@ def manifest
md5 << String($$)
md5 << @app_name
# Do our best to generate a secure secret key for CookieStore
secret = Rails::SecretKeyGenerator.new(@app_name).generate_secret
record do |m|
# Root directory and all subdirectories.
m.directory ''
......@@ -61,7 +65,7 @@ def manifest
# Environments
m.file "environments/boot.rb", "config/boot.rb"
m.template "environments/environment.rb", "config/environment.rb", :assigns => { :freeze => options[:freeze], :app_name => @app_name, :app_secret => md5.hexdigest }
m.template "environments/environment.rb", "config/environment.rb", :assigns => { :freeze => options[:freeze], :app_name => @app_name, :app_secret => secret }
m.file "environments/production.rb", "config/environments/production.rb"
m.file "environments/development.rb", "config/environments/development.rb"
m.file "environments/test.rb", "config/environments/test.rb"
......
# A class for creating random secret keys. This class will do its best to create a
# random secret key that's as secure as possible, using whatever methods are
# available on the current platform. For example:
#
# generator = Rails::SecretKeyGenerator("some unique identifier, such as the application name")
# generator.generate_secret # => "f3f1be90053fa851... (some long string)"
module Rails
class SecretKeyGenerator
GENERATORS = [ :secure_random, :win32_api, :urandom, :openssl, :prng ].freeze
def initialize(identifier)
@identifier = identifier
end
# Generate a random secret key with the best possible method available on
# the current platform.
def generate_secret
generator = GENERATORS.find do |g|
self.class.send("supports_#{g}?")
end
send("generate_secret_with_#{generator}")
end
# Generate a random secret key by using the Win32 API. Raises LoadError
# if the current platform cannot make use of the Win32 API. Raises
# SystemCallError if some other error occured.
def generate_secret_with_win32_api
# Following code is based on David Garamond's GUID library for Ruby.
require 'Win32API'
crypt_acquire_context = Win32API.new("advapi32", "CryptAcquireContext",
'PPPII', 'L')
crypt_gen_random = Win32API.new("advapi32", "CryptGenRandom",
'LIP', 'L')
crypt_release_context = Win32API.new("advapi32", "CryptReleaseContext",
'LI', 'L')
prov_rsa_full = 1
crypt_verifycontext = 0xF0000000
hProvStr = " " * 4
if crypt_acquire_context.call(hProvStr, nil, nil, prov_rsa_full,
crypt_verifycontext) == 0
raise SystemCallError, "CryptAcquireContext failed: #{lastWin32ErrorMessage}"
end
hProv, = hProvStr.unpack('L')
bytes = " " * 64
if crypt_gen_random.call(hProv, bytes.size, bytes) == 0
raise SystemCallError, "CryptGenRandom failed: #{lastWin32ErrorMessage}"
end
if crypt_release_context.call(hProv, 0) == 0
raise SystemCallError, "CryptReleaseContext failed: #{lastWin32ErrorMessage}"
end
bytes.unpack("H*")[0]
end
# Generate a random secret key with Ruby 1.9's SecureRandom module.
# Raises LoadError if the current Ruby version does not support
# SecureRandom.
def generate_secret_with_secure_random
require 'securerandom'
return SecureRandom.hex(64)
end
# Generate a random secret key with OpenSSL. If OpenSSL is not
# already loaded, then this method will attempt to load it.
# LoadError will be raised if that fails.
def generate_secret_with_openssl
require 'openssl'
if !File.exist?("/dev/urandom")
# OpenSSL transparently seeds the random number generator with
# data from /dev/urandom. On platforms where that is not
# available, such as Windows, we have to provide OpenSSL with
# our own seed. Unfortunately there's no way to provide a
# secure seed without OS support, so we'll have to do with
# rand() and Time.now.usec().
OpenSSL::Random.seed(rand(0).to_s + Time.now.usec.to_s)
end
data = OpenSSL::BN.rand(2048, -1, false).to_s
return OpenSSL::Digest::SHA512.new(data).hexdigest
end
# Generate a random secret key with /dev/urandom.
# Raises SystemCallError on failure.
def generate_secret_with_urandom
return File.read("/dev/urandom", 64).unpack("H*")[0]
end
# Generate a random secret key with Ruby's pseudo random number generator,
# as well as some environment information.
#
# This is the least cryptographically secure way to generate a secret key,
# and should be avoided whenever possible.
def generate_secret_with_prng
require 'digest/sha2'
sha = Digest::SHA2.new(512)
now = Time.now
sha << now.to_s
sha << String(now.usec)
sha << String(rand(0))
sha << String($$)
sha << @identifier
return sha.hexdigest
end
private
def lastWin32ErrorMessage
# Following code is based on David Garamond's GUID library for Ruby.
get_last_error = Win32API.new("kernel32", "GetLastError", '', 'L')
format_message = Win32API.new("kernel32", "FormatMessageA",
'LPLLPLPPPPPPPP', 'L')
format_message_ignore_inserts = 0x00000200
format_message_from_system = 0x00001000
code = get_last_error.call
msg = "\0" * 1024
len = format_message.call(format_message_ignore_inserts +
format_message_from_system, 0,
code, 0, msg, 1024, nil, nil,
nil, nil, nil, nil, nil, nil)
msg[0, len].tr("\r", '').chomp
end
def self.supports_secure_random?
begin
require 'securerandom'
true
rescue LoadError
false
end
end
def self.supports_win32_api?
return false unless RUBY_PLATFORM =~ /(:?mswin|mingw)/
begin
require 'Win32API'
true
rescue LoadError
false
end
end
def self.supports_urandom?
File.exists?('/dev/urandom')
end
def self.supports_openssl?
begin
require 'openssl'
true
rescue LoadError
false
end
end
def self.supports_prng?
true
end
end
end
require 'test/unit'
# Must set before requiring generator libs.
if defined?(RAILS_ROOT)
RAILS_ROOT.replace "#{File.dirname(__FILE__)}/fixtures"
else
RAILS_ROOT = "#{File.dirname(__FILE__)}/fixtures"
end
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
require 'rails_generator'
require 'rails_generator/secret_key_generator'
require 'rails_generator/generators/applications/app/app_generator'
class SecretKeyGenerationTest < Test::Unit::TestCase
SECRET_KEY_MIN_LENGTH = 128
APP_NAME = "foo"
def setup
@generator = Rails::SecretKeyGenerator.new(APP_NAME)
end
def test_secret_key_generation
assert @generator.generate_secret.length >= SECRET_KEY_MIN_LENGTH
end
Rails::SecretKeyGenerator::GENERATORS.each do |generator|
if Rails::SecretKeyGenerator.send("supports_#{generator}?")
define_method("test_secret_key_generation_with_#{generator}") do
assert @generator.send("generate_secret_with_#{generator}").length >= SECRET_KEY_MIN_LENGTH
end
end
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册