examples/puma/client-certs/generate_client_test.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: false

=begin
run code to generate all certs
certs before date will be the first of the current month
expire in four years

JRuby:
see https://github.com/puma/puma/commit/4ae0de4f4cc
after running this Ruby code, delete keystore.jks and server.p12, then
cd examples/puma/client-certs
openssl pkcs12 -chain -CAfile ./ca.crt -export -password pass:jruby_puma -inkey server.key -in server.crt -name server -out server.p12
keytool -importkeystore -srckeystore server.p12 -srcstoretype pkcs12 -srcstorepass jruby_puma -destkeystore keystore.jks -deststoretype JKS -storepass jruby_puma
keytool -importcert -alias ca -noprompt -trustcacerts -file ca.crt -keystore keystore.jks -storepass jruby_puma
=end

require "openssl"

module Generate

  KEY_LEN = 2048
  SIGN_ALGORITHM = OpenSSL::Digest::SHA256

  CA_EXTS = [
    ["basicConstraints","CA:TRUE",true],
    ["keyUsage","cRLSign,keyCertSign",true],
  ]
  EE_EXTS = [
    #["keyUsage","keyEncipherment,digitalSignature",true],
    ["keyUsage","keyEncipherment,dataEncipherment,digitalSignature",true],
  ]

  class << self
    def run
      set_dates
      output_info
      setup_issue
      write_files
    end

    private

    def setup_issue
      ca    = OpenSSL::X509::Name.parse "/DC=net/DC=puma/CN=CA"
      ca_u  = OpenSSL::X509::Name.parse "/DC=net/DC=puma/CN=CAU"
      svr   = OpenSSL::X509::Name.parse "/DC=net/DC=puma/CN=localhost"
      cli   = OpenSSL::X509::Name.parse "/DC=net/DC=puma/CN=localhost"
      cli_u = OpenSSL::X509::Name.parse "/DC=net/DC=puma/CN=localhost"

      [:@ca_key, :@svr_key, :@cli_key, :@ca_key_u, :@cli_key_u].each do |k|
        instance_variable_set k, OpenSSL::PKey::RSA.generate(KEY_LEN)
      end

      @ca_cert  = issue_cert ca , @ca_key ,  3, @before, @after, CA_EXTS, nil     , nil    , SIGN_ALGORITHM.new
      @svr_cert = issue_cert svr, @svr_key,  7, @before, @after, EE_EXTS, @ca_cert, @ca_key, SIGN_ALGORITHM.new
      @cli_cert = issue_cert cli, @cli_key, 11, @before, @after, EE_EXTS, @ca_cert, @ca_key, SIGN_ALGORITHM.new

      # unknown certs
      @ca_cert_u  = issue_cert ca_u , @ca_key_u , 17, @before, @after, CA_EXTS, nil       , nil      , SIGN_ALGORITHM.new
      @cli_cert_u = issue_cert cli_u, @cli_key_u, 19, @before, @after, EE_EXTS, @ca_cert_u, @ca_key_u, SIGN_ALGORITHM.new

      # expired cert is identical to client cert with different dates
      @cli_cert_exp = issue_cert cli, @cli_key, 23, @b_exp, @a_exp, EE_EXTS, @ca_cert, @ca_key, SIGN_ALGORITHM.new
    end

    def issue_cert(dn, key, serial, not_before, not_after, extensions, issuer, issuer_key, digest)
      cert = OpenSSL::X509::Certificate.new
      issuer = cert unless issuer
      issuer_key = key unless issuer_key
      cert.version = 2
      cert.serial = serial
      cert.subject = dn
      cert.issuer = issuer.subject
      cert.public_key = key.public_key
      cert.not_before = not_before
      cert.not_after = not_after
      ef = OpenSSL::X509::ExtensionFactory.new
      ef.subject_certificate = cert
      ef.issuer_certificate = issuer
      extensions.each {|oid, value, critical|
        cert.add_extension(ef.create_extension(oid, value, critical))
      }
      cert.sign(issuer_key, digest)
      cert
    end

    def write_files
      Dir.chdir __dir__ do
        File.write "ca.crt"    , @ca_cert.to_pem , mode: 'wb'
        File.write "ca.key"    , @ca_key.to_pem  , mode: 'wb'
        File.write "server.crt", @svr_cert.to_pem, mode: 'wb'
        File.write "server.key", @svr_key.to_pem , mode: 'wb'
        File.write "client.crt", @cli_cert.to_pem, mode: 'wb'
        File.write "client.key", @cli_key.to_pem , mode: 'wb'

        File.write "unknown_ca.crt", @ca_cert_u.to_pem, mode: 'wb'
        File.write "unknown_ca.key", @ca_key_u.to_pem , mode: 'wb'

        File.write "client_unknown.crt", @cli_cert_u.to_pem, mode: 'wb'
        File.write "client_unknown.key", @cli_key_u.to_pem , mode: 'wb'

        File.write "client_expired.crt", @cli_cert_exp.to_pem, mode: 'wb'
        File.write "client_expired.key", @cli_key.to_pem     , mode: 'wb'
      end
    end

    def set_dates
      now = Time.now.utc
      mo = now.month
      yr = now.year
      zone = '+00:00'

      @before = Time.new yr  , mo, 1, 0, 0, 0, zone
      @after  = Time.new yr+4, mo, 1, 0, 0, 0, zone

      @b_exp  = Time.new yr-1, mo, 1, 0, 0, 0, zone
      @a_exp  = Time.new yr  , mo, 1, 0, 0, 0, zone
    end

    def output_info
      puts ""
      puts "    Key length: #{KEY_LEN}"
      puts     "sign_algorithm: #{SIGN_ALGORITHM}"
      puts ""
      puts "Normal cert dates:  #{@before} to #{@after}"
      puts ""
      puts "Expired cert dates: #{@b_exp} to #{@a_exp}"
      puts ""
    end
  end
end

Generate.run