From 16fb1c2bf33ca418a6db06217e286964077a730f Mon Sep 17 00:00:00 2001 From: elijah Date: Sun, 31 Jan 2016 15:07:18 -0800 Subject: vendor certificate_authority, because travis does not like pulling it from github. --- .../lib/certificate_authority.rb | 21 + .../lib/certificate_authority/certificate.rb | 258 +++++++++ .../certificate_revocation_list.rb | 79 +++ .../certificate_authority/distinguished_name.rb | 102 ++++ .../lib/certificate_authority/extensions.rb | 631 +++++++++++++++++++++ .../lib/certificate_authority/key_material.rb | 114 ++++ .../lib/certificate_authority/ocsp_handler.rb | 144 +++++ .../certificate_authority/pkcs11_key_material.rb | 65 +++ .../lib/certificate_authority/revocable.rb | 14 + .../lib/certificate_authority/serial_number.rb | 14 + .../lib/certificate_authority/signing_entity.rb | 16 + .../lib/certificate_authority/signing_request.rb | 91 +++ .../lib/tasks/certificate_authority.rake | 23 + 13 files changed, 1572 insertions(+) create mode 100644 vendor/gems/certificate_authority/lib/certificate_authority.rb create mode 100644 vendor/gems/certificate_authority/lib/certificate_authority/certificate.rb create mode 100644 vendor/gems/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb create mode 100644 vendor/gems/certificate_authority/lib/certificate_authority/distinguished_name.rb create mode 100644 vendor/gems/certificate_authority/lib/certificate_authority/extensions.rb create mode 100644 vendor/gems/certificate_authority/lib/certificate_authority/key_material.rb create mode 100644 vendor/gems/certificate_authority/lib/certificate_authority/ocsp_handler.rb create mode 100644 vendor/gems/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb create mode 100644 vendor/gems/certificate_authority/lib/certificate_authority/revocable.rb create mode 100644 vendor/gems/certificate_authority/lib/certificate_authority/serial_number.rb create mode 100644 vendor/gems/certificate_authority/lib/certificate_authority/signing_entity.rb create mode 100644 vendor/gems/certificate_authority/lib/certificate_authority/signing_request.rb create mode 100644 vendor/gems/certificate_authority/lib/tasks/certificate_authority.rake (limited to 'vendor/gems/certificate_authority/lib') diff --git a/vendor/gems/certificate_authority/lib/certificate_authority.rb b/vendor/gems/certificate_authority/lib/certificate_authority.rb new file mode 100644 index 0000000..a697c1b --- /dev/null +++ b/vendor/gems/certificate_authority/lib/certificate_authority.rb @@ -0,0 +1,21 @@ +$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__))) + +#Exterior requirements +require 'openssl' +require 'active_model' + +#Internal modules +require 'certificate_authority/signing_entity' +require 'certificate_authority/revocable' +require 'certificate_authority/distinguished_name' +require 'certificate_authority/serial_number' +require 'certificate_authority/key_material' +require 'certificate_authority/pkcs11_key_material' +require 'certificate_authority/extensions' +require 'certificate_authority/certificate' +require 'certificate_authority/certificate_revocation_list' +require 'certificate_authority/ocsp_handler' +require 'certificate_authority/signing_request' + +module CertificateAuthority +end diff --git a/vendor/gems/certificate_authority/lib/certificate_authority/certificate.rb b/vendor/gems/certificate_authority/lib/certificate_authority/certificate.rb new file mode 100644 index 0000000..0bddb86 --- /dev/null +++ b/vendor/gems/certificate_authority/lib/certificate_authority/certificate.rb @@ -0,0 +1,258 @@ +require 'active_support/all' + +module CertificateAuthority + class Certificate + include ActiveModel::Validations + include Revocable + + attr_accessor :distinguished_name + attr_accessor :serial_number + attr_accessor :key_material + attr_accessor :not_before + attr_accessor :not_after + attr_accessor :extensions + attr_accessor :openssl_body + + alias :subject :distinguished_name #Same thing as the DN + + attr_accessor :parent + + validate do |certificate| + errors.add :base, "Distinguished name must be valid" unless distinguished_name.valid? + errors.add :base, "Key material must be valid" unless key_material.valid? + errors.add :base, "Serial number must be valid" unless serial_number.valid? + errors.add :base, "Extensions must be valid" unless extensions.each do |item| + unless item.respond_to?(:valid?) + true + else + item.valid? + end + end + end + + def initialize + self.distinguished_name = DistinguishedName.new + self.serial_number = SerialNumber.new + self.key_material = MemoryKeyMaterial.new + 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() + + self.signing_entity = false + + 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.not_before = self.not_before + openssl_cert.not_after = self.not_after + openssl_cert.public_key = self.key_material.public_key + + openssl_cert.serial = self.serial_number.number + + openssl_cert.subject = self.distinguished_name.to_x509_name + openssl_cert.issuer = parent.distinguished_name.to_x509_name + + require 'tempfile' + t = Tempfile.new("bullshit_conf") + ## The config requires a file even though we won't use it + openssl_config = OpenSSL::Config.new(t.path) + + factory = OpenSSL::X509::ExtensionFactory.new + factory.subject_certificate = openssl_cert + + #NB: If the parent doesn't have an SSL body we're making this a self-signed cert + if parent.openssl_body.nil? + factory.issuer_certificate = openssl_cert + else + factory.issuer_certificate = parent.openssl_body + end + + self.extensions.keys.each do |k| + config_extensions = extensions[k].config_extensions + openssl_config = merge_options(openssl_config,config_extensions) + end + + # p openssl_config.sections + + factory.config = openssl_config + + # Order matters: e.g. for self-signed, subjectKeyIdentifier must come before authorityKeyIdentifier + 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, e.critical) + openssl_cert.add_extension(ext) + end + + if signing_profile["digest"].nil? + digest = OpenSSL::Digest.new("SHA512") + else + digest = OpenSSL::Digest.new(signing_profile["digest"]) + end + + 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? + self.extensions["basicConstraints"].ca + end + + def signing_entity=(signing) + self.extensions["basicConstraints"].ca = signing + end + + def revoked? + !self.revoked_at.nil? + end + + def to_pem + raise "Certificate has no signed body" if self.openssl_body.nil? + 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 + + def is_intermediate_entity? + (self.parent != self) && is_signing_entity? + end + + private + + def merge_profile_with_extensions(signing_profile={}) + return self.extensions if signing_profile["extensions"].nil? + signing_config = signing_profile["extensions"] + signing_config.keys.each do |k| + extension = self.extensions[k] + 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!" + end + end + 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 = {} + + EXTENSIONS.each do |klass| + extension = klass.new + extension_hash[extension.openssl_identifier] = extension + end + + extension_hash + end + + def merge_options(config,hash) + hash.keys.each do |k| + config[k] = hash[k] + end + config + end + + 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 + 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 + + end +end diff --git a/vendor/gems/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb b/vendor/gems/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb new file mode 100644 index 0000000..1c68d09 --- /dev/null +++ b/vendor/gems/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb @@ -0,0 +1,79 @@ +module CertificateAuthority + class CertificateRevocationList + include ActiveModel::Validations + + attr_accessor :certificates + attr_accessor :parent + attr_accessor :crl_body + attr_accessor :next_update + attr_accessor :last_update_skew_seconds + + validate do |crl| + errors.add :next_update, "Next update must be a positive value" if crl.next_update < 0 + errors.add :parent, "A parent entity must be set" if crl.parent.nil? + end + + def initialize + self.certificates = [] + self.next_update = 60 * 60 * 4 # 4 hour default + self.last_update_skew_seconds = 0 + end + + def <<(revocable) + case revocable + when Revocable + raise "Only revoked entities can be added to a CRL" unless revocable.revoked? + self.certificates << revocable + when OpenSSL::X509::Certificate + raise "Not implemented yet" + else + raise "#{revocable.class} cannot be included in a CRL" + end + end + + def sign!(signing_profile={}) + raise "No parent entity has been set!" if self.parent.nil? + raise "Invalid CRL" unless self.valid? + + revocations = self.certificates.collect do |revocable| + revocation = OpenSSL::X509::Revoked.new + + ## We really just need a serial number, now we have to dig it out + case revocable + when Certificate + x509_cert = OpenSSL::X509::Certificate.new(revocable.to_pem) + revocation.serial = x509_cert.serial + when SerialNumber + revocation.serial = revocable.number + end + revocation.time = revocable.revoked_at + revocation + end + + crl = OpenSSL::X509::CRL.new + revocations.each do |revocation| + crl.add_revoked(revocation) + end + + crl.version = 1 + crl.last_update = Time.now - self.last_update_skew_seconds + crl.next_update = Time.now + self.next_update + + signing_cert = OpenSSL::X509::Certificate.new(self.parent.to_pem) + if signing_profile["digest"].nil? + digest = OpenSSL::Digest.new("SHA512") + else + digest = OpenSSL::Digest.new(signing_profile["digest"]) + end + crl.issuer = signing_cert.subject + self.crl_body = crl.sign(self.parent.key_material.private_key, digest) + + self.crl_body + end + + def to_pem + raise "No signed CRL body" if self.crl_body.nil? + self.crl_body.to_pem + end + end#CertificateRevocationList +end diff --git a/vendor/gems/certificate_authority/lib/certificate_authority/distinguished_name.rb b/vendor/gems/certificate_authority/lib/certificate_authority/distinguished_name.rb new file mode 100644 index 0000000..32d9c1e --- /dev/null +++ b/vendor/gems/certificate_authority/lib/certificate_authority/distinguished_name.rb @@ -0,0 +1,102 @@ +module CertificateAuthority + class DistinguishedName + include ActiveModel::Validations + + validates_presence_of :common_name + + attr_accessor :common_name + alias :cn :common_name + alias :cn= :common_name= + + attr_accessor :locality + alias :l :locality + alias :l= :locality= + + attr_accessor :state + alias :s :state + alias :st= :state= + + attr_accessor :country + alias :c :country + alias :c= :country= + + attr_accessor :organization + alias :o :organization + alias :o= :organization= + + attr_accessor :organizational_unit + alias :ou :organizational_unit + alias :ou= :organizational_unit= + + attr_accessor :email_address + 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? + name.add_entry("O", organization) unless organization.blank? + name.add_entry("OU", organizational_unit) unless organizational_unit.blank? + name.add_entry("CN", common_name) + name.add_entry("emailAddress", email_address) unless email_address.blank? + name + end + + def ==(other) + # Use the established OpenSSL comparison + self.to_x509_name() == other.to_x509_name() + end + + def self.from_openssl openssl_name + unless openssl_name.is_a? OpenSSL::X509::Name + raise "Argument must be a OpenSSL::X509::Name" + end + + WrappedDistinguishedName.new(openssl_name) + end + end + + ## This is a significantly more complicated case. It's possible that + ## generically handled certificates will include custom OIDs in the + ## subject. + class WrappedDistinguishedName < DistinguishedName + attr_accessor :x509_name + + def initialize(x509_name) + @x509_name = x509_name + + subject = @x509_name.to_a + subject.each do |element| + field = element[0].downcase + value = element[1] + #type = element[2] ## -not used + method_sym = "#{field}=".to_sym + if self.respond_to?(method_sym) + self.send("#{field}=",value) + else + ## Custom OID + @custom_oids = true + end + end + + end + + def to_x509_name + @x509_name + end + + def custom_oids? + @custom_oids + end + end +end diff --git a/vendor/gems/certificate_authority/lib/certificate_authority/extensions.rb b/vendor/gems/certificate_authority/lib/certificate_authority/extensions.rb new file mode 100644 index 0000000..89de032 --- /dev/null +++ b/vendor/gems/certificate_authority/lib/certificate_authority/extensions.rb @@ -0,0 +1,631 @@ +module CertificateAuthority + module Extensions + module ExtensionAPI + def to_s + raise "Implementation required" + end + + def self.parse(value, critical) + raise "Implementation required" + end + + def config_extensions + {} + end + + 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 + @critical = false + @ca = false + end + + def openssl_identifier + OPENSSL_IDENTIFIER + end + + def is_ca? + @ca + end + + def path_len=(value) + raise "path_len must be a non-negative integer" if value < 0 or !value.is_a?(Fixnum) + @path_len = value + end + + def to_s + res = [] + res << "CA:#{@ca}" + res << "pathlen:#{@path_len}" unless @path_len.nil? + 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.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 :critical + attr_accessor :uris + + def initialize + @critical = false + @uris = [] + end + + def openssl_identifier + OPENSSL_IDENTIFIER + end + + ## NB: At this time it seems OpenSSL's extension handlers don't support + ## any of the config options the docs claim to support... everything comes back + ## "missing value" on GENERAL NAME. Even if copied verbatim + def config_extensions + { + # "custom_crl_fields" => {"fullname" => "URI:#{fullname}"}, + # "issuer_sect" => {"CN" => "crlissuer.com", "C" => "US", "O" => "shudder"} + } + 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 + 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 + OPENSSL_IDENTIFIER + end + + def to_s + 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 + OPENSSL_IDENTIFIER + end + + def to_s + 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 + @critical = false + @ocsp = [] + @ca_issuers = [] + end + + def openssl_identifier + OPENSSL_IDENTIFIER + end + + def to_s + 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 + @critical = false + @usage = ["digitalSignature", "nonRepudiation"] + end + + def openssl_identifier + OPENSSL_IDENTIFIER + end + + def to_s + 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 + @critical = false + @usage = ["serverAuth"] + end + + def openssl_identifier + OPENSSL_IDENTIFIER + end + + def to_s + 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 :critical + attr_accessor :uris, :dns_names, :ips, :emails + + def initialize + @critical = false + @uris = [] + @dns_names = [] + @ips = [] + @emails = [] + end + + def openssl_identifier + OPENSSL_IDENTIFIER + end + + def uris=(value) + raise "URIs must be an array" unless value.is_a?(Array) + @uris = value + end + + def dns_names=(value) + raise "DNS names must be an array" unless value.is_a?(Array) + @dns_names = value + end + + def ips=(value) + raise "IPs must be an array" unless value.is_a?(Array) + @ips = value + end + + def emails=(value) + raise "Emails must be an array" unless value.is_a?(Array) + @emails = value + end + + def to_s + 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 + + 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 + attr_accessor :explicit_text + attr_accessor :organization + attr_accessor :notice_numbers + + def initialize + self.critical = false + @contains_data = false + end + + def openssl_identifier + OPENSSL_IDENTIFIER + end + + def user_notice=(value={}) + value.keys.each do |key| + self.send("#{key}=".to_sym, value[key]) + end + end + + def config_extensions + config_extension = {} + custom_policies = {} + notice = {} + unless self.policy_identifier.nil? + custom_policies["policyIdentifier"] = self.policy_identifier + end + + if !self.cps_uris.nil? and self.cps_uris.is_a?(Array) + self.cps_uris.each_with_index do |cps_uri,i| + custom_policies["CPS.#{i}"] = cps_uri + end + end + + unless self.explicit_text.nil? + notice["explicitText"] = self.explicit_text + end + + unless self.organization.nil? + notice["organization"] = self.organization + end + + unless self.notice_numbers.nil? + notice["noticeNumbers"] = self.notice_numbers + end + + if notice.keys.size > 0 + custom_policies["userNotice.1"] = "@notice" + config_extension["notice"] = notice + end + + if custom_policies.keys.size > 0 + config_extension["custom_policies"] = custom_policies + @contains_data = true + end + + config_extension + end + + def to_s + return "" unless @contains_data + 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 + + end +end diff --git a/vendor/gems/certificate_authority/lib/certificate_authority/key_material.rb b/vendor/gems/certificate_authority/lib/certificate_authority/key_material.rb new file mode 100644 index 0000000..1fd4dd9 --- /dev/null +++ b/vendor/gems/certificate_authority/lib/certificate_authority/key_material.rb @@ -0,0 +1,114 @@ +module CertificateAuthority + module KeyMaterial + def public_key + raise "Required implementation" + end + + def private_key + raise "Required implementation" + end + + def is_in_hardware? + raise "Required implementation" + end + + def is_in_memory? + raise "Required implementation" + end + + def self.from_x509_key_pair(pair,password=nil) + if password.nil? + key = OpenSSL::PKey::RSA.new(pair) + else + key = OpenSSL::PKey::RSA.new(pair,password) + end + mem_key = MemoryKeyMaterial.new + mem_key.public_key = key.public_key + mem_key.private_key = key + mem_key + end + + def self.from_x509_public_key(public_key_pem) + key = OpenSSL::PKey::RSA.new(public_key_pem) + signing_request_key = SigningRequestKeyMaterial.new + signing_request_key.public_key = key.public_key + signing_request_key + end + end + + class MemoryKeyMaterial + include KeyMaterial + include ActiveModel::Validations + + attr_accessor :keypair + attr_accessor :private_key + attr_accessor :public_key + + def initialize + end + + validates_each :private_key do |record, attr, value| + record.errors.add :private_key, "cannot be blank" if record.private_key.nil? + end + validates_each :public_key do |record, attr, value| + record.errors.add :public_key, "cannot be blank" if record.public_key.nil? + end + + def is_in_hardware? + false + end + + def is_in_memory? + true + end + + def generate_key(modulus_bits=2048) + self.keypair = OpenSSL::PKey::RSA.new(modulus_bits) + self.private_key = keypair + self.public_key = keypair.public_key + self.keypair + end + + def private_key + @private_key + end + + def public_key + @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/gems/certificate_authority/lib/certificate_authority/ocsp_handler.rb b/vendor/gems/certificate_authority/lib/certificate_authority/ocsp_handler.rb new file mode 100644 index 0000000..e101f98 --- /dev/null +++ b/vendor/gems/certificate_authority/lib/certificate_authority/ocsp_handler.rb @@ -0,0 +1,144 @@ +module CertificateAuthority + class OCSPResponseBuilder + attr_accessor :ocsp_response + attr_accessor :verification_mechanism + attr_accessor :ocsp_request_reader + attr_accessor :parent + attr_accessor :next_update + + GOOD = OpenSSL::OCSP::V_CERTSTATUS_GOOD + REVOKED = OpenSSL::OCSP::V_CERTSTATUS_REVOKED + + NO_REASON=0 + KEY_COMPROMISED=OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE + UNSPECIFIED=OpenSSL::OCSP::REVOKED_STATUS_UNSPECIFIED + + def build_response() + raise "Requires a parent for signing" if @parent.nil? + if @verification_mechanism.nil? + ## If no verification callback is provided we're marking it GOOD + @verification_mechanism = lambda {|cert_id| [GOOD,NO_REASON] } + end + + @ocsp_request_reader.ocsp_request.certid.each do |cert_id| + result,reason = verification_mechanism.call(cert_id.serial) + + ## cert_id, status, reason, rev_time, this update, next update, ext + ## - unit of time is seconds + ## - rev_time is currently set to "now" + @ocsp_response.add_status(cert_id, + result, reason, + 0, 0, @next_update, nil) + end + + @ocsp_response.sign(OpenSSL::X509::Certificate.new(@parent.to_pem), @parent.key_material.private_key, nil, nil) + OpenSSL::OCSP::Response.create(OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, @ocsp_response) + end + + def self.from_request_reader(request_reader,verification_mechanism=nil) + response_builder = OCSPResponseBuilder.new + response_builder.ocsp_request_reader = request_reader + + ocsp_response = OpenSSL::OCSP::BasicResponse.new + ocsp_response.copy_nonce(request_reader.ocsp_request) + response_builder.ocsp_response = ocsp_response + response_builder.next_update = 60*15 #Default of 15 minutes + response_builder + end + end + + class OCSPRequestReader + attr_accessor :raw_ocsp_request + attr_accessor :ocsp_request + + def serial_numbers + @ocsp_request.certid.collect do |cert_id| + cert_id.serial + end + end + + def self.from_der(request_body) + reader = OCSPRequestReader.new + reader.raw_ocsp_request = request_body + reader.ocsp_request = OpenSSL::OCSP::Request.new(request_body) + + reader + end + end + + ## DEPRECATED + class OCSPHandler + include ActiveModel::Validations + + attr_accessor :ocsp_request + attr_accessor :certificate_ids + + attr_accessor :certificates + attr_accessor :parent + + attr_accessor :ocsp_response_body + + validate do |crl| + errors.add :parent, "A parent entity must be set" if parent.nil? + end + validate :all_certificates_available + + def initialize + self.certificates = {} + end + + def <<(cert) + self.certificates[cert.serial_number.number.to_s] = cert + end + + def extract_certificate_serials + openssl_request = OpenSSL::OCSP::Request.new(@ocsp_request) + + self.certificate_ids = openssl_request.certid.collect do |cert_id| + cert_id.serial + end + + self.certificate_ids + end + + + def response + raise "Invalid response" unless valid? + + openssl_ocsp_response = OpenSSL::OCSP::BasicResponse.new + openssl_ocsp_request = OpenSSL::OCSP::Request.new(self.ocsp_request) + openssl_ocsp_response.copy_nonce(openssl_ocsp_request) + + openssl_ocsp_request.certid.each do |cert_id| + certificate = self.certificates[cert_id.serial.to_s] + + openssl_ocsp_response.add_status(cert_id, + OpenSSL::OCSP::V_CERTSTATUS_GOOD, 0, + 0, 0, 30, nil) + end + + + openssl_ocsp_response.sign(OpenSSL::X509::Certificate.new(self.parent.to_pem), self.parent.key_material.private_key, nil, nil) + final_response = OpenSSL::OCSP::Response.create(OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL, openssl_ocsp_response) + self.ocsp_response_body = final_response + self.ocsp_response_body + end + + def to_der + raise "No signed OCSP response body available" if self.ocsp_response_body.nil? + self.ocsp_response_body.to_der + end + + private + + def all_certificates_available + openssl_ocsp_request = OpenSSL::OCSP::Request.new(self.ocsp_request) + + openssl_ocsp_request.certid.each do |cert_id| + certificate = self.certificates[cert_id.serial.to_s] + errors.add(:base, "Certificate #{cert_id.serial} has not been added yet") if certificate.nil? + end + end + + end +end diff --git a/vendor/gems/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb b/vendor/gems/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb new file mode 100644 index 0000000..d4ebc47 --- /dev/null +++ b/vendor/gems/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb @@ -0,0 +1,65 @@ +module CertificateAuthority + class Pkcs11KeyMaterial + include KeyMaterial + include ActiveModel::Validations + include ActiveModel::Serialization + + attr_accessor :engine + attr_accessor :token_id + attr_accessor :pkcs11_lib + attr_accessor :openssl_pkcs11_engine_lib + attr_accessor :pin + + def initialize(attributes = {}) + @attributes = attributes + initialize_engine + end + + def is_in_hardware? + true + end + + def is_in_memory? + false + end + + def generate_key(modulus_bits=1024) + puts "Key generation is not currently supported in hardware" + nil + end + + def private_key + initialize_engine + self.engine.load_private_key(self.token_id) + end + + def public_key + initialize_engine + self.engine.load_public_key(self.token_id) + end + + private + + def initialize_engine + ## We're going to return early and try again later if params weren't passed in + ## at initialization. Any attempt at getting a public/private key will try + ## again. + return false if self.openssl_pkcs11_engine_lib.nil? or self.pkcs11_lib.nil? + return self.engine unless self.engine.nil? + OpenSSL::Engine.load + + pkcs11 = OpenSSL::Engine.by_id("dynamic") do |e| + e.ctrl_cmd("SO_PATH",self.openssl_pkcs11_engine_lib) + e.ctrl_cmd("ID","pkcs11") + e.ctrl_cmd("LIST_ADD","1") + e.ctrl_cmd("LOAD") + e.ctrl_cmd("PIN",self.pin) unless self.pin.nil? or self.pin == "" + e.ctrl_cmd("MODULE_PATH",self.pkcs11_lib) + end + + self.engine = pkcs11 + pkcs11 + end + + end +end diff --git a/vendor/gems/certificate_authority/lib/certificate_authority/revocable.rb b/vendor/gems/certificate_authority/lib/certificate_authority/revocable.rb new file mode 100644 index 0000000..eba5d98 --- /dev/null +++ b/vendor/gems/certificate_authority/lib/certificate_authority/revocable.rb @@ -0,0 +1,14 @@ +module CertificateAuthority + module Revocable + attr_accessor :revoked_at + + def revoke!(time=Time.now) + @revoked_at = time + end + + def revoked? + # If we have a time, then we're revoked + !@revoked_at.nil? + end + end +end diff --git a/vendor/gems/certificate_authority/lib/certificate_authority/serial_number.rb b/vendor/gems/certificate_authority/lib/certificate_authority/serial_number.rb new file mode 100644 index 0000000..143c144 --- /dev/null +++ b/vendor/gems/certificate_authority/lib/certificate_authority/serial_number.rb @@ -0,0 +1,14 @@ +module CertificateAuthority + class SerialNumber + include ActiveModel::Validations + include Revocable + + 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/gems/certificate_authority/lib/certificate_authority/signing_entity.rb b/vendor/gems/certificate_authority/lib/certificate_authority/signing_entity.rb new file mode 100644 index 0000000..748350b --- /dev/null +++ b/vendor/gems/certificate_authority/lib/certificate_authority/signing_entity.rb @@ -0,0 +1,16 @@ +module CertificateAuthority + module SigningEntity + + def self.included(mod) + mod.class_eval do + attr_accessor :signing_entity + end + end + + def signing_entity=(val) + raise "invalid param" unless [true,false].include?(val) + @signing_entity = val + end + + end +end diff --git a/vendor/gems/certificate_authority/lib/certificate_authority/signing_request.rb b/vendor/gems/certificate_authority/lib/certificate_authority/signing_request.rb new file mode 100644 index 0000000..3584dac --- /dev/null +++ b/vendor/gems/certificate_authority/lib/certificate_authority/signing_request.rb @@ -0,0 +1,91 @@ +module CertificateAuthority + class SigningRequest + attr_accessor :distinguished_name + attr_accessor :key_material + 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 + if !@distinguished_name.nil? + 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 + + def to_pem + to_x509_csr.to_pem + end + + def to_x509_csr + raise "Must specify a DN/subject on csr" if @distinguished_name.nil? + 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.new(@digest || "SHA512") + opensslcsr + end + + def self.from_x509_csr(raw_csr) + csr = SigningRequest.new + openssl_csr = OpenSSL::X509::Request.new(raw_csr) + 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 + csr + end + + def self.from_netscape_spkac(raw_spkac) + openssl_spkac = OpenSSL::Netscape::SPKI.new raw_spkac + csr = SigningRequest.new + csr.raw_body = raw_spkac + key_material = SigningRequestKeyMaterial.new + key_material.public_key = openssl_spkac.public_key + csr + end + end +end diff --git a/vendor/gems/certificate_authority/lib/tasks/certificate_authority.rake b/vendor/gems/certificate_authority/lib/tasks/certificate_authority.rake new file mode 100644 index 0000000..e7d5bf9 --- /dev/null +++ b/vendor/gems/certificate_authority/lib/tasks/certificate_authority.rake @@ -0,0 +1,23 @@ +require 'certificate_authority' + +namespace :certificate_authority do + desc "Generate a quick self-signed cert" + task :self_signed do + + cn = "http://localhost" + cn = ENV['DOMAIN'] unless ENV['DOMAIN'].nil? + + root = CertificateAuthority::Certificate.new + root.subject.common_name= cn + root.key_material.generate_key + root.signing_entity = true + root.valid? + root.sign! + + print "Your cert for #{cn}\n" + print root.to_pem + + print "Your private key\n" + print root.key_material.private_key.to_pem + end +end -- cgit v1.2.3