From a6b4ff1c21915475655a4a28c163904687d1035e Mon Sep 17 00:00:00 2001 From: elijah Date: Tue, 21 Oct 2014 17:32:28 -0700 Subject: fixed `leap cert csr` to add correct "Requested Extensions" attribute on the CSR. --- .../lib/certificate_authority/extensions.rb | 447 +++++++++++++++++++-- 1 file changed, 406 insertions(+), 41 deletions(-) (limited to 'vendor/certificate_authority/lib/certificate_authority/extensions.rb') 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 -- cgit v1.2.3