Class: Acmesmith::Certificate

Inherits:
Object
  • Object
show all
Defined in:
lib/acmesmith/certificate.rb

Defined Under Namespace

Classes: CertificateExport, PassphraseRequired, PrivateKeyDecrypted

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(certificate, chain, private_key, key_passphrase = nil, csr = nil) ⇒ Certificate



31
32
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
# File 'lib/acmesmith/certificate.rb', line 31

def initialize(certificate, chain, private_key, key_passphrase = nil, csr = nil)
  @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

#certificateOpenSSL::X509::Certificate (readonly)



91
92
93
# File 'lib/acmesmith/certificate.rb', line 91

def certificate
  @certificate
end

#chainArray<OpenSSL::X509::Certificate> (readonly)



93
94
95
# File 'lib/acmesmith/certificate.rb', line 93

def chain
  @chain
end

#csrOpenSSL::X509::Request (readonly)



95
96
97
# File 'lib/acmesmith/certificate.rb', line 95

def csr
  @csr
end

Class Method Details

.by_issuance(pem_chain, csr) ⇒ Acmesmith::Certificate

Return Acmesmith::Certificate by an issued certificate



21
22
23
24
# File 'lib/acmesmith/certificate.rb', line 21

def self.by_issuance(pem_chain, csr)
  pems = split_pems(pem_chain)
  new(pems[0], pems[1..-1], csr.private_key, nil, csr)
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

#common_nameString



132
133
134
# File 'lib/acmesmith/certificate.rb', line 132

def common_name
  certificate.subject.to_a.assoc('CN')[1]
end

#export(passphrase, cipher: OpenSSL::Cipher.new('aes-256-cbc')) ⇒ CertificateExport



154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/acmesmith/certificate.rb', line 154

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

#fullchainString



122
123
124
# File 'lib/acmesmith/certificate.rb', line 122

def fullchain
  "#{certificate.to_pem}\n#{issuer_pems}".gsub(/\n+/,?\n)
end

#issuer_pemsString



127
128
129
# File 'lib/acmesmith/certificate.rb', line 127

def issuer_pems
  chain.map(&:to_pem).join("\n")
end

#key_passphrase=(pw) ⇒ Object

Try to decrypt private_key if encrypted.

Raises:



100
101
102
103
104
105
106
107
# File 'lib/acmesmith/certificate.rb', line 100

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



149
150
151
# File 'lib/acmesmith/certificate.rb', line 149

def pkcs12(passphrase)
  OpenSSL::PKCS12.create(passphrase, common_name, private_key, certificate, chain)
end

#private_keyOpenSSL::PKey::PKey

Raises:



111
112
113
114
# File 'lib/acmesmith/certificate.rb', line 111

def private_key
  return @private_key if @private_key
  raise PassphraseRequired, 'key_passphrase required'
end

#public_keyOpenSSL::PKey::PKey



117
118
119
# File 'lib/acmesmith/certificate.rb', line 117

def public_key
  @certificate.public_key
end

#sansArray<String>



137
138
139
140
141
# File 'lib/acmesmith/certificate.rb', line 137

def sans
  certificate.extensions.select { |_| _.oid == 'subjectAltName' }.flat_map do |ext|
    ext.value.split(/,\s*/).select { |_| _.start_with?('DNS:') }.map { |_| _[4..-1] }
  end
end

#versionString



144
145
146
# File 'lib/acmesmith/certificate.rb', line 144

def version
  "#{certificate.not_before.utc.strftime('%Y%m%d-%H%M%S')}_#{certificate.serial.to_i.to_s(16)}"
end