diff options
| author | elijah <elijah@riseup.net> | 2014-10-21 17:32:28 -0700 | 
|---|---|---|
| committer | elijah <elijah@riseup.net> | 2014-10-21 17:32:28 -0700 | 
| commit | a6b4ff1c21915475655a4a28c163904687d1035e (patch) | |
| tree | 00502e075dc5ecafa10df9a3165033300658dd0b | |
| parent | 9e5572c7177ab10904aafbdcba99de0364e57db9 (diff) | |
| download | leap_cli-a6b4ff1c21915475655a4a28c163904687d1035e.tar.gz leap_cli-a6b4ff1c21915475655a4a28c163904687d1035e.tar.bz2 | |
fixed `leap cert csr` to add correct "Requested Extensions" attribute on the CSR.
9 files changed, 583 insertions, 111 deletions
| diff --git a/leap_cli.gemspec b/leap_cli.gemspec index d0b9a99..cbf0674 100644 --- a/leap_cli.gemspec +++ b/leap_cli.gemspec @@ -78,4 +78,5 @@ spec = Gem::Specification.new do |s|    # certificate_authority    s.add_runtime_dependency("activemodel", ">= 3.0.6") +  s.add_runtime_dependency("activesupport", ">= 3.0.6")  end diff --git a/lib/leap_cli/commands/ca.rb b/lib/leap_cli/commands/ca.rb index ef8cd71..ea4c8a8 100644 --- a/lib/leap_cli/commands/ca.rb +++ b/lib/leap_cli/commands/ca.rb @@ -116,7 +116,6 @@ module LeapCli; module Commands          # CSR          dn  = CertificateAuthority::DistinguishedName.new -        csr = CertificateAuthority::SigningRequest.new          dn.common_name   = domain          dn.organization  = options[:organization] || provider.name[provider.default_language]          dn.ou            = options[:organizational_unit] # optional @@ -127,9 +126,7 @@ module LeapCli; module Commands          digest = options[:digest] || server_certificates.digest          log :generating, "CSR with #{digest} digest and #{print_dn(dn)}" do -          csr.distinguished_name = dn -          csr.key_material = keypair -          csr.digest = digest +          csr = create_csr(dn, keypair, digest)            request = csr.to_x509_csr            write_file! [:commercial_csr, domain], csr.to_pem          end @@ -289,6 +286,44 @@ module LeapCli; module Commands      yield cert.key_material.private_key.to_pem, cert.to_pem    end +  # +  # creates a CSR and returns it. +  # with the correct extReq attribute so that the CA +  # doens't generate certs with extensions we don't want. +  # +  def create_csr(dn, keypair, digest) +    csr = CertificateAuthority::SigningRequest.new +    csr.distinguished_name = dn +    csr.key_material = keypair +    csr.digest = digest + +    # define extensions manually (library doesn't support setting these on CSRs) +    extensions = [] +    extensions << CertificateAuthority::Extensions::BasicConstraints.new.tap {|basic| +      basic.ca = false +    } +    extensions << CertificateAuthority::Extensions::KeyUsage.new.tap {|keyusage| +      keyusage.usage = ["digitalSignature", "nonRepudiation"] +    } +    extensions << CertificateAuthority::Extensions::ExtendedKeyUsage.new.tap {|extkeyusage| +      extkeyusage.usage = [ "serverAuth"] +    } + +    # convert extensions to attribute 'extReq' +    # aka "Requested Extensions" +    factory = OpenSSL::X509::ExtensionFactory.new +    attrval = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence( +      extensions.map{|e| factory.create_ext(e.openssl_identifier, e.to_s, e.critical)} +    )]) +    attrs = [ +      OpenSSL::X509::Attribute.new("extReq", attrval), +      OpenSSL::X509::Attribute.new("msExtReq", attrval) +    ] +    csr.attributes = attrs + +    return csr +  end +    def ca_root      @ca_root ||= begin        load_certificate_file(:ca_cert, :ca_key) diff --git a/vendor/certificate_authority/certificate_authority.gemspec b/vendor/certificate_authority/certificate_authority.gemspec index be8cd91..b7e8676 100644 --- a/vendor/certificate_authority/certificate_authority.gemspec +++ b/vendor/certificate_authority/certificate_authority.gemspec @@ -61,7 +61,7 @@ Gem::Specification.new do |s|      "spec/units/units_helper.rb",      "spec/units/working_with_openssl_spec.rb"    ] -  s.homepage = "http://github.com/cchandler/certificate_authority" +  s.homepage = "https://github.com/cchandler/certificate_authority"    s.licenses = ["MIT"]    s.require_paths = ["lib"]    s.rubygems_version = "1.8.15" @@ -72,15 +72,18 @@ Gem::Specification.new do |s|      if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then        s.add_runtime_dependency(%q<activemodel>, [">= 3.0.6"]) +      s.add_runtime_dependency(%q<activesupport>, [">= 3.0.6"])        s.add_development_dependency(%q<rspec>, [">= 0"])        s.add_development_dependency(%q<jeweler>, [">= 1.5.2"])      else        s.add_dependency(%q<activemodel>, [">= 3.0.6"]) +      s.add_dependency(%q<activesupport>, [">= 3.0.6"])        s.add_dependency(%q<rspec>, [">= 0"])        s.add_dependency(%q<jeweler>, [">= 1.5.2"])      end    else      s.add_dependency(%q<activemodel>, [">= 3.0.6"]) +    s.add_dependency(%q<activesupport>, [">= 3.0.6"])      s.add_dependency(%q<rspec>, [">= 0"])      s.add_dependency(%q<jeweler>, [">= 1.5.2"])    end diff --git a/vendor/certificate_authority/lib/certificate_authority/certificate.rb b/vendor/certificate_authority/lib/certificate_authority/certificate.rb index ca8bc7c..f096c5a 100644 --- a/vendor/certificate_authority/lib/certificate_authority/certificate.rb +++ b/vendor/certificate_authority/lib/certificate_authority/certificate.rb @@ -1,3 +1,5 @@ +require 'active_support/all' +  module CertificateAuthority    class Certificate      include ActiveModel::Validations @@ -32,8 +34,8 @@ module CertificateAuthority        self.distinguished_name = DistinguishedName.new        self.serial_number = SerialNumber.new        self.key_material = MemoryKeyMaterial.new -      self.not_before = Time.now -      self.not_after = Time.now + 60 * 60 * 24 * 365 #One year +      self.not_before = Time.now.change(:min => 0).utc +      self.not_after = Time.now.change(:min => 0).utc + 1.year        self.parent = self        self.extensions = load_extensions() @@ -41,12 +43,31 @@ module CertificateAuthority      end +=begin +    def self.from_openssl openssl_cert +      unless openssl_cert.is_a? OpenSSL::X509::Certificate +        raise "Can only construct from an OpenSSL::X509::Certificate" +      end + +      certificate = Certificate.new +      # Only subject, key_material, and body are used for signing +      certificate.distinguished_name = DistinguishedName.from_openssl openssl_cert.subject +      certificate.key_material.public_key = openssl_cert.public_key +      certificate.openssl_body = openssl_cert +      certificate.serial_number.number = openssl_cert.serial.to_i +      certificate.not_before = openssl_cert.not_before +      certificate.not_after = openssl_cert.not_after +      # TODO extensions +      certificate +    end +=end +      def sign!(signing_profile={})        raise "Invalid certificate #{self.errors.full_messages}" unless valid?        merge_profile_with_extensions(signing_profile)        openssl_cert = OpenSSL::X509::Certificate.new -      openssl_cert.version    = 2 +      openssl_cert.version = 2        openssl_cert.not_before = self.not_before        openssl_cert.not_after = self.not_after        openssl_cert.public_key = self.key_material.public_key @@ -58,7 +79,6 @@ module CertificateAuthority        require 'tempfile'        t = Tempfile.new("bullshit_conf") -      # t = File.new("/tmp/openssl.cnf")        ## The config requires a file even though we won't use it        openssl_config = OpenSSL::Config.new(t.path) @@ -85,7 +105,7 @@ module CertificateAuthority        self.extensions.keys.sort{|a,b| b<=>a}.each do |k|          e = extensions[k]          next if e.to_s.nil? or e.to_s == "" ## If the extension returns an empty string we won't include it -        ext = factory.create_ext(e.openssl_identifier, e.to_s) +        ext = factory.create_ext(e.openssl_identifier, e.to_s, e.critical)          openssl_cert.add_extension(ext)        end @@ -94,9 +114,10 @@ module CertificateAuthority        else          digest = OpenSSL::Digest::Digest.new(signing_profile["digest"])        end -      self.openssl_body = openssl_cert.sign(parent.key_material.private_key,digest) -      t.close! if t.is_a?(Tempfile)# We can get rid of the ridiculous temp file -      self.openssl_body + +      self.openssl_body = openssl_cert.sign(parent.key_material.private_key, digest) +    ensure +      t.close! if t # We can get rid of the ridiculous temp file      end      def is_signing_entity? @@ -116,6 +137,34 @@ module CertificateAuthority        self.openssl_body.to_pem      end +    def to_csr +      csr = SigningRequest.new +      csr.distinguished_name = self.distinguished_name +      csr.key_material = self.key_material +      factory = OpenSSL::X509::ExtensionFactory.new +      exts = [] +      self.extensions.keys.each do |k| +        ## Don't copy over key identifiers for CSRs +        next if k == "subjectKeyIdentifier" || k == "authorityKeyIdentifier" +        e = extensions[k] +        ## If the extension returns an empty string we won't include it +        next if e.to_s.nil? or e.to_s == "" +        exts << factory.create_ext(e.openssl_identifier, e.to_s, e.critical) +      end +      attrval = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(exts)]) +      attrs = [ +        OpenSSL::X509::Attribute.new("extReq", attrval), +        OpenSSL::X509::Attribute.new("msExtReq", attrval) +      ] +      csr.attributes = attrs +      csr +    end + +    def self.from_x509_cert(raw_cert) +      openssl_cert = OpenSSL::X509::Certificate.new(raw_cert) +      Certificate.from_openssl(openssl_cert) +    end +      def is_root_entity?        self.parent == self && is_signing_entity?      end @@ -134,6 +183,16 @@ module CertificateAuthority          items = signing_config[k]          items.keys.each do |profile_item_key|            if extension.respond_to?("#{profile_item_key}=".to_sym) +            if k == 'subjectAltName' && profile_item_key == 'emails' +              items[profile_item_key].map do |email| +                if email == 'email:copy' +                  fail "no email address provided for subject: #{subject.to_x509_name}" unless subject.email_address +                  "email:#{subject.email_address}" +                else +                  email +                end +              end +            end              extension.send("#{profile_item_key}=".to_sym, items[profile_item_key] )            else              p "Tried applying '#{profile_item_key}' to #{extension.class} but it doesn't respond!" @@ -142,30 +201,25 @@ module CertificateAuthority        end      end +    # Enumeration of the extensions. Not the worst option since +    # the likelihood of these needing to be updated is low at best. +    EXTENSIONS = [ +        CertificateAuthority::Extensions::BasicConstraints, +        CertificateAuthority::Extensions::CrlDistributionPoints, +        CertificateAuthority::Extensions::SubjectKeyIdentifier, +        CertificateAuthority::Extensions::AuthorityKeyIdentifier, +        CertificateAuthority::Extensions::AuthorityInfoAccess, +        CertificateAuthority::Extensions::KeyUsage, +        CertificateAuthority::Extensions::ExtendedKeyUsage, +        CertificateAuthority::Extensions::SubjectAlternativeName, +        CertificateAuthority::Extensions::CertificatePolicies +    ] +      def load_extensions        extension_hash = {} -      temp_extensions = [] -      basic_constraints = CertificateAuthority::Extensions::BasicConstraints.new -      temp_extensions << basic_constraints -      crl_distribution_points = CertificateAuthority::Extensions::CrlDistributionPoints.new -      temp_extensions << crl_distribution_points -      subject_key_identifier = CertificateAuthority::Extensions::SubjectKeyIdentifier.new -      temp_extensions << subject_key_identifier -      authority_key_identifier = CertificateAuthority::Extensions::AuthorityKeyIdentifier.new -      temp_extensions << authority_key_identifier -      authority_info_access = CertificateAuthority::Extensions::AuthorityInfoAccess.new -      temp_extensions << authority_info_access -      key_usage = CertificateAuthority::Extensions::KeyUsage.new -      temp_extensions << key_usage -      extended_key_usage = CertificateAuthority::Extensions::ExtendedKeyUsage.new -      temp_extensions << extended_key_usage -      subject_alternative_name = CertificateAuthority::Extensions::SubjectAlternativeName.new -      temp_extensions << subject_alternative_name -      certificate_policies = CertificateAuthority::Extensions::CertificatePolicies.new -      temp_extensions << certificate_policies - -      temp_extensions.each do |extension| +      EXTENSIONS.each do |klass| +        extension = klass.new          extension_hash[extension.openssl_identifier] = extension        end @@ -192,7 +246,11 @@ module CertificateAuthority        certificate.serial_number.number = openssl_cert.serial.to_i        certificate.not_before = openssl_cert.not_before        certificate.not_after = openssl_cert.not_after -      # TODO extensions +      EXTENSIONS.each do |klass| +        _,v,c = (openssl_cert.extensions.detect { |e| e.to_a.first == klass::OPENSSL_IDENTIFIER } || []).to_a +        certificate.extensions[klass::OPENSSL_IDENTIFIER] = klass.parse(v, c) if v +      end +        certificate      end diff --git a/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb b/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb index 165fe29..32d9c1e 100644 --- a/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb +++ b/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb @@ -32,11 +32,16 @@ module CertificateAuthority      alias :emailAddress :email_address      alias :emailAddress= :email_address= +    attr_accessor :serial_number +    alias :serialNumber :serial_number +    alias :serialNumber= :serial_number= +      def to_x509_name        raise "Invalid Distinguished Name" unless valid?        # NB: the capitalization in the strings counts        name = OpenSSL::X509::Name.new +      name.add_entry("serialNumber", serial_number) unless serial_number.blank?        name.add_entry("C", country) unless country.blank?        name.add_entry("ST", state) unless state.blank?        name.add_entry("L", locality) unless locality.blank? diff --git a/vendor/certificate_authority/lib/certificate_authority/extensions.rb b/vendor/certificate_authority/lib/certificate_authority/extensions.rb index e5a8e85..89de032 100644 --- a/vendor/certificate_authority/lib/certificate_authority/extensions.rb +++ b/vendor/certificate_authority/lib/certificate_authority/extensions.rb @@ -5,6 +5,10 @@ module CertificateAuthority          raise "Implementation required"        end +      def self.parse(value, critical) +        raise "Implementation required" +      end +        def config_extensions          {}        end @@ -12,21 +16,40 @@ module CertificateAuthority        def openssl_identifier          raise "Implementation required"        end + +      def ==(value) +        raise "Implementation required" +      end      end +    # Specifies whether an X.509v3 certificate can act as a CA, signing other +    # certificates to be verified. If set, a path length constraint can also be +    # specified. +    # Reference: Section 4.2.1.10 of RFC3280 +    # http://tools.ietf.org/html/rfc3280#section-4.2.1.10      class BasicConstraints +      OPENSSL_IDENTIFIER = "basicConstraints" +        include ExtensionAPI        include ActiveModel::Validations + +      attr_accessor :critical        attr_accessor :ca        attr_accessor :path_len +      validates :critical, :inclusion => [true,false]        validates :ca, :inclusion => [true,false]        def initialize -        self.ca = false +        @critical = false +        @ca = false +      end + +      def openssl_identifier +        OPENSSL_IDENTIFIER        end        def is_ca? -        self.ca +        @ca        end        def path_len=(value) @@ -34,29 +57,54 @@ module CertificateAuthority          @path_len = value        end -      def openssl_identifier -        "basicConstraints" +      def to_s +        res = [] +        res << "CA:#{@ca}" +        res << "pathlen:#{@path_len}" unless @path_len.nil? +        res.join(',')        end -      def to_s -        result = "" -        result += "CA:#{self.ca}" -        result += ",pathlen:#{self.path_len}" unless self.path_len.nil? -        result +      def ==(o) +        o.class == self.class && o.state == state +      end + +      def self.parse(value, critical) +        obj = self.new +        return obj if value.nil? +        obj.critical = critical +        value.split(/,\s*/).each do |v| +          c = v.split(':', 2) +          obj.ca = (c.last.upcase == "TRUE") if c.first == "CA" +          obj.path_len = c.last.to_i if c.first == "pathlen" +        end +        obj +      end + +      protected +      def state +        [@critical,@ca,@path_len]        end      end +    # Specifies where CRL information be be retrieved. This extension isn't +    # critical, but is recommended for proper CAs. +    # Reference: Section 4.2.1.14 of RFC3280 +    # http://tools.ietf.org/html/rfc3280#section-4.2.1.14      class CrlDistributionPoints +      OPENSSL_IDENTIFIER = "crlDistributionPoints" +        include ExtensionAPI -      attr_accessor :uri +      attr_accessor :critical +      attr_accessor :uris        def initialize -        # self.uri = "http://moo.crlendPoint.example.com/something.crl" +        @critical = false +        @uris = []        end        def openssl_identifier -        "crlDistributionPoints" +        OPENSSL_IDENTIFIER        end        ## NB: At this time it seems OpenSSL's extension handlers don't support @@ -69,99 +117,302 @@ module CertificateAuthority          }        end +      # This is for legacy support. Technically it can (and probably should) +      # be an array. But if someone is calling the old accessor we shouldn't +      # necessarily break it. +      def uri=(value) +        @uris << value +      end +        def to_s -        return "" if self.uri.nil? -        "URI:#{self.uri}" +        res = [] +        @uris.each do |uri| +          res << "URI:#{uri}" +        end +        res.join(',') +      end + +      def ==(o) +        o.class == self.class && o.state == state +      end + +      def self.parse(value, critical) +        obj = self.new +        return obj if value.nil? +        obj.critical = critical +        value.split(/,\s*/).each do |v| +          c = v.split(':', 2) +          obj.uris << c.last if c.first == "URI" +        end +        obj +      end + +      protected +      def state +        [@critical,@uri]        end      end +    # Identifies the public key associated with a given certificate. +    # Should be required for "CA" certificates. +    # Reference: Section 4.2.1.2 of RFC3280 +    # http://tools.ietf.org/html/rfc3280#section-4.2.1.2      class SubjectKeyIdentifier +      OPENSSL_IDENTIFIER = "subjectKeyIdentifier" +        include ExtensionAPI + +      attr_accessor :critical +      attr_accessor :identifier + +      def initialize +        @critical = false +        @identifier = "hash" +      end +        def openssl_identifier -        "subjectKeyIdentifier" +        OPENSSL_IDENTIFIER        end        def to_s -        "hash" +        res = [] +        res << @identifier +        res.join(',') +      end + +      def ==(o) +        o.class == self.class && o.state == state +      end + +      def self.parse(value, critical) +        obj = self.new +        return obj if value.nil? +        obj.critical = critical +        obj.identifier = value +        obj +      end + +      protected +      def state +        [@critical,@identifier]        end      end +    # Identifies the public key associated with a given private key. +    # Reference: Section 4.2.1.1 of RFC3280 +    # http://tools.ietf.org/html/rfc3280#section-4.2.1.1      class AuthorityKeyIdentifier +      OPENSSL_IDENTIFIER = "authorityKeyIdentifier" +        include ExtensionAPI +      attr_accessor :critical +      attr_accessor :identifier + +      def initialize +        @critical = false +        @identifier = ["keyid", "issuer"] +      end +        def openssl_identifier -        "authorityKeyIdentifier" +        OPENSSL_IDENTIFIER        end        def to_s -        "keyid,issuer" +        res = [] +        res += @identifier +        res.join(',') +      end + +      def ==(o) +        o.class == self.class && o.state == state +      end + +      def self.parse(value, critical) +        obj = self.new +        return obj if value.nil? +        obj.critical = critical +        obj.identifier = value.split(/,\s*/).last.chomp +        obj +      end + +      protected +      def state +        [@critical,@identifier]        end      end +    # Specifies how to access CA information and services for the CA that +    # issued this certificate. +    # Generally used to specify OCSP servers. +    # Reference: Section 4.2.2.1 of RFC3280 +    # http://tools.ietf.org/html/rfc3280#section-4.2.2.1      class AuthorityInfoAccess +      OPENSSL_IDENTIFIER = "authorityInfoAccess" +        include ExtensionAPI +      attr_accessor :critical        attr_accessor :ocsp +      attr_accessor :ca_issuers        def initialize -        self.ocsp = [] +        @critical = false +        @ocsp = [] +        @ca_issuers = []        end        def openssl_identifier -        "authorityInfoAccess" +        OPENSSL_IDENTIFIER        end        def to_s -        return "" if self.ocsp.empty? -        "OCSP;URI:#{self.ocsp}" +        res = [] +        res += @ocsp.map {|o| "OCSP;URI:#{o}" } +        res += @ca_issuers.map {|c| "caIssuers;URI:#{c}" } +        res.join(',') +      end + +      def ==(o) +        o.class == self.class && o.state == state +      end + +      def self.parse(value, critical) +        obj = self.new +        return obj if value.nil? +        obj.critical = critical +        value.split("\n").each do |v| +          if v.starts_with?("OCSP") +            obj.ocsp << v.split.last +          end + +          if v.starts_with?("CA Issuers") +            obj.ca_issuers << v.split.last +          end +        end +        obj +      end + +      protected +      def state +        [@critical,@ocsp,@ca_issuers]        end      end +    # Specifies the allowed usage purposes of the keypair specified in this certificate. +    # Reference: Section 4.2.1.3 of RFC3280 +    # http://tools.ietf.org/html/rfc3280#section-4.2.1.3 +    # +    # Note: OpenSSL when parsing an extension will return results in the form +    # 'Digital Signature', but on signing you have to set it to 'digitalSignature'. +    # So copying an extension from an imported cert isn't going to work yet.      class KeyUsage +      OPENSSL_IDENTIFIER = "keyUsage" +        include ExtensionAPI +      attr_accessor :critical        attr_accessor :usage        def initialize -        self.usage = ["digitalSignature", "nonRepudiation"] +        @critical = false +        @usage = ["digitalSignature", "nonRepudiation"]        end        def openssl_identifier -        "keyUsage" +        OPENSSL_IDENTIFIER        end        def to_s -        "#{self.usage.join(',')}" +        res = [] +        res += @usage +        res.join(',') +      end + +      def ==(o) +        o.class == self.class && o.state == state +      end + +      def self.parse(value, critical) +        obj = self.new +        return obj if value.nil? +        obj.critical = critical +        obj.usage = value.split(/,\s*/) +        obj +      end + +      protected +      def state +        [@critical,@usage]        end      end +    # Specifies even more allowed usages in addition to what is specified in +    # the Key Usage extension. +    # Reference: Section 4.2.1.13 of RFC3280 +    # http://tools.ietf.org/html/rfc3280#section-4.2.1.13      class ExtendedKeyUsage +      OPENSSL_IDENTIFIER = "extendedKeyUsage" +        include ExtensionAPI +      attr_accessor :critical        attr_accessor :usage        def initialize -        self.usage = ["serverAuth","clientAuth"] +        @critical = false +        @usage = ["serverAuth"]        end        def openssl_identifier -        "extendedKeyUsage" +        OPENSSL_IDENTIFIER        end        def to_s -        "#{self.usage.join(',')}" +        res = [] +        res += @usage +        res.join(',') +      end + +      def ==(o) +        o.class == self.class && o.state == state +      end + +      def self.parse(value, critical) +        obj = self.new +        return obj if value.nil? +        obj.critical = critical +        obj.usage = value.split(/,\s*/) +        obj +      end + +      protected +      def state +        [@critical,@usage]        end      end +    # Specifies additional "names" for which this certificate is valid. +    # Reference: Section 4.2.1.7 of RFC3280 +    # http://tools.ietf.org/html/rfc3280#section-4.2.1.7      class SubjectAlternativeName +      OPENSSL_IDENTIFIER = "subjectAltName" +        include ExtensionAPI -      attr_accessor :uris, :dns_names, :ips +      attr_accessor :critical +      attr_accessor :uris, :dns_names, :ips, :emails        def initialize -        self.uris = [] -        self.dns_names = [] -        self.ips = [] +        @critical = false +        @uris = [] +        @dns_names = [] +        @ips = [] +        @emails = [] +      end + +      def openssl_identifier +        OPENSSL_IDENTIFIER        end        def uris=(value) @@ -179,22 +430,50 @@ module CertificateAuthority          @ips = value        end -      def openssl_identifier -        "subjectAltName" +      def emails=(value) +        raise "Emails must be an array" unless value.is_a?(Array) +        @emails = value        end        def to_s -        res =  self.uris.map {|u| "URI:#{u}" } -        res += self.dns_names.map {|d| "DNS:#{d}" } -        res += self.ips.map {|i| "IP:#{i}" } +        res = [] +        res += @uris.map {|u| "URI:#{u}" } +        res += @dns_names.map {|d| "DNS:#{d}" } +        res += @ips.map {|i| "IP:#{i}" } +        res += @emails.map {|i| "email:#{i}" } +        res.join(',') +      end + +      def ==(o) +        o.class == self.class && o.state == state +      end + +      def self.parse(value, critical) +        obj = self.new +        return obj if value.nil? +        obj.critical = critical +        value.split(/,\s*/).each do |v| +          c = v.split(':', 2) +          obj.uris << c.last if c.first == "URI" +          obj.dns_names << c.last if c.first == "DNS" +          obj.ips << c.last if c.first == "IP" +          obj.emails << c.last if c.first == "EMAIL" +        end +        obj +      end -        return res.join(',') +      protected +      def state +        [@critical,@uris,@dns_names,@ips,@emails]        end      end      class CertificatePolicies +      OPENSSL_IDENTIFIER = "certificatePolicies" +        include ExtensionAPI +      attr_accessor :critical        attr_accessor :policy_identifier        attr_accessor :cps_uris        ##User notice @@ -203,12 +482,12 @@ module CertificateAuthority        attr_accessor :notice_numbers        def initialize +        self.critical = false          @contains_data = false        end -        def openssl_identifier -        "certificatePolicies" +        OPENSSL_IDENTIFIER        end        def user_notice=(value={}) @@ -258,7 +537,93 @@ module CertificateAuthority        def to_s          return "" unless @contains_data -        "ia5org,@custom_policies" +        res = [] +        res << "ia5org" +        res += @config_extensions["custom_policies"] unless @config_extensions.nil? +        res.join(',') +      end + +      def self.parse(value, critical) +        obj = self.new +        return obj if value.nil? +        obj.critical = critical +        value.split(/,\s*/).each do |v| +          c = v.split(':', 2) +          obj.policy_identifier = c.last if c.first == "policyIdentifier" +          obj.cps_uris << c.last if c.first =~ %r{CPS.\d+} +          # TODO: explicit_text, organization, notice_numbers +        end +        obj +      end +    end + +    # DEPRECATED +    # Specifics the purposes for which a certificate can be used. +    # The basicConstraints, keyUsage, and extendedKeyUsage extensions are now used instead. +    # https://www.openssl.org/docs/apps/x509v3_config.html#Netscape_Certificate_Type +    class NetscapeCertificateType +      OPENSSL_IDENTIFIER = "nsCertType" + +      include ExtensionAPI + +      attr_accessor :critical +      attr_accessor :flags + +      def initialize +        self.critical = false +        self.flags = [] +      end + +      def openssl_identifier +        OPENSSL_IDENTIFIER +      end + +      def to_s +        res = [] +        res += self.flags +        res.join(',') +      end + +      def self.parse(value, critical) +        obj = self.new +        return obj if value.nil? +        obj.critical = critical +        obj.flags = value.split(/,\s*/) +        obj +      end +    end + +    # DEPRECATED +    # Contains a comment which will be displayed when the certificate is viewed in some browsers. +    # https://www.openssl.org/docs/apps/x509v3_config.html#Netscape_String_extensions_ +    class NetscapeComment +      OPENSSL_IDENTIFIER = "nsComment" + +      include ExtensionAPI + +      attr_accessor :critical +      attr_accessor :comment + +      def initialize +        self.critical = false +      end + +      def openssl_identifier +        OPENSSL_IDENTIFIER +      end + +      def to_s +        res = [] +        res << self.comment if self.comment +        res.join(',') +      end + +      def self.parse(value, critical) +        obj = self.new +        return obj if value.nil? +        obj.critical = critical +        obj.comment = value +        obj        end      end diff --git a/vendor/certificate_authority/lib/certificate_authority/key_material.rb b/vendor/certificate_authority/lib/certificate_authority/key_material.rb index 75ec62e..1fd4dd9 100644 --- a/vendor/certificate_authority/lib/certificate_authority/key_material.rb +++ b/vendor/certificate_authority/lib/certificate_authority/key_material.rb @@ -111,38 +111,4 @@ module CertificateAuthority        @public_key      end    end - -  class SigningRequestKeyMaterial -    include KeyMaterial -    include ActiveModel::Validations - -    validates_each :public_key do |record, attr, value| -      record.errors.add :public_key, "cannot be blank" if record.public_key.nil? -    end - -    attr_accessor :public_key - -    def initialize(request=nil) -      if request.is_a? OpenSSL::X509::Request -        raise "Invalid certificate signing request" unless request.verify request.public_key -        self.public_key = request.public_key -      end -    end - -    def is_in_hardware? -      false -    end - -    def is_in_memory? -      true -    end - -    def private_key -      nil -    end - -    def public_key -      @public_key -    end -  end  end diff --git a/vendor/certificate_authority/lib/certificate_authority/serial_number.rb b/vendor/certificate_authority/lib/certificate_authority/serial_number.rb index ec0b836..143c144 100644 --- a/vendor/certificate_authority/lib/certificate_authority/serial_number.rb +++ b/vendor/certificate_authority/lib/certificate_authority/serial_number.rb @@ -6,5 +6,9 @@ module CertificateAuthority      attr_accessor :number      validates :number, :presence => true, :numericality => {:greater_than => 0} + +    def initialize +      self.number = SecureRandom.random_number(2**128-1) +    end    end  end diff --git a/vendor/certificate_authority/lib/certificate_authority/signing_request.rb b/vendor/certificate_authority/lib/certificate_authority/signing_request.rb index 590d5be..72d9e2b 100644 --- a/vendor/certificate_authority/lib/certificate_authority/signing_request.rb +++ b/vendor/certificate_authority/lib/certificate_authority/signing_request.rb @@ -5,6 +5,29 @@ module CertificateAuthority      attr_accessor :raw_body      attr_accessor :openssl_csr      attr_accessor :digest +    attr_accessor :attributes + +    def initialize() +      @attributes = [] +    end + +    # Fake attribute for convenience because adding +    # alternative names on a CSR is remarkably non-trivial. +    def subject_alternative_names=(alt_names) +      raise "alt_names must be an Array" unless alt_names.is_a?(Array) + +      factory = OpenSSL::X509::ExtensionFactory.new +      name_list = alt_names.map{|m| "DNS:#{m}"}.join(",") +      ext = factory.create_ext("subjectAltName",name_list,false) +      ext_set = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence([ext])]) +      attr = OpenSSL::X509::Attribute.new("extReq", ext_set) +      @attributes << attr +    end + +    def read_attributes_by_oid(*oids) +      attributes.detect { |a| oids.include?(a.oid) } +    end +    protected :read_attributes_by_oid      def to_cert        cert = Certificate.new @@ -12,6 +35,15 @@ module CertificateAuthority          cert.distinguished_name = @distinguished_name        end        cert.key_material = @key_material +      if attribute = read_attributes_by_oid('extReq', 'msExtReq') +        set = OpenSSL::ASN1.decode(attribute.value) +        seq = set.value.first +        seq.value.collect { |asn1ext| OpenSSL::X509::Extension.new(asn1ext).to_a }.each do |o, v, c| +         Certificate::EXTENSIONS.each do |klass| +            cert.extensions[klass::OPENSSL_IDENTIFIER] = klass.parse(v, c) if v && klass::OPENSSL_IDENTIFIER == o +          end +        end +      end        cert      end @@ -24,10 +56,12 @@ module CertificateAuthority        raise "Invalid DN in request" unless @distinguished_name.valid?        raise "CSR must have key material" if @key_material.nil?        raise "CSR must include a public key on key material" if @key_material.public_key.nil? +      raise "Need a private key on key material for CSR generation" if @key_material.private_key.nil?        opensslcsr = OpenSSL::X509::Request.new        opensslcsr.subject = @distinguished_name.to_x509_name        opensslcsr.public_key = @key_material.public_key +      opensslcsr.attributes = @attributes unless @attributes.nil?        opensslcsr.sign @key_material.private_key, OpenSSL::Digest::Digest.new(@digest || "SHA512")        opensslcsr      end @@ -38,6 +72,7 @@ module CertificateAuthority        csr.distinguished_name = DistinguishedName.from_openssl openssl_csr.subject        csr.raw_body = raw_csr        csr.openssl_csr = openssl_csr +      csr.attributes = openssl_csr.attributes        key_material = SigningRequestKeyMaterial.new        key_material.public_key = openssl_csr.public_key        csr.key_material = key_material @@ -53,4 +88,4 @@ module CertificateAuthority        csr      end    end -end
\ No newline at end of file +end | 
