class Localhost::Authority

def self.fetch(*args)

def self.fetch(*args)
	authority = self.new(*args)
	path = self.path
	
	unless authority.load(path)
		Dir.mkdir(path, 0700) unless File.directory?(path)
		
		authority.save(path)
	end
	
	return authority
end

def self.path

def self.path
	File.expand_path("~/.localhost")
end

def certificate

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.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
		
		# Because we are using a self-signed root certificate, we also need to make it a "pseudo-CA".
		# https://security.stackexchange.com/questions/143061/does-openssl-refuse-self-signed-certificates-without-basic-constraints
		certificate.add_extension extension_factory.create_extension("basicConstraints", "CA:TRUE", true)
		certificate.add_extension extension_factory.create_extension("keyUsage", "keyCertSign, cRLSign, digitalSignature", true)
		certificate.add_extension extension_factory.create_extension("subjectKeyIdentifier", "hash")
		
		certificate.sign self.key, OpenSSL::Digest::SHA256.new
	end
end

def client_context(*args)

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,
			verify_hostname: false,
		)
	end
end

def initialize(hostname = "localhost")

def initialize(hostname = "localhost")
	@hostname = hostname
	
	@key = nil
	@name = nil
	@certificate = nil
	@store = nil
end

def key

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

def load(path)

def load(path)
	if File.directory? path
		key_path = File.join(path, "#{@hostname}.key")
		return false unless File.exist?(key_path)
		@key = OpenSSL::PKey::RSA.new(File.read(key_path))
		
		certificate_path = File.join(path, "#{@hostname}.crt")
		@certificate = OpenSSL::X509::Certificate.new(File.read(certificate_path))
		
		return true
	end
end

def name

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

def save(path)

def save(path)
	File.write(
		File.join(path, "#{@hostname}.crt"),
		self.certificate.to_pem
	)
	
	File.write(
		File.join(path, "#{@hostname}.key"),
		self.key.to_pem
	)
end

def server_context(*args)

def server_context(*args)
	OpenSSL::SSL::SSLContext.new(*args).tap do |context|
		context.key = self.key
		context.cert = self.certificate
		
		context.session_id_context = "localhost"
		
		context.set_params(
			verify_hostname: false,
		)
	end
end

def store

The certificate store which is used for validating the server certificate:
def store
	@store ||= OpenSSL::X509::Store.new.tap do |store|
		store.add_cert(self.certificate)
	end
end