diff options
Diffstat (limited to 'vendor/gems/certificate_authority/lib/certificate_authority/certificate.rb')
-rw-r--r-- | vendor/gems/certificate_authority/lib/certificate_authority/certificate.rb | 258 |
1 files changed, 258 insertions, 0 deletions
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 |