Class: MiniCa::Certificate

Inherits:
Object
  • Object
show all
Defined in:
lib/mini_ca/certificate.rb

Constant Summary collapse

DIGEST =
OpenSSL::Digest::SHA256

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(cn, sans: nil, issuer: nil, ca: false, serial: nil, not_before: nil, not_after: nil, country: nil, state: nil, location: nil, organization: nil, private_key: nil) ⇒ Certificate

rubocop:disable ParameterLists



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
# File 'lib/mini_ca/certificate.rb', line 8

def initialize(
  cn,
  sans: nil,
  issuer: nil,
  ca: false,
  serial: nil,
  not_before: nil,
  not_after: nil,
  country: nil,
  state: nil,
  location: nil,
  organization: nil,
  private_key: nil
)
  @private_key = private_key || OpenSSL::PKey::RSA.new(2048)
  @x509 = OpenSSL::X509::Certificate.new
  @issuer = issuer
  @ca = ca
  @counter = 0

  x509.version = 0x2
  x509.serial = serial || 0

  x509.public_key = public_key

  x509.subject = OpenSSL::X509::Name.new

  [
    ['CN', cn],
    ['C', country],
    ['ST', state],
    ['L', location],
    ['O', organization]
  ].each do |prop, value|
    next if value.nil?
    x509.subject = x509.subject.add_entry(prop, value)
  end

  x509.issuer = issuer ? issuer.x509.subject : x509.subject

  if issuer
    not_before ||= issuer.x509.not_before
    not_after ||= issuer.x509.not_after

    if issuer.x509.not_before > not_before
      raise Error, 'Certificate cannot become valid before issuer'
    end

    if issuer.x509.not_after < not_after
      raise Error, 'Certificate cannot expire after issuer'
    end
  else
    not_before ||= Time.now - 3600 * 24
    not_after ||= Time.now + 3600 + 24
  end

  x509.not_before = not_before
  x509.not_after = not_after

  ef = OpenSSL::X509::ExtensionFactory.new
  ef.subject_certificate = x509

  sans = (sans || []) + ["DNS:#{cn}"]

  exts = if ca
           [
             ef.create_extension('basicConstraints', 'CA:TRUE', true)
           ]
         else
           [
             ef.create_extension('basicConstraints', 'CA:FALSE', true),
             ef.create_extension('subjectAltName', sans.join(','), false)
           ]
         end

  exts.each { |e| x509.add_extension(e) }

  signing_key = issuer ? issuer.private_key : send(:private_key)
  x509.sign signing_key, DIGEST.new
end

Instance Attribute Details

#caObject (readonly)

Returns the value of attribute ca.



5
6
7
# File 'lib/mini_ca/certificate.rb', line 5

def ca
  @ca
end

#issuerObject (readonly)

Returns the value of attribute issuer.



5
6
7
# File 'lib/mini_ca/certificate.rb', line 5

def issuer
  @issuer
end

#private_keyObject (readonly)

Returns the value of attribute private_key.



5
6
7
# File 'lib/mini_ca/certificate.rb', line 5

def private_key
  @private_key
end

#x509Object (readonly)

Returns the value of attribute x509.



5
6
7
# File 'lib/mini_ca/certificate.rb', line 5

def x509
  @x509
end

Instance Method Details

#bundleObject



110
111
112
# File 'lib/mini_ca/certificate.rb', line 110

def bundle
  [self] + chain
end

#bundle_pemObject



122
123
124
# File 'lib/mini_ca/certificate.rb', line 122

def bundle_pem
  bundle.map(&:x509).map(&:to_pem).join('')
end

#chainObject



101
102
103
104
105
106
107
108
# File 'lib/mini_ca/certificate.rb', line 101

def chain
  bits = []
  this_cert = self
  until (this_cert = this_cert.issuer).nil?
    bits << this_cert
  end
  bits[0...-1]
end

#chain_pemObject



118
119
120
# File 'lib/mini_ca/certificate.rb', line 118

def chain_pem
  chain.map(&:x509).map(&:to_pem).join('')
end

#issue(cn, **opts) ⇒ Object

rubocop:enable ParameterLists



90
91
92
93
94
# File 'lib/mini_ca/certificate.rb', line 90

def issue(cn, **opts)
  raise 'CA must be set to use #issue' unless ca
  @counter += 1
  Certificate.new(cn, issuer: self, serial: @counter, **opts)
end

#private_key_pemObject



126
127
128
# File 'lib/mini_ca/certificate.rb', line 126

def private_key_pem
  private_key.to_pem
end

#public_keyObject



130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/mini_ca/certificate.rb', line 130

def public_key
  case private_key
  when OpenSSL::PKey::RSA
    private_key.public_key
  when OpenSSL::PKey::EC
    # See: https://github.com/ruby/openssl/issues/29#issuecomment-230664793
    # See: https://alexpeattie.com/blog/signing-a-csr-with-ecdsa-in-ruby
    pub = OpenSSL::PKey::EC.new(private_key.group)
    pub.public_key = private_key.public_key
    pub
  else
    raise Error, "Unsupported private_key: #{private_key.class}"
  end
end

#storeObject



96
97
98
99
# File 'lib/mini_ca/certificate.rb', line 96

def store
  raise 'CA must be set to use #store' unless ca
  OpenSSL::X509::Store.new.tap { |store| store.add_cert(x509) }
end

#x509_pemObject



114
115
116
# File 'lib/mini_ca/certificate.rb', line 114

def x509_pem
  x509.to_pem
end