ProjectSource

Localhost::Authority

Represents a single public/private key pair for a given hostname.

Definitions

def self.list(root = self.path)

List all certificate authorities in the given directory:

Implementation

def self.list(root = self.path)
	return to_enum(:list) unless block_given?
	
	Dir.glob("*.crt", base: root) do |path|
		name = File.basename(path, ".crt")
		
		authority = self.new(name, root: root)
		
		if authority.load
			yield authority
		end
	end
end

def self.fetch(*arguments, **options)

Fetch (load or create) a certificate with the given hostname. See #initialize for the format of the arguments.

Implementation

def self.fetch(*arguments, **options)
	authority = self.new(*arguments, **options)
	
	unless authority.load
		authority.save
	end
	
	return authority
end

def initialize(hostname = "localhost", root: self.class.path)

Create an authority forn the given hostname.

Signature

parameter hostname String

The common name to use for the certificate.

parameter root String

The root path for loading and saving the certificate.

Implementation

def initialize(hostname = "localhost", root: self.class.path)
	@root = root
	@hostname = hostname
	
	@key = nil
	@name = nil
	@certificate = nil
	@store = nil
end

attr :hostname

The hostname of the certificate authority.

def key_path

The private key path.

Implementation

def key_path
	File.join(@root, "#{@hostname}.key")
end

def certificate_path

The public certificate path.

Implementation

def certificate_path
	File.join(@root, "#{@hostname}.crt")
end

def key

The private key.

Implementation

def key
	@key ||= OpenSSL::PKey::RSA.new(BITS)
end

def name

The certificate name.

Implementation

def name
	@name ||= OpenSSL::X509::Name.parse("/O=Development/CN=#{@hostname}")
end

def certificate

The public certificate.

Signature

returns OpenSSL::X509::Certificate

A self-signed certificate.

Implementation

def certificate
	@certificate ||= OpenSSL::X509::Certificate.new.tap do |certificate|
		certificate.subject = self.name
		# We use the same issuer as the subject, which makes this certificate self-signed:
		certificate.issuer = self.name
		
		certificate.public_key = self.key.public_key
		
		certificate.serial = 1
		certificate.version = 2
		
		certificate.not_before = Time.now
		certificate.not_after = Time.now + (3600 * 24 * 365 * 10)
		
		extension_factory = OpenSSL::X509::ExtensionFactory.new
		extension_factory.subject_certificate = certificate
		extension_factory.issuer_certificate = certificate
		
		certificate.extensions = [
			extension_factory.create_extension("basicConstraints", "CA:FALSE", true),
			extension_factory.create_extension("subjectKeyIdentifier", "hash"),
		]
		
		certificate.add_extension extension_factory.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
		certificate.add_extension extension_factory.create_extension("subjectAltName", "DNS: #{@hostname}")
		
		certificate.sign self.key, OpenSSL::Digest::SHA256.new
	end
end

def store

The certificate store which is used for validating the server certificate.

Implementation

def store
	@store ||= OpenSSL::X509::Store.new.tap do |store|
		store.add_cert(self.certificate)
	end
end

def server_context(*arguments)

Signature

returns OpenSSL::SSL::SSLContext

An context suitable for implementing a secure server.

Implementation

def server_context(*arguments)
	OpenSSL::SSL::SSLContext.new(*arguments).tap do |context|
		context.key = self.key
		context.cert = self.certificate
		
		context.session_id_context = "localhost"
		
		if context.respond_to? :tmp_dh_callback=
			context.tmp_dh_callback = proc {self.dh_key}
		end
		
		if context.respond_to? :ecdh_curves=
			context.ecdh_curves = 'P-256:P-384:P-521'
		elsif context.respond_to? :tmp_ecdh_callback=
			context.tmp_ecdh_callback = proc {self.ecdh_key}
		end
		
		context.set_params(
			ciphers: SERVER_CIPHERS,
			verify_mode: OpenSSL::SSL::VERIFY_NONE,
		)
	end
end

def client_context(*args)

Signature

returns OpenSSL::SSL::SSLContext

An context suitable for connecting to a secure server using this authority.

Implementation

def client_context(*args)
	OpenSSL::SSL::SSLContext.new(*args).tap do |context|
		context.cert_store = self.store
		
		context.set_params(
			verify_mode: OpenSSL::SSL::VERIFY_PEER,
		)
	end
end