summaryrefslogtreecommitdiff
path: root/lib/trocla/formats/x509.rb
blob: bf94cec9ff53f99ea469c22cf222e48c27dd124d (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
class Trocla::Formats::X509
  require 'openssl'
  def format(plain_password,options={})

    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

    # 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
      cert.subject = subject
      cert.issuer = issuer
      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 = cert
      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)
      subreq = Trocla.new
      subreq.get_password(ca,'x509')
    end

    def getserial(ca,serial)
      subreq = Trocla.new
      subreq.get_password("#{ca}_serial",'plain') + 1 || serial
    end

    def setserial(ca,serial)
      subreq = Trocla.new
      subreq.set_password("#{ca}_serial",'plain',serial)
    end

    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}" unless ca
      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.subject, 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, subj, 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
end