summaryrefslogtreecommitdiff
path: root/lib/trocla/formats/x509.rb
blob: 219cd38a761b803092a980f3e6e079966e9d9336 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
class Trocla::Formats::X509 < Trocla::Formats::Base
  require 'openssl'
  def format(plain_password,options={})

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

    if options['subject']
      subject = options['subject']
    elsif options['CN']
      subject = ''
      ['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
    sign_with = options['ca'] || nil
    keysize = options['keysize'] || 2048
    serial = options['serial'] || 1
    days = options['days'] || 365
    altnames = options['altnames'] || nil
    altnames.collect { |v| "DNS:#{v}" }.join(', ') if altnames

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

    if sign_with # certificate signed with CA
      begin
        ca = OpenSSL::X509::Certificate.new(getca(sign_with))
        cakey = OpenSSL::PKey::RSA.new(getca(sign_with))
        caserial = getserial(sign_with, serial)
      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, OpenSSL::Digest::SHA1.new)
      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)
        csr_cert.sign(cakey, OpenSSL::Digest::SHA1.new)
        setserial(sign_with, caserial)
      rescue Exception => e
        raise "Certificate #{subject} signing failed: #{e.message}"
      end

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

      key.send("to_pem") + cert.send("to_pem")
    end
  end
  private

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

  def mkkey(len)
    OpenSSL::PKey::RSA.generate(len)
  end

  def mkreq(subject,public_key)
    request = OpenSSL::X509::Request.new
    request.version = 0
    request.subject = subject
    request.public_key = public_key

    request
  end

  def mkcert(serial,subject,issuer,public_key,days,altnames)
    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") ]
    cert.add_extension ef.create_extension("basicConstraints","CA:TRUE", true) if subject == issuer
    cert.add_extension ef.create_extension("basicConstraints","CA:FALSE", true) if subject != issuer
    cert.add_extension ef.create_extension("keyUsage", "nonRepudiation, digitalSignature, keyEncipherment", true)
    cert.add_extension ef.create_extension("subjectAltName", altnames, true) if altnames
    cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")

    cert
  end

  def getca(ca)
    trocla.get_password(ca,'x509')
  end

  def getserial(ca,serial)
    newser = trocla.get_password("#{ca}_serial",'plain')
    if newser
      newser + 1
    else
      serial
    end
  end

  def setserial(ca,serial)
    trocla.set_password("#{ca}_serial",'plain',serial)
  end
end