summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorelijah <elijah@riseup.net>2014-10-21 17:32:28 -0700
committerelijah <elijah@riseup.net>2014-10-21 17:32:28 -0700
commita6b4ff1c21915475655a4a28c163904687d1035e (patch)
tree00502e075dc5ecafa10df9a3165033300658dd0b
parent9e5572c7177ab10904aafbdcba99de0364e57db9 (diff)
fixed `leap cert csr` to add correct "Requested Extensions" attribute on the CSR.
-rw-r--r--leap_cli.gemspec1
-rw-r--r--lib/leap_cli/commands/ca.rb43
-rw-r--r--vendor/certificate_authority/certificate_authority.gemspec5
-rw-r--r--vendor/certificate_authority/lib/certificate_authority/certificate.rb118
-rw-r--r--vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb5
-rw-r--r--vendor/certificate_authority/lib/certificate_authority/extensions.rb447
-rw-r--r--vendor/certificate_authority/lib/certificate_authority/key_material.rb34
-rw-r--r--vendor/certificate_authority/lib/certificate_authority/serial_number.rb4
-rw-r--r--vendor/certificate_authority/lib/certificate_authority/signing_request.rb37
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