Class: Portless::Certs
- Inherits:
-
Object
- Object
- Portless::Certs
- Defined in:
- lib/portless/certs.rb
Overview
Local CA + per-host leaf certs, all in native Ruby OpenSSL (portless shells out to the openssl binary; we don't have to). Because *.localhost wildcard certs aren't honoured at the reserved-TLD boundary, every SNI hostname gets its own leaf, minted on demand and cached on disk + in memory.
Constant Summary collapse
- CA_SUBJECT =
"/CN=rb-portless Local CA"- CA_DAYS =
3650- LEAF_DAYS =
365- CURVE =
"prime256v1"
Instance Method Summary collapse
-
#ca_certificate ⇒ Object
The CA certificate (PEM-loaded), generating + persisting one on first use.
-
#ca_fingerprint ⇒ Object
SHA-256 fingerprint — used by the trust marker + OS trust check.
- #ca_key ⇒ Object
- #ensure_ca! ⇒ Object
-
#initialize ⇒ Certs
constructor
A new instance of Certs.
-
#leaf_for(hostname) ⇒ Object
[cert, key] for an SNI hostname.
Constructor Details
#initialize ⇒ Certs
Returns a new instance of Certs.
17 18 19 |
# File 'lib/portless/certs.rb', line 17 def initialize @leaves = {} # hostname => [cert, key] end |
Instance Method Details
#ca_certificate ⇒ Object
The CA certificate (PEM-loaded), generating + persisting one on first use.
22 23 24 25 26 27 |
# File 'lib/portless/certs.rb', line 22 def ca_certificate @ca_certificate ||= begin ensure_ca! OpenSSL::X509::Certificate.new(File.read(State.ca_cert)) end end |
#ca_fingerprint ⇒ Object
SHA-256 fingerprint — used by the trust marker + OS trust check.
37 38 39 |
# File 'lib/portless/certs.rb', line 37 def ca_fingerprint OpenSSL::Digest::SHA256.hexdigest(ca_certificate.to_der) end |
#ca_key ⇒ Object
29 30 31 32 33 34 |
# File 'lib/portless/certs.rb', line 29 def ca_key @ca_key ||= begin ensure_ca! OpenSSL::PKey.read(File.read(State.ca_key)) end end |
#ensure_ca! ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/portless/certs.rb', line 48 def ensure_ca! return if File.exist?(State.ca_cert) && File.exist?(State.ca_key) State.ensure_dir! key = OpenSSL::PKey::EC.generate(CURVE) cert = OpenSSL::X509::Certificate.new name = OpenSSL::X509::Name.parse(CA_SUBJECT) cert.version = 2 cert.serial = random_serial cert.subject = name cert.issuer = name cert.public_key = ec_public(key) cert.not_before = Time.now - 60 cert.not_after = Time.now + CA_DAYS * 86_400 ef = OpenSSL::X509::ExtensionFactory.new ef.subject_certificate = cert ef.issuer_certificate = cert cert.add_extension(ef.create_extension("basicConstraints", "CA:TRUE", true)) cert.add_extension(ef.create_extension("keyUsage", "keyCertSign,cRLSign", true)) cert.add_extension(ef.create_extension("subjectKeyIdentifier", "hash")) cert.sign(key, OpenSSL::Digest.new("SHA256")) write_secret(State.ca_key, key.to_pem) File.write(State.ca_cert, cert.to_pem) State.fix_ownership end |
#leaf_for(hostname) ⇒ Object
[cert, key] for an SNI hostname. Cached in memory; persisted under host-certs/ so a proxy restart doesn't re-mint everything.
43 44 45 46 |
# File 'lib/portless/certs.rb', line 43 def leaf_for(hostname) hostname = hostname.to_s.downcase @leaves[hostname] ||= load_leaf(hostname) || generate_leaf(hostname) end |