Class: AcmeWrapper

Inherits:
Object
  • Object
show all
Defined in:
lib/letsencrypt/cli/acme_wrapper.rb

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ AcmeWrapper

Returns a new instance of AcmeWrapper.



8
9
10
11
12
13
# File 'lib/letsencrypt/cli/acme_wrapper.rb', line 8

def initialize(options)
  @options = options
  if !@options[:color]
    String.disable_colorization = true
  end
end

Instance Method Details

#authorize(authorization) ⇒ Object



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
# File 'lib/letsencrypt/cli/acme_wrapper.rb', line 34

def authorize(authorization)
  domain = authorization.domain
  FileUtils.mkdir_p(@options[:webroot_path])
  log "Authorizing #{domain.blue}.."
  challenge = authorization.http

  challenge_file = File.join(@options[:webroot_path], challenge.filename.split('/').last)
  log "Writing challenge to #{challenge_file}", :debug
  File.write(challenge_file, challenge.file_content)

  challenge.request_validation

  10.times do
    log "Checking verification...", :debug
    sleep 1
    challenge.reload
    break if challenge.status != 'pending'
  end

  if challenge.status == 'valid'
    log "Authorization successful for #{domain.green}"
    File.unlink(challenge_file)
    true
  else
    log "Authorization error for #{domain.red}", :error
    log challenge.error['detail']
    false
  end
end

#cert(domains) ⇒ Object



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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/letsencrypt/cli/acme_wrapper.rb', line 64

def cert(domains)
  return if certificate_exists_and_valid_and_all_domains_included?(domains)
  csr = OpenSSL::X509::Request.new
  certificate_private_key = find_or_create_pkey(@options[:private_key_path], "private key", @options[:key_length] || 2048)

  csr.subject = OpenSSL::X509::Name.new([
    # ['C',             options[:country], OpenSSL::ASN1::PRINTABLESTRING],
    # ['ST',            options[:state],        OpenSSL::ASN1::PRINTABLESTRING],
    # ['L',             options[:city],         OpenSSL::ASN1::PRINTABLESTRING],
    # ['O',             options[:organization], OpenSSL::ASN1::UTF8STRING],
    # ['OU',            options[:department],   OpenSSL::ASN1::UTF8STRING],
    # ['CN',            options[:common_name],  OpenSSL::ASN1::UTF8STRING],
    # ['emailAddress',  options[:email],        OpenSSL::ASN1::UTF8STRING]
    ['CN', domains.first, OpenSSL::ASN1::UTF8STRING]
  ])
  if domains.count > 1
    ef = OpenSSL::X509::ExtensionFactory.new
    exts = [ ef.create_extension( "subjectAltName", domains.map{|domain| "DNS:#{domain}"}.join(','), false  ) ]
    attrval = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(exts)])
    attrs = [
      OpenSSL::X509::Attribute.new('extReq', attrval),
      OpenSSL::X509::Attribute.new('msExtReq', attrval),
    ]
    attrs.each do |attr|
      csr.add_attribute(attr)
    end
  end
  csr.version = 2
  csr.public_key = certificate_private_key.public_key
  csr.sign(certificate_private_key, OpenSSL::Digest::SHA256.new)
  order = create_order(domains)
  order.finalize(csr: csr)
  while order.status == 'processing'
    sleep(1)
    order.reload
  end
  certificate = Certificate.new(order.certificate)
  File.write(@options[:fullchain_path], certificate.fullchain_to_pem)
  File.write(@options[:chain_path], certificate.chain_to_pem)
  File.write(@options[:certificate_path], certificate.to_pem)
  log "Certificate successfully created to #{@options[:fullchain_path]} #{@options[:chain_path]} and #{@options[:certificate_path]}!".green
  log "Certificate valid until: #{certificate.x509.not_after}"
end

#check_certificate(path) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/letsencrypt/cli/acme_wrapper.rb', line 108

def check_certificate(path)
  unless File.exists?(path)
    log "Certificate #{path} does not exists", :warn
    return false
  end
  cert = OpenSSL::X509::Certificate.new(File.read(path))
  renew_on = cert.not_after.to_date - @options[:days_valid]
  log "Certificate '#{path}' valid until #{cert.not_after.to_date}.", :info
  if Date.today >= renew_on
    log "Certificate '#{path}' should be renewed!", :warn
    return false
  else
    true
  end
end

#clientObject



25
26
27
# File 'lib/letsencrypt/cli/acme_wrapper.rb', line 25

def client
  @client ||= Acme::Client.new(private_key: , directory: directory)
end

#create_order(domains) ⇒ Object



29
30
31
32
# File 'lib/letsencrypt/cli/acme_wrapper.rb', line 29

def create_order(domains)
  log "Creating order for domains #{domains.to_s.blue}"
  return client.new_order(identifiers: domains)
end

#log(message, severity = :info) ⇒ Object



15
16
17
18
19
20
21
22
23
# File 'lib/letsencrypt/cli/acme_wrapper.rb', line 15

def log(message, severity=:info)
  @logger ||= Logger.new(STDOUT).tap {|logger|
    logger.level = Logger::SEV_LABEL.index(@options[:log_level].upcase)
    logger.formatter = proc do |sev, datetime, progname, msg|
      "#{datetime.to_s.light_black}: #{msg}\n"
    end
  }
  @logger.send(severity, message)
end

#revoke_certificate(path) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/letsencrypt/cli/acme_wrapper.rb', line 124

def revoke_certificate(path)
  unless File.exists?(path)
    log "Certificate #{path} does not exists", :warn
    return false
  end
  cert = OpenSSL::X509::Certificate.new(File.read(path))
  if client.revoke(certificate: cert)
    log "Certificate '#{path}' was revoked", :info
  end
  true
rescue Acme::Client::Error::Malformed => e
  log e.message, :error
  return false
end