Class: Acme::PKI

Inherits:
Object
  • Object
show all
Includes:
Information
Defined in:
lib/acme/pki.rb,
lib/acme/pki/version.rb,
lib/acme/pki/information.rb

Defined Under Namespace

Modules: Information

Constant Summary collapse

DEFAULT_ENDPOINT =
ENV['ACME_ENDPOINT'] || 'https://acme-v01.api.letsencrypt.org/'
DEFAULT_DIRECTORY =
ENV['ACME_DIRECTORY'] || Dir.pwd
DEFAULT_ACCOUNT_KEY =
ENV['ACME_ACCOUNT_KEY'] || 'account.key'
DEFAULT_KEY =
[:ecc, 'prime256v1'].freeze
DEFAULT_RENEW_DURATION =

1 month

60 * 60 * 24 * 30
VERSION =
'0.1.4'

Instance Method Summary collapse

Methods included from Information

#certifificate_info, #chain_info, #key_info

Constructor Details

#initialize(directory: DEFAULT_DIRECTORY, account_key: DEFAULT_ACCOUNT_KEY, endpoint: DEFAULT_ENDPOINT) ⇒ PKI

Returns a new instance of PKI.



25
26
27
28
29
30
31
32
33
34
35
# File 'lib/acme/pki.rb', line 25

def initialize(directory: DEFAULT_DIRECTORY, account_key: , endpoint: DEFAULT_ENDPOINT)
  @directory        = directory
  @challenge_dir    = ENV['ACME_CHALLENGE'] || File.join(@directory, 'acme-challenge')
  @account_key_file = File.join @directory, 
  @account_key      = if File.exists? @account_key_file
              open(@account_key_file, 'r') { |f| OpenSSL::PKey.read f }
            else
              nil
            end
  @endpoint         = endpoint
end

Instance Method Details

#crt(name) ⇒ Object



45
46
47
# File 'lib/acme/pki.rb', line 45

def crt(name)
  file name, 'crt'
end

#csr(name) ⇒ Object



41
42
43
# File 'lib/acme/pki.rb', line 41

def csr(name)
  file name, 'csr'
end

#generate_crt(crt, csr: nil) ⇒ Object



122
123
124
125
126
127
128
129
# File 'lib/acme/pki.rb', line 122

def generate_crt(crt, csr: nil)
  csr       = crt unless csr
  short_csr = csr
  crt       = self.crt crt
  csr       = self.csr csr
  generate_csr short_csr unless File.exist? csr
  internal_generate_crt crt, csr: csr
end

#generate_csr(csr, domains: [], key: nil) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/acme/pki.rb', line 83

def generate_csr(csr, domains: [], key: nil)
  key      = csr unless key
  domains  = [csr, *domains].collect { |d| SimpleIDN.to_ascii d }
  csr_file = self.csr csr
  key_file = self.key key

  generate_key key unless File.exist? key_file

  process "Generating CSR for #{domains.join ', '} with key #{key_file} into #{csr_file}" do
    key_file    = open(key_file, 'r') { |f| OpenSSL::PKey.read f }
    csr         = OpenSSL::X509::Request.new
    csr.subject = OpenSSL::X509::Name.parse "/CN=#{domains.first}"

    public_key     = case key_file
               when OpenSSL::PKey::EC
                 curve             = key_file.group.curve_name
                 public            = OpenSSL::PKey::EC.new curve
                 public.public_key = key_file.public_key
                 public
               else
                 key_file.public_key
             end
    csr.public_key = public_key

    factory    = OpenSSL::X509::ExtensionFactory.new
    extensions = []
    #extensions << factory.create_extension('basicConstraints', 'CA:FALSE', true)
    extensions << factory.create_extension('keyUsage', 'digitalSignature,nonRepudiation,keyEncipherment')
    extensions << factory.create_extension('subjectAltName', domains.collect { |d| "DNS:#{d}" }.join(', '))

    extensions = OpenSSL::ASN1::Sequence extensions
    extensions = OpenSSL::ASN1::Set [extensions]
    csr.add_attribute OpenSSL::X509::Attribute.new 'extReq', extensions

    csr.sign key_file, OpenSSL::Digest::SHA512.new
    open(csr_file, 'w') { |f| f.write csr.to_pem }
  end
end

#generate_key(name, type: DEFAULT_KEY) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/acme/pki.rb', line 61

def generate_key(name, type: DEFAULT_KEY)
  key_file   = self.key name
  type, size = type

  key = case type
        when :rsa
          process "Generating RSA #{size} bits private key into #{key_file}" do
            key = OpenSSL::PKey::RSA.new size
            open(key_file, 'w') { |f| f.write key.to_pem }
            key
          end
        when :ecc
          process "Generating ECC #{size} curve private key into #{key_file}" do
            key = OpenSSL::PKey::EC.new(size).generate_key
            open(key_file, 'w') { |f| f.write key.to_pem }
            key
          end
      end

  key_info key
end

#key(name) ⇒ Object



37
38
39
# File 'lib/acme/pki.rb', line 37

def key(name)
  file name, 'pem'
end

#register(mail) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
# File 'lib/acme/pki.rb', line 49

def register(mail)
  process("Generating RSA 4096 bits account key into #{@account_key_file}") do
    @account_key = OpenSSL::PKey::RSA.new 4096
    File.write @account_key_file, @account_key.to_pem
  end

  process("Registering account key #{@account_key_file}") do
    registration = client.register contact: "mailto:#{mail}"
    registration.agree_terms
  end
end

#renew(crt, csr: nil, duration: DEFAULT_RENEW_DURATION) ⇒ Object



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/acme/pki.rb', line 131

def renew(crt, csr: nil, duration: DEFAULT_RENEW_DURATION)
  csr = crt unless csr
  crt = self.crt crt
  csr = self.csr csr
  puts "Renewing #{crt} CRT from #{csr} CSR"

  if File.exists? crt
    x509  = OpenSSL::X509::Certificate.new File.read crt
    delay = x509.not_after - Time.now
    if delay > duration
      puts "No need to renew (#{humanize delay})"
      return false
    end
  end

  internal_generate_crt crt, csr: csr
  true
end