lib/r509/private_key.rb in r509-0.9.2 vs lib/r509/private_key.rb in r509-0.10.0

- old
+ new

@@ -5,68 +5,37 @@ module R509 #private key management class PrivateKey include R509::IOHelpers - # @option opts [Symbol] :type :rsa/:dsa/:ec - # @option opts [String] :curve_name ("secp384r1") Only used if :type is :ec - # @option opts [Integer] :bit_strength (2048) Only used if :type is :rsa or :dsa. + # a list of key types + KNOWN_TYPES = ["RSA","DSA","EC"] + # the default type + DEFAULT_TYPE = "RSA" + # default bit length for DSA/RSA + DEFAULT_STRENGTH = 2048 + # default curve name for EC + DEFAULT_CURVE = "secp384r1" + + # @option opts [Symbol] :type Defaults to R509::PrivateKey::DEFAULT_TYPE. Allows R509::PrivateKey::KNOWN_TYPES. + # @option opts [String] :curve_name ("secp384r1") Only used if :type is EC + # @option opts [Integer] :bit_length (2048) Only used if :type is RSA or DSA + # @option opts [Integer] :bit_strength (2048) Deprecated, identical to bit_length. # @option opts [String] :password # @option opts [String,OpenSSL::PKey::RSA,OpenSSL::PKey::DSA,OpenSSL::PKey::EC] :key # @option opts [OpenSSL::Engine] :engine # @option opts [string] :key_name (used with engine) def initialize(opts={}) if not opts.kind_of?(Hash) raise ArgumentError, 'Must provide a hash of options' end + validate_engine(opts) - if opts.has_key?(:engine) and opts.has_key?(:key) - raise ArgumentError, 'You can\'t pass both :key and :engine' - elsif opts.has_key?(:key_name) and not opts.has_key?(:engine) - raise ArgumentError, 'When providing a :key_name you MUST provide an :engine' - elsif opts.has_key?(:engine) and not opts.has_key?(:key_name) - raise ArgumentError, 'When providing an :engine you MUST provide a :key_name' - elsif opts.has_key?(:engine) and opts.has_key?(:key_name) - if not opts[:engine].kind_of?(OpenSSL::Engine) - raise ArgumentError, 'When providing an engine, it must be of type OpenSSL::Engine' - end - @engine = opts[:engine] - @key_name = opts[:key_name] - end - if opts.has_key?(:key) - password = opts[:password] || nil - #OpenSSL::PKey.read solves this begin/rescue garbage but is only - #available to Ruby 1.9.3+ and may not solve the EC portion - begin - @key = OpenSSL::PKey::RSA.new(opts[:key],password) - rescue OpenSSL::PKey::RSAError - begin - @key = OpenSSL::PKey::DSA.new(opts[:key],password) - rescue - begin - @key = OpenSSL::PKey::EC.new(opts[:key],password) - rescue - raise R509::R509Error, "Failed to load private key. Invalid key or incorrect password." - end - end - end + validate_key(opts) else - bit_strength = opts[:bit_strength] || 2048 - type = opts[:type] || :rsa - case type - when :rsa - @key = OpenSSL::PKey::RSA.new(bit_strength) - when :dsa - @key = OpenSSL::PKey::DSA.new(bit_strength) - when :ec - curve_name = opts[:curve_name] || "secp384r1" - @key = OpenSSL::PKey::EC.new(curve_name) - @key.generate_key - else - raise ArgumentError, 'Must provide :rsa, :dsa , or :ec as type when key or engine is nil' - end + generate_key(opts) end end # Helper method to quickly load a private key from the filesystem # @@ -75,22 +44,23 @@ def self.load_from_file( filename, password = nil ) return R509::PrivateKey.new(:key => IOHelpers.read_data(filename), :password => password ) end - # Returns the bit strength of the key + # Returns the bit length of the key # # @return [Integer] - def bit_strength + def bit_length if self.rsa? return self.public_key.n.num_bits elsif self.dsa? return self.public_key.p.num_bits elsif self.ec? - raise R509::R509Error, 'Bit strength is not available for EC at this time.' + raise R509::R509Error, 'Bit length is not available for EC at this time.' end end + alias :bit_strength :bit_length # Returns the short name of the elliptic curve used to generate the private key # if the key is EC. If not, raises an error. # # @return [String] elliptic curve name @@ -221,8 +191,62 @@ # Returns whether the key is EC # # @return [Boolean] true if the key is EC, false otherwise def ec? self.key.kind_of?(OpenSSL::PKey::EC) + end + + private + + def validate_engine(opts) + if opts.has_key?(:engine) and opts.has_key?(:key) + raise ArgumentError, 'You can\'t pass both :key and :engine' + elsif opts.has_key?(:key_name) and not opts.has_key?(:engine) + raise ArgumentError, 'When providing a :key_name you MUST provide an :engine' + elsif opts.has_key?(:engine) and not opts.has_key?(:key_name) + raise ArgumentError, 'When providing an :engine you MUST provide a :key_name' + elsif opts.has_key?(:engine) and opts.has_key?(:key_name) + if not opts[:engine].kind_of?(OpenSSL::Engine) + raise ArgumentError, 'When providing an engine, it must be of type OpenSSL::Engine' + end + @engine = opts[:engine] + @key_name = opts[:key_name] + end + end + + def validate_key(opts) + password = opts[:password] || nil + #OpenSSL::PKey.read solves this begin/rescue garbage but is only + #available to Ruby 1.9.3+ and may not solve the EC portion + begin + @key = OpenSSL::PKey::RSA.new(opts[:key],password) + rescue OpenSSL::PKey::RSAError + begin + @key = OpenSSL::PKey::DSA.new(opts[:key],password) + rescue + begin + @key = OpenSSL::PKey::EC.new(opts[:key],password) + rescue + raise R509::R509Error, "Failed to load private key. Invalid key or incorrect password." + end + end + end + end + + def generate_key(opts) + bit_length = opts[:bit_length] || opts[:bit_strength] || DEFAULT_STRENGTH + type = opts[:type] || DEFAULT_TYPE + case type.upcase + when "RSA" + @key = OpenSSL::PKey::RSA.new(bit_length) + when "DSA" + @key = OpenSSL::PKey::DSA.new(bit_length) + when "EC" + curve_name = opts[:curve_name] || DEFAULT_CURVE + @key = OpenSSL::PKey::EC.new(curve_name) + @key.generate_key + else + raise ArgumentError, "Must provide #{KNOWN_TYPES.join(", ")} as type when key or engine is nil" + end end end end