Class: Puppetserver::Ca::LocalCertificateAuthority

Inherits:
Object
  • Object
show all
Defined in:
lib/puppetserver/ca/local_certificate_authority.rb

Constant Summary collapse

CERT_VALID_FROM =

Make the certificate valid as of yesterday, because so many people’s clocks are out of sync. This gives one more day of validity than people might expect, but is better than making every person who has a messed up clock fail, and better than having every cert we generate expire a day before the user expected it to when they asked for “one year”.

(Time.now - (60*60*24)).freeze
SSL_SERVER_CERT =
"1.3.6.1.5.5.7.3.1"
SSL_CLIENT_CERT =
"1.3.6.1.5.5.7.3.2"
CLI_AUTH_EXT_OID =
"1.3.6.1.4.1.34380.1.3.39"
SERVER_EXTENSIONS =
[
  ["basicConstraints", "CA:FALSE", true],
  ["nsComment", "Puppet Server Internal Certificate", false],
  ["authorityKeyIdentifier", "keyid:always", false],
  ["extendedKeyUsage", "#{SSL_SERVER_CERT}, #{SSL_CLIENT_CERT}", true],
  ["keyUsage", "keyEncipherment, digitalSignature", true],
  ["subjectKeyIdentifier", "hash", false]
].freeze
CA_EXTENSIONS =
[
  ["basicConstraints", "CA:TRUE", true],
  ["keyUsage", "keyCertSign, cRLSign", true],
  ["subjectKeyIdentifier", "hash", false],
  ["nsComment", "Puppet Server Internal Certificate", false],
  ["authorityKeyIdentifier", "keyid:always", false]
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(digest, settings) ⇒ LocalCertificateAuthority

Returns a new instance of LocalCertificateAuthority.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 42

def initialize(digest, settings)
  @digest = digest
  @host = Host.new(digest)
  @settings = settings
  @errors = []

  if ssl_assets_exist?
    loader = Puppetserver::Ca::X509Loader.new(@settings[:cacert], @settings[:cakey], @settings[:cacrl])
    if loader.errors.empty?
      load_ssl_components(loader)
    else
      @errors += loader.errors
      @errors << "CA not initialized. Please set up your CA before attempting to generate certs offline."
    end
  end
end

Instance Attribute Details

#certObject (readonly)

Returns the value of attribute cert.



40
41
42
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 40

def cert
  @cert
end

#cert_bundleObject (readonly)

Returns the value of attribute cert_bundle.



40
41
42
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 40

def cert_bundle
  @cert_bundle
end

#crlObject (readonly)

Returns the value of attribute crl.



40
41
42
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 40

def crl
  @crl
end

#crl_chainObject (readonly)

Returns the value of attribute crl_chain.



40
41
42
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 40

def crl_chain
  @crl_chain
end

#keyObject (readonly)

Returns the value of attribute key.



40
41
42
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 40

def key
  @key
end

Instance Method Details

#add_authorized_extensions(cert, ef) ⇒ Object



178
179
180
181
182
183
184
185
186
187
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 178

def add_authorized_extensions(cert, ef)
  SERVER_EXTENSIONS.each do |ext|
    extension = ef.create_extension(*ext)
    cert.add_extension(extension)
  end

  # Status API access for the CA CLI
  cli_auth_ext = OpenSSL::X509::Extension.new(CLI_AUTH_EXT_OID, OpenSSL::ASN1::UTF8String.new("true").to_der, false)
  cert.add_extension(cli_auth_ext)
end

#add_custom_extensions(cert) ⇒ Object

This takes all the extension requests from csr_attributes.yaml and adds those to the cert



196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 196

def add_custom_extensions(cert)
  extension_requests = @host.get_extension_requests(@settings[:csr_attributes])

  if extension_requests
    extensions = @host.validated_extensions(extension_requests)
    extensions.each do |ext|
      cert.add_extension(ext)
    end
  end

  @host.errors.empty?
end

#add_subject_alt_names_extension(alt_names, cert, ef) ⇒ Object



189
190
191
192
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 189

def add_subject_alt_names_extension(alt_names, cert, ef)
  alt_names_ext = ef.create_extension("subjectAltName", alt_names, false)
  cert.add_extension(alt_names_ext)
end

#create_crl_for(cert, key) ⇒ Object



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 240

def create_crl_for(cert, key)
  crl = OpenSSL::X509::CRL.new
  crl.version = 1
  crl.issuer = cert.subject

  ef = extension_factory_for(cert)
  crl.add_extension(
    ef.create_extension(["authorityKeyIdentifier", "keyid:always", false]))
  crl.add_extension(
    OpenSSL::X509::Extension.new("crlNumber", OpenSSL::ASN1::Integer(0)))

  crl.last_update = CERT_VALID_FROM
  crl.next_update = valid_until
  crl.sign(key, @digest)

  crl
end

#create_intermediate_cert(root_key, root_cert) ⇒ Object



258
259
260
261
262
263
264
265
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 258

def create_intermediate_cert(root_key, root_cert)
  @key = @host.create_private_key(@settings[:keylength])
  int_csr = @host.create_csr(name: @settings[:ca_name], key: @key)
  @cert = sign_intermediate(root_key, root_cert, int_csr)
  @crl = create_crl_for(@cert, @key)

  return nil
end

#create_root_certObject



209
210
211
212
213
214
215
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 209

def create_root_cert
  root_key = @host.create_private_key(@settings[:keylength])
  root_cert = self_signed_ca(root_key)
  root_crl = create_crl_for(root_cert, root_key)

  return root_key, root_cert, root_crl
end

#create_server_certObject



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 135

def create_server_cert
  server_cert = nil
  server_key = @host.create_private_key(@settings[:keylength],
                                        @settings[:hostprivkey],
                                        @settings[:hostpubkey])
  if server_key
    server_csr = @host.create_csr(name: @settings[:certname], key: server_key)
    if @settings[:subject_alt_names].empty?
      alt_names = "DNS:puppet, DNS:#{@settings[:certname]}"
    else
      alt_names = @settings[:subject_alt_names]
    end

    server_cert = sign_authorized_cert(server_csr, alt_names)
  end

  return server_key, server_cert
end

#errorsObject



102
103
104
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 102

def errors
  @errors += @host.errors
end

#extension_factory_for(ca, cert = nil) ⇒ Object



110
111
112
113
114
115
116
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 110

def extension_factory_for(ca, cert = nil)
  ef = OpenSSL::X509::ExtensionFactory.new
  ef.issuer_certificate  = ca
  ef.subject_certificate = cert if cert

  ef
end

#format_time(time) ⇒ Object



131
132
133
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 131

def format_time(time)
  time.strftime('%Y-%m-%dT%H:%M:%S%Z')
end

#initialize_ssl_components(loader) ⇒ void

Note:

Check #errors after calling this method for issues that may have occurred during initialization.

This method returns an undefined value.

Initialize SSL state

This method is similar to #load_ssl_components, but has extra logic for initializing components that may not be present when the CA is set up for the first time. For example, SSL components provided by an external CA will often not include a pre-generated leaf CRL.

Parameters:



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 86

def initialize_ssl_components(loader)
  @cert_bundle = loader.certs
  @key = loader.key
  @cert = loader.cert

  if loader.crl.nil?
    loader.crl = create_crl_for(@cert, @key)

    loader.validate_full_chain(@cert_bundle, loader.crls)
    @errors += loader.errors
  end

  @crl_chain = loader.crls
  @crl = loader.crl
end

#inventory_entry(cert) ⇒ Object



118
119
120
121
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 118

def inventory_entry(cert)
  "0x%04x %s %s %s" % [cert.serial, format_time(cert.not_before),
                       format_time(cert.not_after), cert.subject]
end

#load_ssl_components(loader) ⇒ Object



65
66
67
68
69
70
71
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 65

def load_ssl_components(loader)
  @cert_bundle = loader.certs
  @key = loader.key
  @cert = loader.cert
  @crl_chain = loader.crls
  @crl = loader.crl
end

#next_serial(serial_file) ⇒ Object



123
124
125
126
127
128
129
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 123

def next_serial(serial_file)
  if File.exist?(serial_file)
    File.read(serial_file).to_i(16)
  else
    1
  end
end

#self_signed_ca(key) ⇒ Object



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 217

def self_signed_ca(key)
  cert = OpenSSL::X509::Certificate.new

  cert.public_key = key.public_key
  cert.subject = OpenSSL::X509::Name.new([["CN", @settings[:root_ca_name]]])
  cert.issuer = cert.subject
  cert.version = 2
  cert.serial = 1

  cert.not_before = CERT_VALID_FROM
  cert.not_after  = valid_until

  ef = extension_factory_for(cert, cert)
  CA_EXTENSIONS.each do |ext|
    extension = ef.create_extension(*ext)
    cert.add_extension(extension)
  end

  cert.sign(key, @digest)

  cert
end

#sign_authorized_cert(csr, alt_names = '') ⇒ Object



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 154

def sign_authorized_cert(csr, alt_names = '')
  cert = OpenSSL::X509::Certificate.new
  cert.public_key = csr.public_key
  cert.subject = csr.subject
  cert.issuer = @cert.subject
  cert.version = 2
  cert.serial = next_serial(@settings[:serial])
  cert.not_before = CERT_VALID_FROM
  cert.not_after = valid_until

  return unless add_custom_extensions(cert)

  ef = extension_factory_for(@cert, cert)
  add_authorized_extensions(cert, ef)

  if !alt_names.empty?
    add_subject_alt_names_extension(alt_names, cert, ef)
  end

  cert.sign(@key, @digest)

  cert
end

#sign_intermediate(ca_key, ca_cert, csr) ⇒ Object



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 267

def sign_intermediate(ca_key, ca_cert, csr)
  cert = OpenSSL::X509::Certificate.new

  cert.public_key = csr.public_key
  cert.subject = csr.subject
  cert.issuer = ca_cert.subject
  cert.version = 2
  cert.serial = 2

  cert.not_before = CERT_VALID_FROM
  cert.not_after = valid_until

  ef = extension_factory_for(ca_cert, cert)
  CA_EXTENSIONS.each do |ext|
    extension = ef.create_extension(*ext)
    cert.add_extension(extension)
  end

  cert.sign(ca_key, @digest)

  cert
end

#ssl_assets_exist?Boolean

Returns:

  • (Boolean)


59
60
61
62
63
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 59

def ssl_assets_exist?
  File.exist?(@settings[:cacert]) &&
    File.exist?(@settings[:cakey]) &&
    File.exist?(@settings[:cacrl])
end

#update_serial_file(serial) ⇒ Object



290
291
292
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 290

def update_serial_file(serial)
  Puppetserver::Ca::Utils::FileSystem.write_file(@settings[:serial], serial.to_s(16), 0644)
end

#valid_untilObject



106
107
108
# File 'lib/puppetserver/ca/local_certificate_authority.rb', line 106

def valid_until
  Time.now + @settings[:ca_ttl]
end