lib/r509/spki.rb in r509-0.8.1 vs lib/r509/spki.rb in r509-0.9

- old
+ new

@@ -1,112 +1,164 @@ require 'openssl' require 'r509/exceptions' require 'r509/io_helpers' module R509 - # class for handling SPKAC/SPKI requests (typically generated by the <keygen> tag - class Spki - include R509::IOHelpers + # class for loading/generating SPKAC/SPKI requests (typically generated by the <keygen> tag + class SPKI + include R509::IOHelpers - attr_reader :subject, :spki, :san_names - # @option opts [String,OpenSSL::Netscape::SPKI] :spki the spki you want to parse - # @option opts [R509::Subject,Array,OpenSSL::X509::Name] :subject array of subject items - # @example [['CN','langui.sh'],['ST','Illinois'],['L','Chicago'],['C','US'],['emailAddress','ca@langui.sh']] - # you can also pass OIDs (see tests) - # @option opts [Array] :san_names array of SAN names - def initialize(opts={}) - if not opts.kind_of?(Hash) - raise ArgumentError, 'Must provide a hash of options' - end - if opts.has_key?(:spki) and not opts.has_key?(:subject) - raise ArgumentError, "Must provide both spki and subject" - end - if opts.has_key?(:san_names) and not opts[:san_names].kind_of?(Array) - raise ArgumentError, "if san_names are provided they must be in an Array" - end - @spki = OpenSSL::Netscape::SPKI.new(opts[:spki].sub("SPKAC=","")) - @subject = R509::Subject.new(opts[:subject]) - @san_names = opts[:san_names] || [] - end + attr_reader :spki, :key + # @option opts [String,OpenSSL::Netscape::SPKI] :spki the spki you want to parse + # @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). if supplied you do not need to pass an spki. + # @option opts [String] :message_digest Optional digest. sha1, sha224, sha256, sha384, sha512, md5. Defaults to sha1. Only used if you supply a :key and no :spki + def initialize(opts={}) + if not opts.kind_of?(Hash) + raise ArgumentError, 'Must provide a hash of options' + elsif not opts.has_key?(:spki) and not opts.has_key?(:key) + raise ArgumentError, 'Must provide either :spki or :key' + end - # @return [OpenSSL::PKey::RSA] public key - def public_key - @spki.public_key + if opts.has_key?(:key) + if opts[:key].kind_of?(R509::PrivateKey) + @key = opts[:key] + else + @key = R509::PrivateKey.new(:key => opts[:key]) end - - # Converts the SPKI into the PEM format - # - # @return [String] the SPKI converted into PEM format. - def to_pem - @spki.to_pem + end + if opts.has_key?(:spki) + spki = opts[:spki] + # first let's try cleaning up the input a bit so OpenSSL is happy with it + # OpenSSL hates SPKAC= + spki.sub!("SPKAC=","") + # it really hates newlines (Firefox loves 'em) + # so let's normalize line endings + spki.gsub!(/\r\n?/, "\n") + # and nuke 'em + spki.gsub!("\n", "") + # ...and leading/trailing whitespace + spki.strip! + @spki = OpenSSL::Netscape::SPKI.new(spki) + if not @key.nil? and not @spki.verify(@key.public_key) then + raise R509Error, 'Key does not match SPKI.' end + end + # create the SPKI from the private key if it wasn't passed in + if @spki.nil? + @spki = OpenSSL::Netscape::SPKI.new + @spki.public_key = @key.public_key + if @key.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') + end + @spki.sign(@key.key,message_digest.digest) + end + end - alias :to_s :to_pem + # @return [OpenSSL::PKey::RSA] public key + def public_key + @spki.public_key + end - # Converts the SPKI into the DER format - # - # @return [String] the SPKI converted into DER format. - def to_der - @spki.to_der - end + # Verifies the integrity of the signature on the SPKI + # @return [Boolean] + def verify_signature + @spki.verify(public_key) + end - # Writes the SPKI 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, @spki.to_pem) - end + # Converts the SPKI into the PEM format + # + # @return [String] the SPKI converted into PEM format. + def to_pem + @spki.to_pem + end - # Writes the SPKI 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, @spki.to_der) - end + alias :to_s :to_pem - # Returns whether the public key is RSA - # - # @return [Boolean] true if the public key is RSA, false otherwise - def rsa? - @spki.public_key.kind_of?(OpenSSL::PKey::RSA) - end + # Converts the SPKI into the DER format + # + # @return [String] the SPKI converted into DER format. + def to_der + @spki.to_der + end - # Returns whether the public key is DSA - # - # @return [Boolean] true if the public key is DSA, false otherwise - def dsa? - @spki.public_key.kind_of?(OpenSSL::PKey::DSA) - end + # Writes the SPKI 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, @spki.to_pem) + end - # Returns the bit strength of the key used to create the SPKI - # @return [Integer] the integer bit strength. - def bit_strength - if self.rsa? - return @spki.public_key.n.num_bits - elsif self.dsa? - return @spki.public_key.p.num_bits - end - end + # Writes the SPKI 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, @spki.to_der) + end - # Returns key algorithm (RSA/DSA) - # - # @return [String] value of the key algorithm. RSA or DSA - def key_algorithm - if @spki.public_key.kind_of? OpenSSL::PKey::RSA then - 'RSA' - elsif @spki.public_key.kind_of? OpenSSL::PKey::DSA then - 'DSA' - end - end + # Returns whether the public key is RSA + # + # @return [Boolean] true if the public key is RSA, false otherwise + def rsa? + @spki.public_key.kind_of?(OpenSSL::PKey::RSA) + end - # Returns a hash structure you can pass to the Ca - # You will want to call this method if you intend to alter the values - # and then pass them to the Ca class. - # - # @return [Hash] :subject and :san_names you can pass to Ca - def to_hash - { :subject => @subject.dup , :san_names => @san_names.dup } - end + # Returns whether the public key is DSA + # + # @return [Boolean] true if the public key is DSA, false otherwise + def dsa? + @spki.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? + @spki.public_key.kind_of?(OpenSSL::PKey::EC) + end + + # Returns the bit strength of the key used to create the SPKI + # @return [Integer] the integer bit strength. + def bit_strength + if self.rsa? + return @spki.public_key.n.num_bits + elsif self.dsa? + return @spki.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? + @spki.public_key.group.curve_name + else + raise R509::R509Error, 'Curve name is only available with EC SPKIs' + end + end + + # Returns key algorithm (RSA/DSA) + # + # @return [String] value of the key algorithm. RSA or DSA + def key_algorithm + if self.rsa? + :rsa + elsif self.dsa? + :dsa + elsif self.ec? + :ec + end + end + end end