Class: Localhost::Authority

Inherits:
Object
  • Object
show all
Defined in:
lib/localhost/authority.rb

Constant Summary collapse

BITS =
1024*2
SERVER_CIPHERS =
"EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5".freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(hostname = "localhost") ⇒ Authority

Returns a new instance of Authority.



43
44
45
46
47
48
49
50
# File 'lib/localhost/authority.rb', line 43

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

Class Method Details

.fetch(*args) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/localhost/authority.rb', line 30

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

.pathObject



26
27
28
# File 'lib/localhost/authority.rb', line 26

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

Instance Method Details

#certificateObject



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/localhost/authority.rb', line 78

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

#client_context(*args) ⇒ Object



140
141
142
143
144
145
146
147
148
# File 'lib/localhost/authority.rb', line 140

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

#dh_keyObject



58
59
60
# File 'lib/localhost/authority.rb', line 58

def dh_key
	@dh_key ||= OpenSSL::PKey::DH.new(BITS)
end

#ecdh_keyObject



54
55
56
# File 'lib/localhost/authority.rb', line 54

def ecdh_key
	@ecdh_key ||= OpenSSL::PKey::EC.new "prime256v1"
end

#keyObject



62
63
64
# File 'lib/localhost/authority.rb', line 62

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

#key=(key) ⇒ Object



66
67
68
# File 'lib/localhost/authority.rb', line 66

def key= key
	@key = key
end

#load(path) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/localhost/authority.rb', line 150

def load(path)
	if File.directory? path
		certificate_path = File.join(path, "#{@hostname}.crt")
		key_path = File.join(path, "#{@hostname}.key")
		
		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))
		
		# Certificates with old version need to be regenerated.
		return false if certificate.version < 2
		
		@certificate = certificate
		@key = key
		
		return true
	end
end

#nameObject



70
71
72
# File 'lib/localhost/authority.rb', line 70

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

#name=(name) ⇒ Object



74
75
76
# File 'lib/localhost/authority.rb', line 74

def name= name
	@name = name
end

#save(path) ⇒ Object



170
171
172
173
174
175
176
177
178
179
180
# File 'lib/localhost/authority.rb', line 170

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

#server_context(*args) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/localhost/authority.rb', line 117

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"
		
		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-224:P-521'
		elsif context.respond_to? :tmp_ecdh_callback=
			context.tmp_ecdh_callback = proc {self.ecdh_key}
		end
		
		context.set_params(
			ciphers: SERVER_CIPHERS
		)
	end
end

#storeObject

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



109
110
111
112
113
# File 'lib/localhost/authority.rb', line 109

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