LocalhostSourceLocalhostIssuer

class Issuer

Represents a local Root Certificate Authority used to sign development certificates.

Definitions

BITS = 4096

The default number of bits for the private key. 4096 bits.

VALIDITY = 10 * 365 * 24 * 60 * 60

The default validity period for the certificate. 10 years in seconds.

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

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

Implementation

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

NAME = "development"

The default certificate issuer name.

def initialize(name = nil, path: State.path)

Initialize the issuer with the given name.

Signature

parameter name String

The common name to use for the certificate.

parameter path String

The path path for loading and saving the certificate.

Implementation

def initialize(name = nil, path: State.path)
	@name = name || NAME
	@path = path
	
	@subject = nil
	@key = nil
	@certificate = nil
end

def key_path

Signature

returns String

The path to the private key.

Implementation

def key_path
	File.join(@path, "#{@name}.key")
end

def certificate_path

Signature

returns String

The path to the public certificate.

Implementation

def certificate_path
	File.join(@path, "#{@name}.crt")
end

def subject

Signature

returns OpenSSL::X509::Name

The subject name for the certificate.

Implementation

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

def subject= subject

Set the subject name for the certificate.

Signature

parameter subject OpenSSL::X509::Name

The subject name for the certificate.

Implementation

def subject= subject
	@subject = subject
end

def key

Signature

returns OpenSSL::PKey::RSA

The private key.

Implementation

def key
	@key ||= OpenSSL::PKey::RSA.new(BITS)
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.subject
		# We use the same issuer as the subject, which makes this certificate self-signed:
		certificate.issuer = self.subject
		
		certificate.public_key = self.key.public_key
		
		certificate.serial = Time.now.to_i
		certificate.version = 2
		
		certificate.not_before = Time.now - 10
		certificate.not_after = Time.now + VALIDITY
		
		extension_factory = ::OpenSSL::X509::ExtensionFactory.new
		extension_factory.subject_certificate = certificate
		extension_factory.issuer_certificate = certificate
		
		certificate.add_extension extension_factory.create_extension("basicConstraints", "CA:TRUE", true)
		certificate.add_extension extension_factory.create_extension("keyUsage", "keyCertSign, cRLSign", true)
		certificate.add_extension extension_factory.create_extension("subjectKeyIdentifier", "hash")
		certificate.add_extension extension_factory.create_extension("authorityKeyIdentifier", "keyid:always", false)
		
		certificate.sign self.key, OpenSSL::Digest::SHA256.new
	end
end

def load(path = @root)

Load the certificate and key from the given path.

Signature

parameter path String

The path to load the certificate and key.

returns Boolean

True if the certificate and key were loaded successfully.

Implementation

def load(path = @root)
	certificate_path = self.certificate_path
	key_path = self.key_path
	
	return false unless File.exist?(certificate_path) and File.exist?(key_path)
	
	certificate = OpenSSL::X509::Certificate.new(File.read(certificate_path))
	key = OpenSSL::PKey::RSA.new(File.read(key_path))
	
	@certificate = certificate
	@key = key
	
	return true
end

def lockfile_path

Signature

returns String

The path to the lockfile.

Implementation

def lockfile_path
	File.join(@path, "#{@name}.lock")
end

def save(path = @root)

Save the certificate and key to the given path.

Signature

parameter path String

The path to save the certificate and key.

Implementation

def save(path = @root)
	lockfile_path = self.lockfile_path
	
	File.open(lockfile_path, File::RDWR|File::CREAT, 0644) do |lockfile|
		lockfile.flock(File::LOCK_EX)
		
		File.write(
			self.certificate_path,
			self.certificate.to_pem
		)
		
		File.write(
			self.key_path,
			self.key.to_pem
		)
	end
	
	return true
end