Class: Acmesmith::Certificate
- Inherits:
-
Object
- Object
- Acmesmith::Certificate
- Defined in:
- lib/acmesmith/certificate.rb
Defined Under Namespace
Classes: CertificateExport, PassphraseRequired, PrivateKeyDecrypted
Instance Attribute Summary collapse
- #certificate ⇒ OpenSSL::X509::Certificate readonly
- #chain ⇒ Array<OpenSSL::X509::Certificate> readonly
- #csr ⇒ OpenSSL::X509::Request readonly
-
#name ⇒ String
Returns a predicted certificate name, taken from common name or first SAN.
Class Method Summary collapse
-
.by_issuance(pem_chain, csr, name: nil) ⇒ Acmesmith::Certificate
Return Acmesmith::Certificate by an issued certificate.
-
.split_pems(pems) ⇒ Array<String>
Split string containing multiple PEMs into Array of PEM strings.
Instance Method Summary collapse
-
#all_sans ⇒ Array<String>
Returns a list of subject alternative names included in the certificate.
-
#common_name ⇒ String?
Returns a certificate common name taken from the certificate subject’s CN field.
- #export(passphrase, cipher: OpenSSL::Cipher.new('aes-256-cbc')) ⇒ CertificateExport
-
#fullchain ⇒ String
Leaf certificate + full certificate chain.
-
#initialize(certificate, chain, private_key, key_passphrase = nil, csr = nil, name: nil) ⇒ Certificate
constructor
A new instance of Certificate.
-
#issuer_pems ⇒ String
Issuer certificate chain.
-
#key_passphrase=(pw) ⇒ Object
Try to decrypt private_key if encrypted.
- #pkcs12(passphrase) ⇒ OpenSSL::PKCS12
- #private_key ⇒ OpenSSL::PKey::PKey
- #public_key ⇒ OpenSSL::PKey::PKey
-
#sans ⇒ Array<String>
Returns a list of DNS subject alternative names included in the certificate.
-
#version ⇒ String
Version string (consists of NotBefore time & certificate serial).
Constructor Details
#initialize(certificate, chain, private_key, key_passphrase = nil, csr = nil, name: nil) ⇒ Certificate
Returns a new instance of Certificate.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/acmesmith/certificate.rb', line 33 def initialize(certificate, chain, private_key, key_passphrase = nil, csr = nil, name: nil) @name = name @certificate = case certificate when OpenSSL::X509::Certificate certificate when String OpenSSL::X509::Certificate.new(certificate) else raise TypeError, 'certificate is expected to be a String or OpenSSL::X509::Certificate' end chain = case chain when String self.class.split_pems(chain) when Array chain when nil [] else raise TypeError, 'chain is expected to be an Array<String or OpenSSL::X509::Certificate> or nil' end @chain = chain.map { |cert| case cert when OpenSSL::X509::Certificate cert when String OpenSSL::X509::Certificate.new(cert) else raise TypeError, 'chain is expected to be an Array<String or OpenSSL::X509::Certificate> or nil' end } case private_key when String @raw_private_key = private_key if key_passphrase self.key_passphrase = key_passphrase else begin @private_key = OpenSSL::PKey.read(@raw_private_key) { nil } rescue OpenSSL::PKey::PKeyError # may be encrypted end end when OpenSSL::PKey::PKey @private_key = private_key else raise TypeError, 'private_key is expected to be a String or OpenSSL::PKey::PKey' end @csr = case csr when nil nil when String OpenSSL::X509::Request.new(csr) when OpenSSL::X509::Request csr end end |
Instance Attribute Details
#certificate ⇒ OpenSSL::X509::Certificate (readonly)
94 95 96 |
# File 'lib/acmesmith/certificate.rb', line 94 def certificate @certificate end |
#chain ⇒ Array<OpenSSL::X509::Certificate> (readonly)
96 97 98 |
# File 'lib/acmesmith/certificate.rb', line 96 def chain @chain end |
#csr ⇒ OpenSSL::X509::Request (readonly)
98 99 100 |
# File 'lib/acmesmith/certificate.rb', line 98 def csr @csr end |
#name ⇒ String
Returns a predicted certificate name, taken from common name or first SAN. Note that this value can contain colons (‘:’) if name is taken from non-DNS subject alternative name.
139 140 141 |
# File 'lib/acmesmith/certificate.rb', line 139 def name @name || common_name || sans.first || all_sans.first end |
Class Method Details
.by_issuance(pem_chain, csr, name: nil) ⇒ Acmesmith::Certificate
Return Acmesmith::Certificate by an issued certificate
22 23 24 25 |
# File 'lib/acmesmith/certificate.rb', line 22 def self.by_issuance(pem_chain, csr, name: nil) pems = split_pems(pem_chain) new(pems[0], pems[1..-1], csr.private_key, nil, csr, name: name) end |
.split_pems(pems) ⇒ Array<String>
Split string containing multiple PEMs into Array of PEM strings.
13 14 15 |
# File 'lib/acmesmith/certificate.rb', line 13 def self.split_pems(pems) pems.each_line.slice_before(/^-----BEGIN CERTIFICATE-----$/).map(&:join) end |
Instance Method Details
#all_sans ⇒ Array<String>
Returns a list of subject alternative names included in the certificate.
153 154 155 156 157 |
# File 'lib/acmesmith/certificate.rb', line 153 def all_sans certificate.extensions.select { |_| _.oid == 'subjectAltName' }.flat_map do |ext| ext.value.split(/,\s*/) end end |
#common_name ⇒ String?
Returns a certificate common name taken from the certificate subject’s CN field. Under the real CA, CNs can be missing. Use #name instead to retrieve the certificate name for most cases. ref. github.com/letsencrypt/pebble/pull/491#pullrequestreview-2718607820
147 148 149 |
# File 'lib/acmesmith/certificate.rb', line 147 def common_name certificate.subject.to_a.assoc('CN')&.fetch(1) end |
#export(passphrase, cipher: OpenSSL::Cipher.new('aes-256-cbc')) ⇒ CertificateExport
179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/acmesmith/certificate.rb', line 179 def export(passphrase, cipher: OpenSSL::Cipher.new('aes-256-cbc')) CertificateExport.new.tap do |h| h.certificate = certificate.to_pem h.chain = issuer_pems h.fullchain = fullchain h.private_key = if passphrase private_key.export(cipher, passphrase) else private_key.export end end end |
#fullchain ⇒ String
Returns leaf certificate + full certificate chain.
127 128 129 |
# File 'lib/acmesmith/certificate.rb', line 127 def fullchain "#{certificate.to_pem}\n#{issuer_pems}".gsub(/\n+/,?\n) end |
#issuer_pems ⇒ String
Returns issuer certificate chain.
132 133 134 |
# File 'lib/acmesmith/certificate.rb', line 132 def issuer_pems chain.map(&:to_pem).join("\n") end |
#key_passphrase=(pw) ⇒ Object
Try to decrypt private_key if encrypted.
105 106 107 108 109 110 111 112 |
# File 'lib/acmesmith/certificate.rb', line 105 def key_passphrase=(pw) raise PrivateKeyDecrypted, 'private_key already given' if @private_key @private_key = OpenSSL::PKey.read(@raw_private_key, pw) @raw_private_key = nil nil end |
#pkcs12(passphrase) ⇒ OpenSSL::PKCS12
174 175 176 |
# File 'lib/acmesmith/certificate.rb', line 174 def pkcs12(passphrase) OpenSSL::PKCS12.create(passphrase, name, private_key, certificate, chain) end |
#private_key ⇒ OpenSSL::PKey::PKey
116 117 118 119 |
# File 'lib/acmesmith/certificate.rb', line 116 def private_key return @private_key if @private_key raise PassphraseRequired, 'key_passphrase required' end |
#public_key ⇒ OpenSSL::PKey::PKey
122 123 124 |
# File 'lib/acmesmith/certificate.rb', line 122 def public_key @certificate.public_key end |
#sans ⇒ Array<String>
Returns a list of DNS subject alternative names included in the certificate. Strips DNS: prefix from returned values.
162 163 164 165 166 |
# File 'lib/acmesmith/certificate.rb', line 162 def sans all_sans.select do |san| san.start_with?('DNS:') end.map { |_| _[4..-1] } end |
#version ⇒ String
Returns Version string (consists of NotBefore time & certificate serial).
169 170 171 |
# File 'lib/acmesmith/certificate.rb', line 169 def version "#{certificate.not_before.utc.strftime('%Y%m%d-%H%M%S')}_#{certificate.serial.to_i.to_s(16)}" end |