1 2 3 4 5 6 7 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 88 89 90 91 92 93 94 95 96 97 98
|
# frozen_string_literal: true
require 'openssl'
##
# Utility module for generating certificates used by a local Memcached server
# exposing a TLS/SSL interface in test.
##
module CertificateGenerator
ROOT_CA_PK_PATH = '/tmp/root.key'
ROOT_CA_CERT_PATH = '/tmp/root.crt'
MEMCACHED_PK_PATH = '/tmp/memcached.key'
MEMCACHED_CERT_PATH = '/tmp/memcached.crt'
def self.generate
issuer_cert, issuer_key = generate_root_certificate
generate_server_certificate(issuer_cert, issuer_key)
end
def self.ssl_args
"-Z -o ssl_chain_cert=#{MEMCACHED_CERT_PATH} -o ssl_key=#{MEMCACHED_PK_PATH}"
end
def self.clean
[ROOT_CA_CERT_PATH, ROOT_CA_PK_PATH, MEMCACHED_CERT_PATH, MEMCACHED_PK_PATH].each do |path|
FileUtils.rm_rf(path)
end
end
def self.ssl_context
ssl_context = OpenSSL::SSL::SSLContext.new
ssl_context.ca_file = CertificateGenerator::ROOT_CA_CERT_PATH
ssl_context.ssl_version = :TLSv1_2 # rubocop:disable Naming/VariableNumber
ssl_context.verify_hostname = true if ssl_context.respond_to?(:verify_hostname=)
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
ssl_context
end
def self.generate_server_certificate(issuer_cert, issuer_key)
cert, key = generate_certificate_common('/CN=localhost', issuer_cert)
cert.serial = 2
ef = extension_factory(cert, issuer_cert)
cert.add_extension(ef.create_extension('subjectAltName', 'DNS:localhost,IP:127.0.0.1', false))
cert.add_extension(ef.create_extension('keyUsage', 'digitalSignature', true))
cert.sign(issuer_key, OpenSSL::Digest.new('SHA256'))
File.write(MEMCACHED_PK_PATH, key)
File.write(MEMCACHED_CERT_PATH, cert)
[cert, key]
end
def self.generate_root_certificate
cert, key = generate_certificate_common('/CN=Dalli CA')
cert.serial = 1
ef = extension_factory(cert, cert)
cert.add_extension(ef.create_extension('basicConstraints', 'CA:TRUE', true))
cert.add_extension(ef.create_extension('keyUsage', 'keyCertSign, cRLSign', true))
cert.sign(key, OpenSSL::Digest.new('SHA256'))
File.write(ROOT_CA_PK_PATH, key)
File.write(ROOT_CA_CERT_PATH, cert)
[cert, key]
end
def self.extension_factory(cert, issuer_cert)
ef = OpenSSL::X509::ExtensionFactory.new
ef.subject_certificate = cert
ef.issuer_certificate = issuer_cert
cert.add_extension(ef.create_extension('subjectKeyIdentifier', 'hash', false))
ef
end
def self.generate_certificate_common(subject_as_s, issuer_cert = nil)
cert = base_cert(subject_as_s)
# Self-sign unless there's an issuer cert
cert.issuer = issuer_cert ? issuer_cert.subject : cert.subject
[cert, pk_for_cert(cert)]
end
def self.base_cert(subject_as_s)
cert = OpenSSL::X509::Certificate.new
cert.version = 2 # cf. RFC 5280 - to make it a "v3" certificate
cert.not_before = Time.now
cert.not_after = cert.not_before + (2 * 365 * 24 * 60 * 60) # 2 years
cert.subject = OpenSSL::X509::Name.parse(subject_as_s)
cert
end
def self.pk_for_cert(cert)
key = OpenSSL::PKey::RSA.new 2048 # the CA's public/private key
cert.public_key = key.public_key
key
end
end
|