class CertificateAuthority::Certificate

Constants

EXTENSIONS

Enumeration of the extensions. Not the worst option since the likelihood of these needing to be updated is low at best.

Attributes

distinguished_name[RW]
extensions[RW]
key_material[RW]
not_after[RW]
not_before[RW]
openssl_body[RW]
parent[RW]
serial_number[RW]
subject[RW]

Public Class Methods

from_x509_cert(raw_cert) click to toggle source
# File vendor/certificate_authority/lib/certificate_authority/certificate.rb, line 161
def self.from_x509_cert(raw_cert)
  openssl_cert = OpenSSL::X509::Certificate.new(raw_cert)
  Certificate.from_openssl(openssl_cert)
end
new() click to toggle source
# File vendor/certificate_authority/lib/certificate_authority/certificate.rb, line 31
def initialize
  self.distinguished_name = DistinguishedName.new
  self.serial_number = SerialNumber.new
  self.key_material = MemoryKeyMaterial.new
  self.not_before = Date.today.utc
  self.not_after = Date.today.advance(:years => 1).utc
  self.parent = self
  self.extensions = load_extensions()

  self.signing_entity = false

end

Private Class Methods

from_openssl(openssl_cert) click to toggle source
# File vendor/certificate_authority/lib/certificate_authority/certificate.rb, line 234
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

Public Instance Methods

is_intermediate_entity?() click to toggle source
# File vendor/certificate_authority/lib/certificate_authority/certificate.rb, line 170
def is_intermediate_entity?
  (self.parent != self) && is_signing_entity?
end
is_root_entity?() click to toggle source
# File vendor/certificate_authority/lib/certificate_authority/certificate.rb, line 166
def is_root_entity?
  self.parent == self && is_signing_entity?
end
is_signing_entity?() click to toggle source
# File vendor/certificate_authority/lib/certificate_authority/certificate.rb, line 121
def is_signing_entity?
  self.extensions["basicConstraints"].ca
end
revoked?() click to toggle source
# File vendor/certificate_authority/lib/certificate_authority/certificate.rb, line 129
def revoked?
  !self.revoked_at.nil?
end
sign!(signing_profile={}) click to toggle source

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

# File vendor/certificate_authority/lib/certificate_authority/certificate.rb, line 63
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
signing_entity=(signing) click to toggle source
# File vendor/certificate_authority/lib/certificate_authority/certificate.rb, line 125
def signing_entity=(signing)
  self.extensions["basicConstraints"].ca = signing
end
to_csr() click to toggle source
# File vendor/certificate_authority/lib/certificate_authority/certificate.rb, line 138
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
to_pem() click to toggle source
# File vendor/certificate_authority/lib/certificate_authority/certificate.rb, line 133
def to_pem
  raise "Certificate has no signed body" if self.openssl_body.nil?
  self.openssl_body.to_pem
end
validate() click to toggle source
# File vendor/certificate_authority/lib/certificate_authority/certificate.rb, line 18
def validate
  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

Private Instance Methods

load_extensions() click to toggle source
# File vendor/certificate_authority/lib/certificate_authority/certificate.rb, line 216
def load_extensions
  extension_hash = {}

  EXTENSIONS.each do |klass|
    extension = klass.new
    extension_hash[extension.openssl_identifier] = extension
  end

  extension_hash
end
merge_options(config,hash) click to toggle source
# File vendor/certificate_authority/lib/certificate_authority/certificate.rb, line 227
def merge_options(config,hash)
  hash.keys.each do |k|
    config[k] = hash[k]
  end
  config
end
merge_profile_with_extensions(signing_profile={}) click to toggle source
# File vendor/certificate_authority/lib/certificate_authority/certificate.rb, line 176
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