summaryrefslogtreecommitdiff
path: root/lib/trocla/formats/x509.rb
blob: 5cb1fb2ada0c86d28138ac02baa18a75207dad36 (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
129
130
class Trocla::Formats::X509
  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

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

    def getserial(ca,serial)
      subreq = Trocla.new
      newser = subreq.get_password("#{ca}_serial",'plain')
      if newser
        newser + 1
      else
        serial
      end
    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}"
      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
end