diff --git a/test-resources/regen_certs.rb b/test-resources/regen_certs.rb new file mode 100644 index 0000000..9c746fa --- /dev/null +++ b/test-resources/regen_certs.rb @@ -0,0 +1,287 @@ +require "fileutils" +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 + + @ca_crl = @ca.crl + @ca_cert = @ca.cert + @ca_key = @ca.key + + 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 + + def generate_cert(certnames, alt_names = []) + certnames = certnames.is_a?(Array) ? certnames : [certnames] + + generate = Puppetserver::Ca::Action::Generate.new(@logger) + generate.generate_authorized_certs(certnames, alt_names, @settings, @digest) + + certnames.each do |certname| + add_inventory_cert("#{@settings[:signeddir]}/#{certname}.pem") + end + 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 generate_key_csr(certname, alt_names = []) + generate = Puppetserver::Ca::Action::Generate.new(@logger) + _key, csr = generate.generate_key_csr(certname, @settings, @digest, alt_names) + + Dir.mkdir @settings[:csrdir] unless Dir.exist? @settings[:csrdir] + generate.save_file(csr, certname, @settings[:csrdir], 'Certificate request') + end + + def revoke_cert(certname) + crl = OpenSSL::X509::CRL.new(File.read(@settings[:cacrl])) + cert = OpenSSL::X509::Certificate.new(File.read("#{@settings[:certdir]}/#{certname}.pem")) + + revocation = OpenSSL::X509::Revoked.new + revocation.serial = cert.serial + revocation.time = Time.now + crl.add_revoked(revocation) + + ca_key = OpenSSL::PKey::RSA.new(File.read(@settings[:cakey])) + crl.sign(ca_key, @digest) + + File.write(@settings[:cacrl], crl.to_pem) + end + + def create_intermediate_cert(certname, ca_key = @root_key, ca_cert = @root_cert) + host = Puppetserver::Ca::Host.new(@digest) + + 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) + + [cert, key] + end + + def create_crl(cert: @ca.cert, key: @ca.key, serial: 0, deltaserial: nil, akid: true, last_update: just_now, next_update: valid_until) + crl = OpenSSL::X509::CRL.new + crl.issuer = cert.subject + crl.version = 1 + + 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? + + crl.last_update = last_update + crl.next_update = next_update + crl.sign(key, @digest) + + crl + end + + def create_root_crl(serial, deltaserial = nil, akid: true) + create_crl(cert: @root_cert, key: @root_key, serial: serial, deltaserial: deltaserial, akid: akid) + end + + def just_now + Time.now - 1 + end + + def valid_until + Time.now + @settings[:ca_ttl] + end + + def cleanup + FileUtils.rm_rf @settings[:confdir] + end + end + end +end + +def regen_crl_tests_pki + puts 'Regenerating PKI for puppetlabs.ssl-utils.core-test/validate-cert-chain-and-crls-test ...' + + 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', pki.ca_key, pki.ca_cert) + ica3_cert, ica3_key = pki.create_intermediate_cert('Intermediate CA 3', pki.ca_key, pki.ca_cert) + root_crl = pki.create_root_crl(0) + ica2_crl = pki.create_crl(cert: ica2_cert, key: ica2_key) + ica3_crl = pki.create_crl(cert: ica3_cert, key: ica3_key) + unrelated_pki = PuppetserverSpec::Ca::Pki.new(settings) + + dest_dir = "#{__dir__}/puppetlabs/ssl_utils/examples/ssl/crl_validation" + File.open("#{dest_dir}/1-cert-chain.pem", 'w') do |f| + f.puts(pki.root_cert) + end + File.open("#{dest_dir}/1-crl-chain.pem", 'w') do |f| + f.puts(pki.create_root_crl(0)) + end + File.open("#{dest_dir}/2-crl-chain.pem", 'w') do |f| + f.puts(pki.ca_crl, ica2_crl) + end + File.open("#{dest_dir}/3-cert-chain.pem", 'w') do |f| + f.puts(ica2_cert, pki.ca_cert, pki.root_cert) + end + File.open("#{dest_dir}/3-crl-chain.pem", 'w') do |f| + f.puts(root_crl, pki.ca_crl, ica2_crl) + end + File.open("#{dest_dir}/4-crl-chain.pem", 'w') do |f| + f.puts(root_crl, pki.ca_crl, ica3_crl, ica2_crl) + end + File.open("#{dest_dir}/cert-with-expired-crl.pem", 'w') do |f| + f.puts(pki.root_cert) + end + File.open("#{dest_dir}/expired-crl.pem", 'w') do |f| + f.puts(pki.create_crl(cert: pki.root_cert, + key: pki.root_key, + last_update: Time.now - pki.settings[:ca_ttl] * 2, + next_update: Time.now - pki.settings[:ca_ttl])) + end + File.open("#{dest_dir}/cert-with-crl-bad-sig.pem", 'w') do |f| + f.puts(pki.root_cert) + end + File.open("#{dest_dir}/crl-with-bad-signature.pem", 'w') do |f| + f.puts(pki.create_crl(cert: unrelated_pki.root_cert, + key: unrelated_pki.root_key)) + end + File.open("#{dest_dir}/cert-with-not-valid-crl.pem", 'w') do |f| + f.puts(pki.root_cert) + end + File.open("#{dest_dir}/not-yet-valid-crl.pem", 'w') do |f| + f.puts(pki.create_crl(cert: pki.root_cert, + key: pki.root_key, + last_update: Time.now + pki.settings[:ca_ttl])) + end + File.open("#{dest_dir}/cert-chain-with-revoked-cert.pem", 'w') do |f| + f.puts(ica2_cert, pki.ca_cert, pki.root_cert) + end + File.open("#{dest_dir}/crl-chain-with-cert-revoked.pem", 'w') do |f| + revocation = OpenSSL::X509::Revoked.new + revocation.serial = ica2_cert.serial + revocation.time = Time.now + root_crl.add_revoked(revocation) + signer = Puppetserver::Ca::Utils::SigningDigest.new + root_crl.sign(pki.root_key, signer.digest) + f.puts(root_crl, pki.ca_crl, ica2_crl) + end + File.open("#{dest_dir}/other-3-cert-chain.pem", 'w') do |f| + f.puts(ica2_cert, pki.ca_cert, pki.root_cert) + end + + pki.cleanup + unrelated_pki.cleanup +end + +regen_crl_tests_pki