From 76a3a736cfb50cb1c6d926d1e3afb0f504818157 Mon Sep 17 00:00:00 2001 From: elijah Date: Fri, 16 Nov 2012 14:30:20 -0800 Subject: added CSR ability (and vendored certificate_authority gem, so we can get the unreleased fixes we need). --- .../certificate_authority.gemspec | 88 +++++++ .../lib/certificate_authority.rb | 21 ++ .../lib/certificate_authority/certificate.rb | 200 ++++++++++++++++ .../certificate_revocation_list.rb | 77 ++++++ .../certificate_authority/distinguished_name.rb | 97 ++++++++ .../lib/certificate_authority/extensions.rb | 266 +++++++++++++++++++++ .../lib/certificate_authority/key_material.rb | 148 ++++++++++++ .../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 | 10 + .../lib/certificate_authority/signing_entity.rb | 16 ++ .../lib/certificate_authority/signing_request.rb | 56 +++++ .../lib/tasks/certificate_authority.rake | 23 ++ 14 files changed, 1225 insertions(+) create mode 100644 vendor/certificate_authority/certificate_authority.gemspec create mode 100644 vendor/certificate_authority/lib/certificate_authority.rb create mode 100644 vendor/certificate_authority/lib/certificate_authority/certificate.rb create mode 100644 vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb create mode 100644 vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb create mode 100644 vendor/certificate_authority/lib/certificate_authority/extensions.rb create mode 100644 vendor/certificate_authority/lib/certificate_authority/key_material.rb create mode 100644 vendor/certificate_authority/lib/certificate_authority/ocsp_handler.rb create mode 100644 vendor/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb create mode 100644 vendor/certificate_authority/lib/certificate_authority/revocable.rb create mode 100644 vendor/certificate_authority/lib/certificate_authority/serial_number.rb create mode 100644 vendor/certificate_authority/lib/certificate_authority/signing_entity.rb create mode 100644 vendor/certificate_authority/lib/certificate_authority/signing_request.rb create mode 100644 vendor/certificate_authority/lib/tasks/certificate_authority.rake (limited to 'vendor/certificate_authority') diff --git a/vendor/certificate_authority/certificate_authority.gemspec b/vendor/certificate_authority/certificate_authority.gemspec new file mode 100644 index 0000000..be8cd91 --- /dev/null +++ b/vendor/certificate_authority/certificate_authority.gemspec @@ -0,0 +1,88 @@ +# Generated by jeweler +# DO NOT EDIT THIS FILE DIRECTLY +# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = "certificate_authority" + s.version = "0.2.0" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Chris Chandler"] + s.date = "2012-09-16" + s.email = "chris@flatterline.com" + s.extra_rdoc_files = [ + "README.rdoc" + ] + s.files = [ + "Gemfile", + "Gemfile.lock", + "README.rdoc", + "Rakefile", + "VERSION.yml", + "certificate_authority.gemspec", + "lib/certificate_authority.rb", + "lib/certificate_authority/certificate.rb", + "lib/certificate_authority/certificate_revocation_list.rb", + "lib/certificate_authority/distinguished_name.rb", + "lib/certificate_authority/extensions.rb", + "lib/certificate_authority/key_material.rb", + "lib/certificate_authority/ocsp_handler.rb", + "lib/certificate_authority/pkcs11_key_material.rb", + "lib/certificate_authority/revocable.rb", + "lib/certificate_authority/serial_number.rb", + "lib/certificate_authority/signing_entity.rb", + "lib/certificate_authority/signing_request.rb", + "lib/tasks/certificate_authority.rake", + "spec/samples/certs/DigiCertHighAssuranceEVCA-1.pem", + "spec/samples/certs/apple_wwdr_issued_cert.pem", + "spec/samples/certs/apple_wwdr_issuer.pem", + "spec/samples/certs/ca.crt", + "spec/samples/certs/ca.key", + "spec/samples/certs/client.crt", + "spec/samples/certs/client.csr", + "spec/samples/certs/client.key", + "spec/samples/certs/github.com.pem", + "spec/samples/certs/server.crt", + "spec/samples/certs/server.csr", + "spec/samples/certs/server.key", + "spec/spec_helper.rb", + "spec/units/certificate_authority_spec.rb", + "spec/units/certificate_revocation_list_spec.rb", + "spec/units/certificate_spec.rb", + "spec/units/distinguished_name_spec.rb", + "spec/units/extensions_spec.rb", + "spec/units/key_material_spec.rb", + "spec/units/ocsp_handler_spec.rb", + "spec/units/pkcs11_key_material_spec.rb", + "spec/units/serial_number_spec.rb", + "spec/units/signing_entity_spec.rb", + "spec/units/signing_request_spec.rb", + "spec/units/units_helper.rb", + "spec/units/working_with_openssl_spec.rb" + ] + s.homepage = "http://github.com/cchandler/certificate_authority" + s.licenses = ["MIT"] + s.require_paths = ["lib"] + s.rubygems_version = "1.8.15" + s.summary = "Ruby gem for managing the core functions outlined in RFC-3280 for PKI" + + if s.respond_to? :specification_version then + s.specification_version = 3 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_runtime_dependency(%q, [">= 3.0.6"]) + s.add_development_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 1.5.2"]) + else + s.add_dependency(%q, [">= 3.0.6"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 1.5.2"]) + end + else + s.add_dependency(%q, [">= 3.0.6"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 1.5.2"]) + end +end + diff --git a/vendor/certificate_authority/lib/certificate_authority.rb b/vendor/certificate_authority/lib/certificate_authority.rb new file mode 100644 index 0000000..a697c1b --- /dev/null +++ b/vendor/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/certificate_authority/lib/certificate_authority/certificate.rb b/vendor/certificate_authority/lib/certificate_authority/certificate.rb new file mode 100644 index 0000000..ca8bc7c --- /dev/null +++ b/vendor/certificate_authority/lib/certificate_authority/certificate.rb @@ -0,0 +1,200 @@ +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 + self.not_after = Time.now + 60 * 60 * 24 * 365 #One year + self.parent = self + self.extensions = load_extensions() + + self.signing_entity = false + + 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") + # 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) + + 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) + openssl_cert.add_extension(ext) + end + + if signing_profile["digest"].nil? + digest = OpenSSL::Digest::Digest.new("SHA512") + 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 + 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 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) + 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 + + 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| + 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 + # TODO extensions + certificate + end + + end +end diff --git a/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb b/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb new file mode 100644 index 0000000..e222e26 --- /dev/null +++ b/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb @@ -0,0 +1,77 @@ +module CertificateAuthority + class CertificateRevocationList + include ActiveModel::Validations + + attr_accessor :certificates + attr_accessor :parent + attr_accessor :crl_body + attr_accessor :next_update + + 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 + 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 + 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::Digest.new("SHA512") + else + digest = OpenSSL::Digest::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/certificate_authority/lib/certificate_authority/distinguished_name.rb b/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb new file mode 100644 index 0000000..165fe29 --- /dev/null +++ b/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb @@ -0,0 +1,97 @@ +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= + + 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("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/certificate_authority/lib/certificate_authority/extensions.rb b/vendor/certificate_authority/lib/certificate_authority/extensions.rb new file mode 100644 index 0000000..e5a8e85 --- /dev/null +++ b/vendor/certificate_authority/lib/certificate_authority/extensions.rb @@ -0,0 +1,266 @@ +module CertificateAuthority + module Extensions + module ExtensionAPI + def to_s + raise "Implementation required" + end + + def config_extensions + {} + end + + def openssl_identifier + raise "Implementation required" + end + end + + class BasicConstraints + include ExtensionAPI + include ActiveModel::Validations + attr_accessor :ca + attr_accessor :path_len + validates :ca, :inclusion => [true,false] + + def initialize + self.ca = false + end + + def is_ca? + self.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 openssl_identifier + "basicConstraints" + end + + def to_s + result = "" + result += "CA:#{self.ca}" + result += ",pathlen:#{self.path_len}" unless self.path_len.nil? + result + end + end + + class CrlDistributionPoints + include ExtensionAPI + + attr_accessor :uri + + def initialize + # self.uri = "http://moo.crlendPoint.example.com/something.crl" + end + + def openssl_identifier + "crlDistributionPoints" + 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 + + def to_s + return "" if self.uri.nil? + "URI:#{self.uri}" + end + end + + class SubjectKeyIdentifier + include ExtensionAPI + def openssl_identifier + "subjectKeyIdentifier" + end + + def to_s + "hash" + end + end + + class AuthorityKeyIdentifier + include ExtensionAPI + + def openssl_identifier + "authorityKeyIdentifier" + end + + def to_s + "keyid,issuer" + end + end + + class AuthorityInfoAccess + include ExtensionAPI + + attr_accessor :ocsp + + def initialize + self.ocsp = [] + end + + def openssl_identifier + "authorityInfoAccess" + end + + def to_s + return "" if self.ocsp.empty? + "OCSP;URI:#{self.ocsp}" + end + end + + class KeyUsage + include ExtensionAPI + + attr_accessor :usage + + def initialize + self.usage = ["digitalSignature", "nonRepudiation"] + end + + def openssl_identifier + "keyUsage" + end + + def to_s + "#{self.usage.join(',')}" + end + end + + class ExtendedKeyUsage + include ExtensionAPI + + attr_accessor :usage + + def initialize + self.usage = ["serverAuth","clientAuth"] + end + + def openssl_identifier + "extendedKeyUsage" + end + + def to_s + "#{self.usage.join(',')}" + end + end + + class SubjectAlternativeName + include ExtensionAPI + + attr_accessor :uris, :dns_names, :ips + + def initialize + self.uris = [] + self.dns_names = [] + self.ips = [] + 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 openssl_identifier + "subjectAltName" + 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}" } + + return res.join(',') + end + end + + class CertificatePolicies + include ExtensionAPI + + attr_accessor :policy_identifier + attr_accessor :cps_uris + ##User notice + attr_accessor :explicit_text + attr_accessor :organization + attr_accessor :notice_numbers + + def initialize + @contains_data = false + end + + + def openssl_identifier + "certificatePolicies" + 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 + "ia5org,@custom_policies" + end + end + + end +end diff --git a/vendor/certificate_authority/lib/certificate_authority/key_material.rb b/vendor/certificate_authority/lib/certificate_authority/key_material.rb new file mode 100644 index 0000000..75ec62e --- /dev/null +++ b/vendor/certificate_authority/lib/certificate_authority/key_material.rb @@ -0,0 +1,148 @@ +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 + + 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/ocsp_handler.rb b/vendor/certificate_authority/lib/certificate_authority/ocsp_handler.rb new file mode 100644 index 0000000..e101f98 --- /dev/null +++ b/vendor/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/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb b/vendor/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb new file mode 100644 index 0000000..d4ebc47 --- /dev/null +++ b/vendor/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/certificate_authority/lib/certificate_authority/revocable.rb b/vendor/certificate_authority/lib/certificate_authority/revocable.rb new file mode 100644 index 0000000..eba5d98 --- /dev/null +++ b/vendor/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/certificate_authority/lib/certificate_authority/serial_number.rb b/vendor/certificate_authority/lib/certificate_authority/serial_number.rb new file mode 100644 index 0000000..ec0b836 --- /dev/null +++ b/vendor/certificate_authority/lib/certificate_authority/serial_number.rb @@ -0,0 +1,10 @@ +module CertificateAuthority + class SerialNumber + include ActiveModel::Validations + include Revocable + + attr_accessor :number + + validates :number, :presence => true, :numericality => {:greater_than => 0} + end +end diff --git a/vendor/certificate_authority/lib/certificate_authority/signing_entity.rb b/vendor/certificate_authority/lib/certificate_authority/signing_entity.rb new file mode 100644 index 0000000..748350b --- /dev/null +++ b/vendor/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/certificate_authority/lib/certificate_authority/signing_request.rb b/vendor/certificate_authority/lib/certificate_authority/signing_request.rb new file mode 100644 index 0000000..590d5be --- /dev/null +++ b/vendor/certificate_authority/lib/certificate_authority/signing_request.rb @@ -0,0 +1,56 @@ +module CertificateAuthority + class SigningRequest + attr_accessor :distinguished_name + attr_accessor :key_material + attr_accessor :raw_body + attr_accessor :openssl_csr + attr_accessor :digest + + def to_cert + cert = Certificate.new + if !@distinguished_name.nil? + cert.distinguished_name = @distinguished_name + end + cert.key_material = @key_material + 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? + + opensslcsr = OpenSSL::X509::Request.new + opensslcsr.subject = @distinguished_name.to_x509_name + opensslcsr.public_key = @key_material.public_key + opensslcsr.sign @key_material.private_key, OpenSSL::Digest::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 + 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 \ No newline at end of file diff --git a/vendor/certificate_authority/lib/tasks/certificate_authority.rake b/vendor/certificate_authority/lib/tasks/certificate_authority.rake new file mode 100644 index 0000000..e7d5bf9 --- /dev/null +++ b/vendor/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