class Trocla::Formats::X509

Public Instance Methods

format(plain_password,options={}) click to toggle source
# File lib/trocla/formats/x509.rb, line 3
def format(plain_password,options={})

  if plain_password.match(/-----BEGIN RSA PRIVATE KEY-----.*-----END RSA PRIVATE KEY-----.*-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----/)
    # just an import, don't generate any new keys
    return plain_password
  end

  cn = nil
  if options['subject']
    subject = options['subject']
    if cna = OpenSSL::X509::Name.parse(subject).to_a.find{|e| e[0] == 'CN' }
      cn = cna[1]
    end
  elsif options['CN']
    subject = ''
    cn = options['CN']
    ['C','ST','L','O','OU','CN','emailAddress'].each do |field|
      subject << "/#{field}=#{options[field]}" if options[field]
    end
  else
    raise "You need to pass \"subject\" or \"CN\" as an option to use this format"
  end
  hash = options['hash'] || 'sha2'
  sign_with = options['ca']
  become_ca = options['become_ca'] || false
  keysize = options['keysize'] || 4096
  days = options['days'].nil? ? 365 : options['days'].to_i
  name_constraints = Array(options['name_constraints'])

  altnames = if become_ca || (an = options['altnames']) && Array(an).empty?
    []
  else
    # ensure that we have the CN with us, but only if it
    # it's like a hostname.
    # This might have to be improved.
    if cn.include?(' ')
      Array(an).collect { |v| "DNS:#{v}" }.join(', ')
    else
      (["DNS:#{cn}"] + Array(an).collect { |v| "DNS:#{v}" }).uniq.join(', ')
    end
  end

  begin
    key = mkkey(keysize)
  rescue Exception => e
    puts e.backtrace
    raise "Private key for #{subject} creation failed: #{e.message}"
  end

  if sign_with # certificate signed with CA
    begin
      ca_str = trocla.get_password(sign_with,'x509')
      ca = OpenSSL::X509::Certificate.new(ca_str)
      cakey = OpenSSL::PKey::RSA.new(ca_str)
      caserial = getserial(sign_with)
    rescue Exception => e
      raise "Value of #{sign_with} can't be loaded as CA: #{e.message}"
    end

    begin
      subj = OpenSSL::X509::Name.parse(subject)
      request = mkreq(subj, key.public_key)
      request.sign(key, signature(hash))
    rescue Exception => e
      raise "Certificate request #{subject} creation failed: #{e.message}"
    end

    begin
      csr_cert = mkcert(caserial, request.subject, ca, request.public_key, days, altnames, name_constraints, become_ca)
      csr_cert.sign(cakey, signature(hash))
      addserial(sign_with, caserial)
    rescue Exception => e
      raise "Certificate #{subject} signing failed: #{e.message}"
    end

    key.to_pem + csr_cert.to_pem
  else # self-signed certificate
    begin
      subj = OpenSSL::X509::Name.parse(subject)
      cert = mkcert(getserial(subj), subj, nil, key.public_key, days, altnames, name_constraints, become_ca)
      cert.sign(key, signature(hash))
    rescue Exception => e
      raise "Self-signed certificate #{subject} creation failed: #{e.message}"
    end

    key.to_pem + cert.to_pem
  end
end

Private Instance Methods

addserial(ca,serial) click to toggle source
# File lib/trocla/formats/x509.rb, line 168
def addserial(ca,serial)
  serials = all_serials(ca) << serial
  trocla.set_password("#{ca}_all_serials",'plain',YAML.dump(serials))
end
all_serials(ca) click to toggle source
# File lib/trocla/formats/x509.rb, line 160
def all_serials(ca)
  if allser = trocla.get_password("#{ca}_all_serials",'plain')
    YAML.load(allser)
  else
    []
  end
end
getserial(ca) click to toggle source
# File lib/trocla/formats/x509.rb, line 155
def getserial(ca)
  newser = Trocla::Util.random_str(20,'hexadecimal').to_i(16)
  all_serials(ca).include?(newser) ? getserial(ca) : newser
end
mkcert(serial,subject,issuer,public_key,days,altnames, name_constraints = [], become_ca = false) click to toggle source
# File lib/trocla/formats/x509.rb, line 123
def mkcert(serial,subject,issuer,public_key,days,altnames, name_constraints = [], become_ca = false)
  cert = OpenSSL::X509::Certificate.new
  issuer = cert if issuer == nil
  cert.subject = subject
  cert.issuer = issuer.subject
  cert.not_before = Time.now
  cert.not_after = Time.now + days * 24 * 60 * 60
  cert.public_key = public_key
  cert.serial = serial
  cert.version = 2

  ef = OpenSSL::X509::ExtensionFactory.new
  ef.subject_certificate = cert
  ef.issuer_certificate = issuer
  cert.extensions = [ ef.create_extension("subjectKeyIdentifier", "hash") ]

  if become_ca
    cert.add_extension ef.create_extension("basicConstraints","CA:TRUE", true)
    cert.add_extension ef.create_extension("keyUsage", "keyCertSign, cRLSign, nonRepudiation, digitalSignature, keyEncipherment", true)
    if name_constraints && !name_constraints.empty?
      cert.add_extension ef.create_extension("nameConstraints","permitted;DNS:#{name_constraints.join(',permitted;DNS:')}",true)
    end
  else
    cert.add_extension ef.create_extension("subjectAltName", altnames, true) unless altnames.empty?
    cert.add_extension ef.create_extension("basicConstraints","CA:FALSE", true)
    cert.add_extension ef.create_extension("keyUsage", "nonRepudiation, digitalSignature, keyEncipherment", true)
  end
  cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")

  cert
end
mkkey(len) click to toggle source
# File lib/trocla/formats/x509.rb, line 111
def mkkey(len)
  OpenSSL::PKey::RSA.generate(len)
end
mkreq(subject,public_key) click to toggle source
# File lib/trocla/formats/x509.rb, line 115
def mkreq(subject,public_key)
  request = OpenSSL::X509::Request.new
  request.subject = subject
  request.public_key = public_key

  request
end
signature(hash = 'sha2') click to toggle source

nice help: gist.github.com/mitfik/1922961

# File lib/trocla/formats/x509.rb, line 95
def signature(hash = 'sha2')
  if hash == 'sha1'
      OpenSSL::Digest::SHA1.new
  elsif hash == 'sha224'
      OpenSSL::Digest::SHA224.new
  elsif hash == 'sha2' || hash == 'sha256'
      OpenSSL::Digest::SHA256.new
  elsif hash == 'sha384'
      OpenSSL::Digest::SHA384.new
  elsif hash == 'sha512'
      OpenSSL::Digest::SHA512.new
  else
      raise "Unrecognized hash: #{hash}"
  end
end