Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #10286: rails-more-secure-secret-keys.diff

File rails-more-secure-secret-keys.diff, 9.4 kB (added by FooBarWidget, 2 years ago)
  • test/secret_key_generation_test.rb

    old new  
     1require 'test/unit' 
     2 
     3# Must set before requiring generator libs. 
     4if defined?(RAILS_ROOT) 
     5  RAILS_ROOT.replace "#{File.dirname(__FILE__)}/fixtures" 
     6else 
     7  RAILS_ROOT = "#{File.dirname(__FILE__)}/fixtures" 
     8end 
     9 
     10$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib" 
     11 
     12require 'rails_generator' 
     13require 'rails_generator/secret_key_generator' 
     14require 'rails_generator/generators/applications/app/app_generator' 
     15 
     16def supports_secure_random? 
     17  begin 
     18    require 'securerandom' 
     19    true 
     20  rescue LoadError 
     21    false 
     22  end 
     23end 
     24 
     25def supports_openssl? 
     26  begin 
     27    require 'openssl' 
     28    true 
     29  rescue LoadError 
     30    false 
     31  end 
     32end 
     33 
     34def supports_win32_api? 
     35  RUBY_PLATFORM =~ /(:?mswin|mingw)/ 
     36end 
     37 
     38def supports_urandom? 
     39  File.exist?("/dev/urandom") 
     40end 
     41 
     42class SecretKeyGenerationTest < Test::Unit::TestCase 
     43  SECRET_KEY_MIN_LENGTH = 128 
     44  APP_NAME = "foo" 
     45   
     46  def setup 
     47    @generator = Rails::SecretKeyGenerator.new(APP_NAME) 
     48  end 
     49 
     50  def test_secret_key_generation 
     51    assert @generator.generate_secret.length >= SECRET_KEY_MIN_LENGTH 
     52  end 
     53   
     54  def test_secret_key_generation_with_prng 
     55    assert @generator.generate_secret_with_prng.length >= SECRET_KEY_MIN_LENGTH 
     56  end 
     57   
     58  if supports_openssl? 
     59    def test_secret_key_generation_with_openssl 
     60      assert @generator.generate_secret_with_openssl.length >= SECRET_KEY_MIN_LENGTH 
     61    end 
     62  end 
     63   
     64  if supports_secure_random? 
     65    def test_secret_key_generation_with_secure_random 
     66      assert @generator.generate_secret_with_secure_random.length >= SECRET_KEY_MIN_LENGTH 
     67    end 
     68  end 
     69   
     70  if supports_win32_api? 
     71    def test_secret_key_generation_with_win32_api 
     72      assert @generator.generate_secret_with_win32_api.length >= SECRET_KEY_MIN_LENGTH 
     73    end 
     74  end 
     75   
     76  if supports_urandom? 
     77    def test_secret_key_generation_with_urandom 
     78      assert @generator.generate_secret_with_urandom.length >= SECRET_KEY_MIN_LENGTH 
     79    end 
     80  end 
     81end 
  • lib/rails_generator/secret_key_generator.rb

    old new  
     1# A class for creating random secret keys. This class will do its best to create a 
     2# random secret key that's as secure as possible, using whatever methods are 
     3# available on the current platform. For example: 
     4# 
     5#   generator = Rails::SecretKeyGenerator("some unique identifier, such as the application name") 
     6#   generator.generate_secret     # => "f3f1be90053fa851... (some long string)" 
     7 
     8module Rails 
     9  class SecretKeyGenerator 
     10    def initialize(identifier) 
     11      @identifier = identifier 
     12    end 
     13 
     14    # Generate a random secret key with the best possible method available on 
     15    # the current platform. 
     16    def generate_secret 
     17      begin 
     18        secret = generate_secret_with_secure_random 
     19      rescue LoadError 
     20        # SecureRandom not available. Try other methods. 
     21        begin 
     22          if RUBY_PLATFORM =~ /(:?mswin|mingw)/ 
     23            begin 
     24              secret = generate_secret_with_win32_api 
     25            rescue LoadError 
     26              secret = generate_secret_with_openssl 
     27            end 
     28          else 
     29            begin 
     30              secret = generate_secret_with_urandom 
     31            rescue SystemCallError 
     32              secret = generate_secret_with_openssl 
     33            end 
     34          end 
     35        rescue LoadError 
     36          # OpenSSL not available. Try last resort. 
     37          secret = generate_secret_with_prng 
     38        end 
     39      end 
     40      secret 
     41    end 
     42     
     43    # Generate a random secret key by using the Win32 API. Raises LoadError 
     44    # if the current platform cannot make use of the Win32 API. Raises 
     45    # SystemCallError if some other error occured. 
     46    def generate_secret_with_win32_api 
     47      # Following code is based on David Garamond's GUID library for Ruby. 
     48      require 'Win32API' 
     49     
     50      crypt_acquire_context = Win32API.new("advapi32", "CryptAcquireContext", 
     51                                           'PPPII', 'L') 
     52      crypt_gen_random = Win32API.new("advapi32", "CryptGenRandom",  
     53                                      'LIP', 'L') 
     54      crypt_release_context = Win32API.new("advapi32", "CryptReleaseContext", 
     55                                         'LI', 'L') 
     56      prov_rsa_full       = 1 
     57      crypt_verifycontext = 0xF0000000 
     58 
     59      hProvStr = " " * 4 
     60      if crypt_acquire_context.call(hProvStr, nil, nil, prov_rsa_full, 
     61                                    crypt_verifycontext) == 0 
     62        raise SystemCallError, "CryptAcquireContext failed: #{lastWin32ErrorMessage}" 
     63      end 
     64      hProv, = hProvStr.unpack('L') 
     65      bytes = " " * 64 
     66      if crypt_gen_random.call(hProv, bytes.size, bytes) == 0 
     67        raise SystemCallError, "CryptGenRandom failed: #{lastWin32ErrorMessage}" 
     68      end 
     69      if crypt_release_context.call(hProv, 0) == 0 
     70        raise SystemCallError, "CryptReleaseContext failed: #{lastWin32ErrorMessage}" 
     71      end 
     72      bytes.unpack("H*")[0] 
     73    end 
     74 
     75    # Generate a random secret key with Ruby 1.9's SecureRandom module. 
     76    # Raises LoadError if the current Ruby version does not support 
     77    # SecureRandom. 
     78    def generate_secret_with_secure_random 
     79      require 'securerandom' 
     80      return SecureRandom.hex(64) 
     81    end 
     82     
     83    # Generate a random secret key with OpenSSL. If OpenSSL is not 
     84    # already loaded, then this method will attempt to load it. 
     85    # LoadError will be raised if that fails. 
     86    def generate_secret_with_openssl 
     87      require 'openssl' 
     88      if !File.exist?("/dev/urandom") 
     89        # OpenSSL transparently seeds the random number generator with 
     90        # data from /dev/urandom. On platforms where that is not 
     91        # available, such as Windows, we have to provide OpenSSL with 
     92        # our own seed. Unfortunately there's no way to provide a 
     93        # secure seed without OS support, so we'll have to do with 
     94        # rand() and Time.now.usec(). 
     95        OpenSSL::Random.seed(rand(0).to_s + Time.now.usec.to_s) 
     96      end 
     97      data = OpenSSL::BN.rand(2048, -1, false).to_s 
     98      return OpenSSL::Digest::SHA512.new(data).hexdigest 
     99    end 
     100     
     101    # Generate a random secret key with /dev/urandom. 
     102    # Raises SystemCallError on failure. 
     103    def generate_secret_with_urandom 
     104      return File.read("/dev/urandom", 64).unpack("H*")[0] 
     105    end 
     106     
     107    # Generate a random secret key with Ruby's pseudo random number generator, 
     108    # as well as some environment information. 
     109    # 
     110    # This is the least cryptographically secure way to generate a secret key, 
     111    # and should be avoided whenever possible. 
     112    def generate_secret_with_prng 
     113      require 'digest/sha2' 
     114      sha = Digest::SHA2.new(512) 
     115      now = Time.now 
     116      sha << now.to_s 
     117      sha << String(now.usec) 
     118      sha << String(rand(0)) 
     119      sha << String($$) 
     120      sha << @identifier 
     121      return sha.hexdigest 
     122    end 
     123     
     124    private 
     125      def lastWin32ErrorMessage 
     126        # Following code is based on David Garamond's GUID library for Ruby. 
     127        get_last_error = Win32API.new("kernel32", "GetLastError", '', 'L') 
     128        format_message = Win32API.new("kernel32", "FormatMessageA", 
     129                                      'LPLLPLPPPPPPPP', 'L') 
     130        format_message_ignore_inserts  = 0x00000200 
     131        format_message_from_system     = 0x00001000 
     132 
     133        code = get_last_error.call 
     134        msg = "\0" * 1024 
     135        len = format_message.call(format_message_ignore_inserts + 
     136                                  format_message_from_system, 0, 
     137                                  code, 0, msg, 1024, nil, nil, 
     138                                  nil, nil, nil, nil, nil, nil) 
     139        msg[0, len].tr("\r", '').chomp 
     140      end 
     141  end 
     142end 
     143 
  • lib/rails_generator/generators/applications/app/app_generator.rb

    old new  
    11require 'rbconfig' 
    22require 'digest/md5'  
     3require 'rails_generator/secret_key_generator' 
    34 
    45class AppGenerator < Rails::Generator::Base 
    56  DEFAULT_SHEBANG = File.join(Config::CONFIG['bindir'], 
     
    3233    md5 << String(rand(0)) 
    3334    md5 << String($$) 
    3435    md5 << @app_name 
     36     
     37    # Do our best to generate a secure secret key for CookieStore 
     38    secret = Rails::SecretKeyGenerator.new(@app_name).generate_secret 
    3539 
    3640    record do |m| 
    3741      # Root directory and all subdirectories. 
     
    6165 
    6266      # Environments 
    6367      m.file "environments/boot.rb",    "config/boot.rb" 
    64       m.template "environments/environment.rb", "config/environment.rb", :assigns => { :freeze => options[:freeze], :app_name => @app_name, :app_secret => md5.hexdigest } 
     68      m.template "environments/environment.rb", "config/environment.rb", :assigns => { :freeze => options[:freeze], :app_name => @app_name, :app_secret => secret } 
    6569      m.file "environments/production.rb",  "config/environments/production.rb" 
    6670      m.file "environments/development.rb", "config/environments/development.rb" 
    6771      m.file "environments/test.rb",        "config/environments/test.rb"