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.
50 51 52 53 54 55 56 57 58 |
# File 'lib/localhost/authority.rb', line 50 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.
61 62 63 |
# File 'lib/localhost/authority.rb', line 61 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.
37 38 39 40 41 42 43 44 45 |
# File 'lib/localhost/authority.rb', line 37 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:
21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/localhost/authority.rb', line 21 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
16 17 18 |
# File 'lib/localhost/authority.rb', line 16 def self.path File.("~/.localhost") end |
Instance Method Details
#certificate ⇒ Object
The public certificate.
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/localhost/authority.rb', line 103 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 = Time.now.to_i 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.
79 80 81 |
# File 'lib/localhost/authority.rb', line 79 def certificate_path File.join(@root, "#{@hostname}.crt") end |
#client_context(*args) ⇒ Object
168 169 170 171 172 173 174 175 176 |
# File 'lib/localhost/authority.rb', line 168 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
69 70 71 |
# File 'lib/localhost/authority.rb', line 69 def dh_key @dh_key ||= OpenSSL::PKey::DH.new(BITS) end |
#ecdh_key ⇒ Object
65 66 67 |
# File 'lib/localhost/authority.rb', line 65 def ecdh_key @ecdh_key ||= OpenSSL::PKey::EC.new "prime256v1" end |
#key ⇒ Object
The private key.
84 85 86 |
# File 'lib/localhost/authority.rb', line 84 def key @key ||= OpenSSL::PKey::RSA.new(BITS) end |
#key=(key) ⇒ Object
88 89 90 |
# File 'lib/localhost/authority.rb', line 88 def key= key @key = key end |
#key_path ⇒ Object
The private key path.
74 75 76 |
# File 'lib/localhost/authority.rb', line 74 def key_path File.join(@root, "#{@hostname}.key") end |
#load(path = @root) ⇒ Object
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/localhost/authority.rb', line 178 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.
93 94 95 |
# File 'lib/localhost/authority.rb', line 93 def name @name ||= OpenSSL::X509::Name.parse("/O=Development/CN=#{@hostname}") end |
#name=(name) ⇒ Object
97 98 99 |
# File 'lib/localhost/authority.rb', line 97 def name= name @name = name end |
#save(path = @root) ⇒ Object
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/localhost/authority.rb', line 198 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
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/localhost/authority.rb', line 143 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.
134 135 136 137 138 |
# File 'lib/localhost/authority.rb', line 134 def store @store ||= OpenSSL::X509::Store.new.tap do |store| store.add_cert(self.certificate) end end |