Class: Localhost::Authority
- Inherits:
-
Object
- Object
- Localhost::Authority
- Defined in:
- lib/localhost/authority.rb
Overview
Represents a single public/private key pair for a given hostname.
Constant Summary collapse
- BITS =
1024*2
- SERVER_CIPHERS =
"EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5".freeze
Instance Attribute Summary collapse
-
#hostname ⇒ Object
readonly
The hostname of the certificate authority.
Class Method Summary collapse
-
.fetch(*arguments, **options) ⇒ Object
Fetch (load or create) a certificate with the given hostname.
-
.list(root = self.path) ⇒ Object
List all certificate authorities in the given directory:.
- .path ⇒ Object
Instance Method Summary collapse
-
#certificate ⇒ Object
The public certificate.
-
#certificate_path ⇒ Object
The public certificate path.
- #client_context(*args) ⇒ Object
- #dh_key ⇒ Object
- #ecdh_key ⇒ Object
-
#initialize(hostname = "localhost", root: self.class.path) ⇒ Authority
constructor
Create an authority forn the given hostname.
-
#key ⇒ Object
The private key.
- #key=(key) ⇒ Object
-
#key_path ⇒ Object
The private key path.
- #load(path = @root) ⇒ Object
-
#name ⇒ Object
The certificate name.
- #name=(name) ⇒ Object
- #save(path = @root) ⇒ Object
- #server_context(*arguments) ⇒ Object
-
#store ⇒ Object
The certificate store which is used for validating the server certificate.
Constructor Details
#initialize(hostname = "localhost", root: self.class.path) ⇒ Authority
Create an authority forn the given hostname.
61 62 63 64 65 66 67 68 69 |
# File 'lib/localhost/authority.rb', line 61 def initialize(hostname = "localhost", root: self.class.path) @root = root @hostname = hostname @key = nil @name = nil @certificate = nil @store = nil end |
Instance Attribute Details
#hostname ⇒ Object (readonly)
The hostname of the certificate authority.
72 73 74 |
# File 'lib/localhost/authority.rb', line 72 def hostname @hostname end |
Class Method Details
.fetch(*arguments, **options) ⇒ Object
Fetch (load or create) a certificate with the given hostname. See #initialize for the format of the arguments.
48 49 50 51 52 53 54 55 56 |
# File 'lib/localhost/authority.rb', line 48 def self.fetch(*arguments, **) = self.new(*arguments, **) unless .load .save end return end |
.list(root = self.path) ⇒ Object
List all certificate authorities in the given directory:
32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/localhost/authority.rb', line 32 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") = self.new(name, root: root) if .load yield end end end |
.path ⇒ Object
27 28 29 |
# File 'lib/localhost/authority.rb', line 27 def self.path File.("~/.localhost") end |
Instance Method Details
#certificate ⇒ Object
The public certificate.
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/localhost/authority.rb', line 114 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 |
#certificate_path ⇒ Object
The public certificate path.
90 91 92 |
# File 'lib/localhost/authority.rb', line 90 def certificate_path File.join(@root, "#{@hostname}.crt") end |
#client_context(*args) ⇒ Object
179 180 181 182 183 184 185 186 187 |
# File 'lib/localhost/authority.rb', line 179 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_key ⇒ Object
80 81 82 |
# File 'lib/localhost/authority.rb', line 80 def dh_key @dh_key ||= OpenSSL::PKey::DH.new(BITS) end |
#ecdh_key ⇒ Object
76 77 78 |
# File 'lib/localhost/authority.rb', line 76 def ecdh_key @ecdh_key ||= OpenSSL::PKey::EC.new "prime256v1" end |
#key ⇒ Object
The private key.
95 96 97 |
# File 'lib/localhost/authority.rb', line 95 def key @key ||= OpenSSL::PKey::RSA.new(BITS) end |
#key=(key) ⇒ Object
99 100 101 |
# File 'lib/localhost/authority.rb', line 99 def key= key @key = key end |
#key_path ⇒ Object
The private key path.
85 86 87 |
# File 'lib/localhost/authority.rb', line 85 def key_path File.join(@root, "#{@hostname}.key") end |
#load(path = @root) ⇒ Object
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/localhost/authority.rb', line 189 def load(path = @root) 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 |
#name ⇒ Object
The certificate name.
104 105 106 |
# File 'lib/localhost/authority.rb', line 104 def name @name ||= OpenSSL::X509::Name.parse("/O=Development/CN=#{@hostname}") end |
#name=(name) ⇒ Object
108 109 110 |
# File 'lib/localhost/authority.rb', line 108 def name= name @name = name end |
#save(path = @root) ⇒ Object
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/localhost/authority.rb', line 209 def save(path = @root) Dir.mkdir(path, 0700) unless File.directory?(path) lockfile_path = File.join(path, "#{@hostname}.lock") File.open(lockfile_path, File::RDWR|File::CREAT, 0644) do |lockfile| lockfile.flock(File::LOCK_EX) File.write( File.join(path, "#{@hostname}.crt"), self.certificate.to_pem ) File.write( File.join(path, "#{@hostname}.key"), self.key.to_pem ) end end |
#server_context(*arguments) ⇒ Object
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/localhost/authority.rb', line 154 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 |
#store ⇒ Object
The certificate store which is used for validating the server certificate.
145 146 147 148 149 |
# File 'lib/localhost/authority.rb', line 145 def store @store ||= OpenSSL::X509::Store.new.tap do |store| store.add_cert(self.certificate) end end |