diff --git a/dev-resources/regen_certs.rb b/dev-resources/regen_certs.rb index daa3609795..3116f480ac 100644 --- a/dev-resources/regen_certs.rb +++ b/dev-resources/regen_certs.rb @@ -1,207 +1,324 @@ require 'openssl' +require 'ostruct' +require 'tmpdir' +require 'securerandom' + +require 'puppetserver/ca/action/generate' +require 'puppetserver/ca/action/setup' +require 'puppetserver/ca/host' +require 'puppetserver/ca/logger' +require 'puppetserver/ca/utils/file_system' + +module PuppetserverSpec + module Ca + class Pki + include Puppetserver::Ca::Utils + attr :ca_cert, :ca_crl, :ca_key, :root_cert, :root_crl, :root_key, :settings + + SETTINGS_TEMPLATE = { + confdir: '%s', + ssldir: '%s/ssl', + cadir: '%s/ca', + certdir: '%s/ssl/certs', + privatekeydir: '%s/ssl/private_keys', + publickeydir: '%s/ssl/public_keys', + hostpubkey: '%s/ssl/public_keys/localhost.pem', + hostprivkey: '%s/ssl/private_keys/localhost.pem', + hostcert: '%s/ssl/certs/localhost.pem', + hostcrl: '%s/ssl/crl.pem', + localcacert: '%s/ssl/certs/ca.pem', + csrdir: '%s/ca/requests', + signeddir: '%s/ca/signed', + cakey: '%s/ca/ca_key.pem', + capub: '%s/ca/ca_pub.pem', + cacert: '%s/ca/ca_crt.pem', + cacrl: '%s/ca/ca_crl.pem', + serial: '%s/ca/serial', + rootkey: '%s/ca/root_key.pem', + cert_inventory: '%s/ca/inventory.txt', + keylength: 2048, + certname: 'localhost', + ca_name: 'Puppet CA: localhost', + root_ca_name: 'Puppet Root CA: %s', + ca_ttl: 157_680_000, + subject_alt_names: '', + csr_attributes: '' + }.freeze + + def initialize(settings = {}, intermediate_cert: true) + settings_vars = { + confdir: (Dir.mktmpdir 'puppetca-', ENV['TMPDIR'] || '/tmp'), + root_ca_name_rand: SecureRandom.hex(7) + } + @settings = SETTINGS_TEMPLATE.merge(settings).transform_values { |v| (v.is_a? String) ? v % settings_vars : v } + + signer = SigningDigest.new + @digest = signer.digest + @logger = Puppetserver::Ca::Logger.new(:warning, $stdout, $stderr) + @ca = Puppetserver::Ca::LocalCertificateAuthority.new(@digest, @settings) + + @root_key, @root_cert, @root_crl = @ca.create_root_cert + + if intermediate_cert + @ca.create_intermediate_cert(@root_key, @root_cert) + ca_cert_setting = [@ca.cert, @root_cert] + ca_crl_setting = [@ca.crl, @root_crl] + else + ca_ssl = OpenStruct.new( + { + cert: root_cert, + certs: root_cert, + crl: root_crl, + crls: root_crl, + key: root_key + } + ) + ca_cert_setting = @root_cert + ca_crl_setting = @root_crl + @ca.load_ssl_components(ca_ssl) + end + + FileSystem.ensure_dirs([@settings[:ssldir], + @settings[:cadir], + @settings[:certdir], + @settings[:privatekeydir], + @settings[:publickeydir], + @settings[:signeddir]]) + + @ca.update_serial_file(2) + server_key, server_cert = @ca.create_server_cert + inventory = @ca.inventory_entry(@ca.cert) + "\n" + @ca.inventory_entry(server_cert) + + [ + [@settings[:cacert], ca_cert_setting], + [@settings[:cacrl], ca_crl_setting], + [@settings[:cadir] + '/infra_crl.pem', ca_crl_setting], + [@settings[:hostcert], server_cert], + [@settings[:localcacert], ca_cert_setting], + [@settings[:hostcrl], ca_crl_setting], + [@settings[:hostpubkey], server_key.public_key], + [@settings[:capub], @ca.key.public_key], + [@settings[:cert_inventory], inventory], + [@settings[:cadir] + '/infra_inventory.txt', ''], + [@settings[:cadir] + '/infra_serials', ''], + [File.join(@settings[:signeddir], "#{@settings[:certname]}.pem"), server_cert], + [@settings[:hostprivkey], server_key], + [@settings[:rootkey], @root_key], + [@settings[:cakey], @ca.key] + ].each do |location, content| + FileSystem.write_file(location, content, 0644) + end + + @ca.update_serial_file(server_cert.serial + 1) + end -module PuppetSpec - module SSL - - PRIVATE_KEY_LENGTH = 2048 - FIVE_YEARS = 5 * 365 * 24 * 60 * 60 - CA_EXTENSIONS = [ - ["basicConstraints", "CA:TRUE", true], - ["keyUsage", "keyCertSign, cRLSign", true], - ["subjectKeyIdentifier", "hash", false], - ["authorityKeyIdentifier", "keyid:always", false] - ] - NODE_EXTENSIONS = [ - ["keyUsage", "digitalSignature,keyEncipherment", true], - ["subjectKeyIdentifier", "hash", false] - ] - DEFAULT_SIGNING_DIGEST = OpenSSL::Digest::SHA256.new - DEFAULT_REVOCATION_REASON = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE - - def self.create_private_key(length = PRIVATE_KEY_LENGTH) - OpenSSL::PKey::RSA.new(length) - end - - def self.self_signed_ca(key, name, serial = rand(2**128)) - cert = OpenSSL::X509::Certificate.new - - cert.public_key = key.public_key - cert.subject = OpenSSL::X509::Name.parse(name) - cert.issuer = cert.subject - cert.version = 2 - cert.serial = serial + def generate_cert(certnames, alt_names = []) + certnames = certnames.is_a?(Array) ? certnames : [certnames] - not_before = just_now - cert.not_before = not_before - cert.not_after = not_before + FIVE_YEARS + generate = Puppetserver::Ca::Action::Generate.new(@logger) + generate.generate_authorized_certs(certnames, alt_names, @settings, @digest) - ext_factory = extension_factory_for(cert, cert) - CA_EXTENSIONS.each do |ext| - extension = ext_factory.create_extension(*ext) - cert.add_extension(extension) + certnames.each do |certname| + add_inventory_cert("#{@settings[:signeddir]}/#{certname}.pem") + end end - cert.sign(key, DEFAULT_SIGNING_DIGEST) - - cert - end + def add_inventory_cert(certfile) + cert = OpenSSL::X509::Certificate.new(File.read(certfile)) + File.open(@settings[:cert_inventory], 'a') do |f| + f.puts(@ca.inventory_entry(cert)) + end + end - def self.create_crl(ca_cert, ca_key, revoked_cert, serial) - crl = OpenSSL::X509::CRL.new + def generate_key_csr(certname, alt_names = []) + generate = Puppetserver::Ca::Action::Generate.new(@logger) + _key, csr = generate.generate_key_csr(certname, @settings, @digest, alt_names) - crl.issuer = ca_cert.issuer - crl.version = 1 - last_update = just_now - crl.last_update = last_update - crl.next_update = last_update + FIVE_YEARS + Dir.mkdir @settings[:csrdir] unless Dir.exist? @settings[:csrdir] + generate.save_file(csr, certname, @settings[:csrdir], 'Certificate request') + end - revoked = OpenSSL::X509::Revoked.new - revoked.serial = revoked_cert.serial - revoked.time = just_now - crl.add_revoked(revoked) + def revoke_cert(certname) + crl = OpenSSL::X509::CRL.new(File.read(@settings[:cacrl])) + cert = OpenSSL::X509::Certificate.new(File.read("#{@settings[:certdir]}/#{certname}.pem")) - crlnum = OpenSSL::ASN1::Integer(serial) - crl.add_extension(OpenSSL::X509::Extension.new('crlNumber', crlnum)) + revocation = OpenSSL::X509::Revoked.new + revocation.serial = cert.serial + revocation.time = Time.now + crl.add_revoked(revocation) - ef = OpenSSL::X509::ExtensionFactory.new - ef.issuer_certificate = ca_cert - ef.crl = crl - crl.add_extension(ef.create_extension('authorityKeyIdentifier', 'keyid:always')) + ca_key = OpenSSL::PKey::RSA.new(File.read(@settings[:cakey])) + crl.sign(ca_key, @digest) - crl.sign(ca_key, DEFAULT_SIGNING_DIGEST) + File.write(@settings[:cacrl], crl.to_pem) + end - crl - end + def create_intermediate_cert(certname, ca_key = @root_key, ca_cert = @root_cert) + host = Puppetserver::Ca::Host.new(@digest) - def self.create_csr(name, key = PuppetSpec::SSL.create_private_key) - csr = OpenSSL::X509::Request.new + key = host.create_private_key(@settings[:keylength]) + int_csr = host.create_csr(name: certname, key: key) + cert = @ca.sign_intermediate(ca_key, ca_cert, int_csr) - csr.public_key = key.public_key - csr.subject = OpenSSL::X509::Name.parse(name) - csr.version = 2 - csr.sign(key, DEFAULT_SIGNING_DIGEST) + [cert, key] + end - csr - end + def create_crl(cert: @ca.cert, key: @ca.key, serial: 0, deltaserial: nil, akid: true) + crl = OpenSSL::X509::CRL.new + crl.issuer = cert.subject + crl.version = 1 - def self.sign(ca_key, ca_cert, csr, serial = rand(2**128), extensions = NODE_EXTENSIONS) - cert = OpenSSL::X509::Certificate.new + ef = @ca.extension_factory_for(cert) + crl.add_extension(ef.create_extension(['authorityKeyIdentifier', 'keyid:always', false])) if akid + crl.add_extension(OpenSSL::X509::Extension.new('crlNumber', OpenSSL::ASN1::Integer(serial))) + crl.add_extension(OpenSSL::X509::Extension.new('2.5.29.27', OpenSSL::ASN1::Integer(deltaserial), true)) \ + unless deltaserial.nil? - cert.public_key = csr.public_key - cert.subject = csr.subject - cert.issuer = ca_cert.subject - cert.version = 2 - cert.serial = serial + crl.last_update = just_now + crl.next_update = valid_until + crl.sign(key, @digest) - not_before = just_now - cert.not_before = not_before - cert.not_after = not_before + FIVE_YEARS + crl + end - ext_factory = extension_factory_for(ca_cert, cert) - extensions.each do |ext| - extension = ext_factory.create_extension(*ext) - cert.add_extension(extension) + def create_root_crl(serial, deltaserial = nil, akid: true) + create_crl(cert: @root_cert, key: @root_key, serial: serial, deltaserial: deltaserial, akid: akid) end - cert.sign(ca_key, DEFAULT_SIGNING_DIGEST) + def just_now + Time.now - 1 + end - cert + def valid_until + Time.now + @settings[:ca_ttl] + end end + end +end - private +def regen_http_client_test_pki + puts 'Regenerating PKI for puppetlabs.puppetserver.ruby.http-client-test ...' - def self.just_now - Time.now - 1 - end - - def self.extension_factory_for(ca, cert = nil) - ef = OpenSSL::X509::ExtensionFactory.new - ef.issuer_certificate = ca - ef.subject_certificate = cert if cert + pki = PuppetserverSpec::Ca::Pki.new + dest_dir = "#{__dir__}/puppetlabs/puppetserver/ruby/http_client_test" + FileUtils.cp pki.settings[:cacert], "#{dest_dir}/ca.pem" + FileUtils.cp pki.settings[:hostcert], "#{dest_dir}/localhost_cert.pem" + FileUtils.cp pki.settings[:hostprivkey], "#{dest_dir}/localhost_key.pem" +end - ef - end +def regen_ca_test_pki + puts 'Regenerating PKI for puppetlabs.puppetserver.certificate-authority-test ...' + + pki = PuppetserverSpec::Ca::Pki.new({ 'root_ca_name': 'Puppet CA: localhost' }, intermediate_cert: false) + pki.generate_cert(%w[test_cert revoked-agent]) + pki.generate_key_csr('test-agent') + pki.revoke_cert('revoked-agent') + + dest_dir = "#{__dir__}/puppetlabs/puppetserver/certificate_authority_test/master/conf/ca" + ca_test_files = [pki.settings[:cacert], + pki.settings[:cakey], + pki.settings[:capub], + pki.settings[:cacrl], + pki.settings[:serial], + pki.settings[:cert_inventory]] + FileUtils.cp ca_test_files, dest_dir + FileUtils.cp_r pki.settings[:signeddir], dest_dir + FileUtils.cp_r pki.settings[:csrdir], dest_dir +end - def self.bundle(*items) - items.map {|i| EXPLANATORY_TEXT + i.to_pem }.join("\n") - end +def regen_ca_test_crls_pki + puts 'Regenerating PKI for puppetlabs.puppetserver.certificate-authority-test/update-crls ...' + + settings = { + 'root_ca_name': 'Root CA', + 'ca_name': 'Intermediate CA 1' + } + + pki = PuppetserverSpec::Ca::Pki.new(settings) + ica2_cert, ica2_key = pki.create_intermediate_cert('Intermediate CA 2') + ica3_cert, ica3_key = pki.create_intermediate_cert('Intermediate CA 3', ica2_key, ica2_cert) + unrelated_pki = PuppetserverSpec::Ca::Pki.new(settings) + + dest_dir = "#{__dir__}/puppetlabs/puppetserver/certificate_authority_test/update_crls" + FileUtils.cp pki.settings[:cacert], "#{dest_dir}/ca_crt.pem" + FileUtils.cp pki.settings[:cacrl], "#{dest_dir}/ca_crl.pem" + FileUtils.cp unrelated_pki.settings[:cacrl], "#{dest_dir}/unrelated_crls.pem" + File.open("#{dest_dir}/old_root_crl.pem", 'w') { |f| f.puts(pki.create_root_crl(0)) } + File.open("#{dest_dir}/new_root_crl.pem", 'w') { |f| f.puts(pki.create_root_crl(1)) } + File.open("#{dest_dir}/multiple_new_root_crls.pem", 'w') do |f| + f.puts(pki.create_root_crl(1), pki.create_root_crl(10)) + end + File.open("#{dest_dir}/three_cert_chain.pem", 'w') do |f| + f.puts(ica3_cert, ica2_cert, pki.root_cert) + end + File.open("#{dest_dir}/three_crl.pem", 'w') do |f| + f.puts(pki.create_crl(cert: ica3_cert, key: ica3_key), + pki.create_crl(cert: ica2_cert, key: ica2_key), + pki.create_root_crl(0)) + end + File.open("#{dest_dir}/three_newer_crl_chain.pem", 'w') do |f| + f.puts(pki.create_crl(cert: ica3_cert, key: ica3_key, serial: 1), + pki.create_crl(cert: ica2_cert, key: ica2_key, serial: 1), + pki.create_root_crl(1)) + end + File.open("#{dest_dir}/new_crls_and_unrelated_crls.pem", 'w') do |f| + f.puts(pki.create_crl(serial: 10), + pki.create_root_crl(10), + File.read(unrelated_pki.settings[:cacrl])) + end + File.open("#{dest_dir}/chain_with_new_root.pem", 'w') do |f| + f.puts(pki.create_crl(serial: 0), pki.create_root_crl(1)) + end + File.open("#{dest_dir}/multiple_newest_root_crls.pem", 'w') do |f| + 2.times { f.puts(pki.create_root_crl(10)); sleep(2) unless _1 == 1 } + end + File.open("#{dest_dir}/delta_crl.pem", 'w') do |f| + f.puts(pki.create_root_crl(1, 0)) + end + File.open("#{dest_dir}/missing_auth_id_crl.pem", 'w') do |f| + f.puts(pki.create_crl(serial: 1005, akid: false)) end end -puts 'Regenerating certificates for puppetlabs.puppetserver.ruby.http-client-test ...' - -ca_key = PuppetSpec::SSL.create_private_key -ca_cert = PuppetSpec::SSL.self_signed_ca(ca_key, "/CN=Puppet CA: swanson.hsd1.or.comcast.net") +def regen_ca_core_test_pki + puts 'Regenerating PKI for puppetlabs.services.ca.certificate-authority-core-test ...' -host_key = PuppetSpec::SSL.create_private_key -host_csr = PuppetSpec::SSL.create_csr("/CN=localhost", host_key) -host_cert = PuppetSpec::SSL.sign(ca_key, ca_cert, host_csr) + settings = { + 'root_ca_name': 'Root CA', + 'ca_name': 'Intermediate CA 1' + } -http_client_test_dir = "#{__dir__}/puppetlabs/puppetserver/ruby/http_client_test" + pki = PuppetserverSpec::Ca::Pki.new(settings) -File.open("#{http_client_test_dir}/ca.pem", 'w') do |f| - f.puts(ca_cert) -end -File.open("#{http_client_test_dir}/localhost_cert.pem", 'w') do |f| - f.puts(host_cert) -end -File.open("#{http_client_test_dir}/localhost_key.pem", 'w') do |f| - f.puts(host_key) + dest_dir = "#{__dir__}/puppetlabs/services/ca/certificate_authority_core_test/update_crls" + FileUtils.cp pki.settings[:cacert], "#{dest_dir}/ca_crt.pem" + FileUtils.cp pki.settings[:cacrl], "#{dest_dir}/ca_crl.pem" + File.open("#{dest_dir}/new_root_crl.pem", 'w') { |f| f.puts(pki.create_root_crl(1)) } + File.open("#{dest_dir}/multiple_newest_root_crls.pem", 'w') do |f| + 2.times { f.puts(pki.create_root_crl(10)); sleep(2) unless _1 == 1 } + end end -puts 'Regenerating certificates for puppetlabs.puppetserver.certificate-authority-test ...' +def regen_ca_int_test_pki + puts 'Regenerating PKI for puppetlabs.services.certificate-authority.certificate-authority-int-test...' -ca_key = PuppetSpec::SSL.create_private_key -ca_cert = PuppetSpec::SSL.self_signed_ca(ca_key, "/CN=Puppet CA: localhost", 1) -test_agent_csr = PuppetSpec::SSL.create_csr("/CN=test-agent") -localhost_csr = PuppetSpec::SSL.create_csr("/CN=localhost") -localhost_cert = PuppetSpec::SSL.sign(ca_key, ca_cert, localhost_csr, 2) -test_cert_csr = PuppetSpec::SSL.create_csr("/CN=test_cert") -test_cert = PuppetSpec::SSL.sign(ca_key, ca_cert, test_cert_csr, 3) -revoked_agent_csr = PuppetSpec::SSL.create_csr("/CN=revoked-agent") -revoked_agent_cert = PuppetSpec::SSL.sign(ca_key, ca_cert, revoked_agent_csr, 4) -ca_crl = PuppetSpec::SSL.create_crl(ca_cert, ca_key, revoked_agent_cert, 1) + pki = PuppetserverSpec::Ca::Pki.new -ca_dir = "#{__dir__}/puppetlabs/puppetserver/certificate_authority_test/master/conf/ca" + dest_dir = "#{__dir__}/puppetlabs/services/certificate_authority/certificate_authority_int_test/ca_true_test/master/conf" + FileUtils.cp_r "#{pki.settings[:cadir]}/.", "#{dest_dir}/ca" + FileUtils.cp_r "#{pki.settings[:ssldir]}/.", "#{dest_dir}/ssl" -File.open("#{ca_dir}/ca_key.pem", 'w') do |f| - f.puts(ca_key) -end -File.open("#{ca_dir}/ca_pub.pem", 'w') do |f| - f.puts(ca_key.public_key) -end -File.open("#{ca_dir}/ca_crt.pem", 'w') do |f| - f.puts(ca_cert) -end -File.open("#{ca_dir}/requests/test-agent.pem", 'w') do |f| - f.puts(test_agent_csr) -end -File.open("#{ca_dir}/signed/localhost.pem", 'w') do |f| - f.puts(localhost_cert) -end -File.open("#{ca_dir}/signed/test_cert.pem", 'w') do |f| - f.puts(test_cert) -end -File.open("#{ca_dir}/signed/revoked-agent.pem", 'w') do |f| - f.puts(revoked_agent_cert) -end -File.open("#{ca_dir}/ca_crl.pem", 'w') do |f| - f.puts(ca_crl) -end -File.open("#{ca_dir}/serial", 'w') do |f| - f.write('0005') -end -File.open("#{ca_dir}/infra_crl.pem", 'w') { |f| f.truncate(0) } -File.open("#{ca_dir}/infra_serials", 'w') { |f| f.truncate(0) } -File.open("#{ca_dir}/inventory.txt", 'w') do |f| - for cert in [ ca_cert, localhost_cert, test_cert, revoked_agent_cert ] do - serial_hex = "0x#{cert.serial.to_s(16).rjust(4, '0')}" - not_before = cert.not_before.strftime('%Y-%m-%dT%H:%M:%SUTC') - not_after = cert.not_after.strftime('%Y-%m-%dT%H:%M:%SUTC') - subject = cert.subject - - f.puts("#{serial_hex} #{not_before} #{not_after} #{subject}") + dest_dir = "#{__dir__}/puppetlabs/services/certificate_authority/certificate_authority_int_test/infracrl_test/master/conf" + pki.generate_cert(%w[agent-node compile-master]) + FileUtils.cp_r "#{pki.settings[:cadir]}/.", "#{dest_dir}/ca" + FileUtils.cp_r "#{pki.settings[:ssldir]}/.", "#{dest_dir}/ssl" + File.open("#{dest_dir}/ca/infra_inventory.txt", 'w') do |f| + f.write("compile-master\n") end end -puts "Done." +regen_http_client_test_pki +regen_ca_test_pki +regen_ca_test_crls_pki +regen_ca_core_test_pki +regen_ca_int_test_pki