lib/r509/csr.rb in r509-0.9.2 vs lib/r509/csr.rb in r509-0.10.0
- old
+ new
@@ -1,68 +1,70 @@
require 'openssl'
require 'r509/exceptions'
require 'r509/io_helpers'
+require 'r509/helpers'
require 'r509/private_key'
require 'r509/ec-hack'
require 'r509/asn1'
module R509
# The primary certificate signing request object
class CSR
include R509::IOHelpers
+ include R509::Helpers
attr_reader :san, :key, :subject, :req, :attributes, :message_digest
# @option opts [String,OpenSSL::X509::Request] :csr a csr
- # @option opts [Symbol] :type :rsa/:dsa/:ec required if not providing existing :csr. Defaults to :rsa
- # @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
+ # @option opts [String] :type Required if not providing existing :csr. 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] :message_digest Optional digest. sha1, sha224, sha256, sha384, sha512, md5. Defaults to sha1
- # @option opts [Array] :san_names List of domains, IPs, email addresses, or URIs to encode as subjectAltNames. The type is determined from the structure of the strings via the R509::ASN1.general_name_parser method
+ # @option opts [Array,R509::ASN1::GeneralNames] :san_names List of domains, IPs, email addresses, or URIs to encode as subjectAltNames. The type is determined from the structure of the strings via the R509::ASN1.general_name_parser method. You can also pass an explicit R509::ASN1::GeneralNames object. Parsed names will be uniqued, but a GeneralNames object will not be touched.
# @option opts [R509::Subject,Array,OpenSSL::X509::Name] :subject array of subject items
# @option opts [R509::PrivateKey,String] :key optional private key to supply. either an unencrypted PEM/DER string or an R509::PrivateKey object (use the latter if you need password/hardware support)
# @example Generate a 4096-bit RSA key + CSR
- # :type => :rsa,
- # :bit_strength => 4096,
+ # :type => "RSA",
+ # :bit_length => 4096,
# :subject => [
# ['CN','somedomain.com'],
# ['O','My Org'],
# ['L','City'],
# ['ST','State'],
# ['C','US']
# ]
+ # @example Generate a 2048-bit RSA key + CSR
+ # :type => "RSA",
+ # :bit_length => 4096,
+ # :subject => { :CN => "myCN", :O => "org" }
# @example Generate an ECDSA key using the secp384r1 curve parameters + CSR and sign with SHA512
- # :type => :ec,
+ # :type => "EC",
# :curve_name => 'secp384r1',
# :message_digest => 'sha512',
# :subject => [
# ['CN','somedomain.com'],
# ]
def initialize(opts={})
if not opts.kind_of?(Hash)
raise ArgumentError, 'Must provide a hash of options'
end
- if opts.has_key?(:subject) and opts.has_key?(:csr)
+ if opts.has_key?(:subject) and opts.has_key?(:csr)
raise ArgumentError, "You must provide :subject or :csr, not both"
end
- @bit_strength = opts[:bit_strength] || 2048
- @curve_name = opts[:curve_name] || "secp384r1"
+ @bit_length = opts[:bit_length] || opts[:bit_strength] || R509::PrivateKey::DEFAULT_STRENGTH
+ @curve_name = opts[:curve_name] || R509::PrivateKey::DEFAULT_CURVE
- if opts.has_key?(:key)
- if opts[:key].kind_of?(R509::PrivateKey)
- @key = opts[:key]
- else
- @key = R509::PrivateKey.new(:key => opts[:key])
- end
- end
+ @key = load_private_key(opts)
- @type = opts[:type] || :rsa
- if not [:rsa,:dsa,:ec].include?(@type) and @key.nil?
- raise ArgumentError, 'Must provide :rsa, :dsa, or :ec as type when key is nil'
+
+ @type = opts[:type] || R509::PrivateKey::DEFAULT_TYPE
+ if not R509::PrivateKey::KNOWN_TYPES.include?(@type.upcase) and @key.nil?
+ raise ArgumentError, "Must provide #{R509::PrivateKey::KNOWN_TYPES.join(", ")} as type when key is nil"
end
if opts.has_key?(:subject)
- san_names = R509::ASN1.general_name_parser(opts[:san_names] || [])
+ san_names = R509::ASN1.general_name_parser(opts[:san_names])
create_request(opts[:subject], san_names) #sets @req
elsif opts.has_key?(:csr)
if opts.has_key?(:san_names)
raise ArgumentError, "You can't add domains to an existing CSR"
end
@@ -74,14 +76,12 @@
if dsa?
#only DSS1 is acceptable for DSA signing in OpenSSL < 1.0
#post-1.0 you can sign with anything, but let's be conservative
#see: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/PKey/DSA.html
@message_digest = R509::MessageDigest.new('dss1')
- elsif opts.has_key?(:message_digest)
- @message_digest = R509::MessageDigest.new(opts[:message_digest])
else
- @message_digest = R509::MessageDigest.new('sha1')
+ @message_digest = R509::MessageDigest.new(opts[:message_digest])
end
if not opts.has_key?(:csr)
@req.sign(@key.key, @message_digest.digest)
end
@@ -119,87 +119,12 @@
else
false
end
end
- # Converts the CSR into the PEM format
- #
- # @return [String] the CSR converted into PEM format.
- def to_pem
- @req.to_pem
- end
-
alias :to_s :to_pem
- # Converts the CSR into the DER format
- #
- # @return [String] the CSR converted into DER format.
- def to_der
- @req.to_der
- end
-
- # Writes the CSR into the PEM format
- #
- # @param [String, #write] filename_or_io Either a string of the path for
- # the file that you'd like to write, or an IO-like object.
- def write_pem(filename_or_io)
- write_data(filename_or_io, @req.to_pem)
- end
-
- # Writes the CSR into the DER format
- #
- # @param [String, #write] filename_or_io Either a string of the path for
- # the file that you'd like to write, or an IO-like object.
- def write_der(filename_or_io)
- write_data(filename_or_io, @req.to_der)
- end
-
- # Returns whether the public key is RSA
- #
- # @return [Boolean] true if the public key is RSA, false otherwise
- def rsa?
- @req.public_key.kind_of?(OpenSSL::PKey::RSA)
- end
-
- # Returns whether the public key is DSA
- #
- # @return [Boolean] true if the public key is DSA, false otherwise
- def dsa?
- @req.public_key.kind_of?(OpenSSL::PKey::DSA)
- end
-
- # Returns whether the public key is EC
- #
- # @return [Boolean] true if the public key is EC, false otherwise
- def ec?
- @req.public_key.kind_of?(OpenSSL::PKey::EC)
- end
-
- # Returns the bit strength of the key used to create the CSR
- # @return [Integer] the integer bit strength.
- def bit_strength
- if self.rsa?
- return @req.public_key.n.num_bits
- elsif self.dsa?
- return @req.public_key.p.num_bits
- elsif self.ec?
- raise R509::R509Error, 'Bit strength is not available for EC at this time.'
- end
- end
-
- # Returns the short name of the elliptic curve used to generate the public key
- # if the key is EC. If not, raises an error.
- #
- # @return [String] elliptic curve name
- def curve_name
- if self.ec?
- self.public_key.group.curve_name
- else
- raise R509::R509Error, 'Curve name is only available with EC CSRs'
- end
- end
-
# Returns subject component
#
# @return [String] value of the subject component requested
def subject_component short_name
@req.subject.to_a.each do |element|
@@ -217,18 +142,18 @@
@req.signature_algorithm
end
# Returns key algorithm (RSA/DSA/EC)
#
- # @return [Symbol] value of the key algorithm. :rsa, :dsa, :ec
+ # @return [String] value of the key algorithm. RSA, DSA, or EC
def key_algorithm
if @req.public_key.kind_of? OpenSSL::PKey::RSA then
- :rsa
+ "RSA"
elsif @req.public_key.kind_of? OpenSSL::PKey::DSA then
- :dsa
+ "DSA"
elsif @req.public_key.kind_of? OpenSSL::PKey::EC then
- :ec
+ "EC"
end
end
private
@@ -263,51 +188,45 @@
@req = OpenSSL::X509::Request.new
@req.version = 0
@subject = R509::Subject.new(subject)
@req.subject = @subject.name
if @key.nil?
- @key = R509::PrivateKey.new(:type => @type, :bit_strength => @bit_strength, :curve_name => @curve_name)
+ @key = R509::PrivateKey.new(:type => @type, :bit_length => @bit_length, :curve_name => @curve_name)
end
@req.public_key = @key.public_key
add_san_extension(san_names)
- parse_san_attribute_from_csr(@req)
end
# @return [Array] array of GeneralName objects
def parse_san_attribute_from_csr(req)
- san = nil
- set = nil
req.attributes.each do |attribute|
if attribute.oid == 'extReq'
set = OpenSSL::ASN1.decode attribute.value
extensions = set.value[0].value.collect{|asn1ext| OpenSSL::X509::Extension.new(asn1ext) }
r509_extensions = R509::Cert::Extensions.wrap_openssl_extensions( extensions )
if not r509_extensions[R509::Cert::Extensions::SubjectAlternativeName].nil?
- san = r509_extensions[R509::Cert::Extensions::SubjectAlternativeName].general_names
+ @san = r509_extensions[R509::Cert::Extensions::SubjectAlternativeName].general_names
end
break
end
end
- @san = san
end
def add_san_extension(san_names)
- if not san_names.nil? and not san_names.names.empty?
- names = san_names.names.uniq
- general_names = R509::ASN1::GeneralNames.new
- names.each do |domain|
- general_names.add_item(domain)
- end
+ if san_names.kind_of?(R509::ASN1::GeneralNames) and not san_names.names.empty?
ef = OpenSSL::X509::ExtensionFactory.new
- serialized = general_names.serialize_names
+ serialized = san_names.serialize_names
ef.config = OpenSSL::Config.parse(serialized[:conf])
ex = []
ex << ef.create_extension("subjectAltName", serialized[:extension_string])
request_extension_set = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(ex)])
@req.add_attribute(OpenSSL::X509::Attribute.new("extReq", request_extension_set))
parse_san_attribute_from_csr(@req)
end
end
+ def internal_obj
+ @req
+ end
end
end