class AcmeWrapper
Public Class Methods
new(options)
click to toggle source
# File lib/letsencrypt/cli/acme_wrapper.rb, line 8 def initialize(options) @options = options if !@options[:color] String.disable_colorization = true end end
Public Instance Methods
cert(domains)
click to toggle source
# 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)
click to toggle source
# 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
client()
click to toggle source
# File lib/letsencrypt/cli/acme_wrapper.rb, line 25 def client @client ||= Acme::Client.new(private_key: account_key, directory: directory) end
create_order(domains)
click to toggle source
# 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)
click to toggle source
# 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)
click to toggle source
# 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
Private Instance Methods
account_key()
click to toggle source
# File lib/letsencrypt/cli/acme_wrapper.rb, line 174 def account_key @account_key ||= find_or_create_pkey(@options[:account_key], "account key", @options[:key_length] || 4096) end
certificate_exists_and_valid_and_all_domains_included?(domains)
click to toggle source
# File lib/letsencrypt/cli/acme_wrapper.rb, line 141 def certificate_exists_and_valid_and_all_domains_included?(domains) return false if !File.exists?(@options[:certificate_path]) cert = OpenSSL::X509::Certificate.new(File.read(@options[:certificate_path])) domains_in_cert = cert.extensions.map(&:to_h).select{|i| i['oid'] == 'subjectAltName' }.map{|i| i['value']}.join(', ').split(/, */).map{|i| i.sub(/^DNS:/, '') } + [ cert.subject.to_s.sub(%r{/CN=}, '') ].uniq.sort missing_domains = domains.sort.uniq - domains_in_cert if missing_domains != [] log "Certificate '#{@options[:certificate_path]}' missing domains #{missing_domains.join(' ')}. Existing: #{domains_in_cert.join(' ')}", :warn return false end expires_on = cert.not_after.to_date if expires_on <= Date.today log "Certificate '#{@options[:certificate_path]}' has expired on #{expires_on}.", :warn return false end renew_on = expires_on - @options[:days_valid] if renew_on > Date.today log "Certificate '#{@options[:certificate_path]}' still valid till #{cert.not_after.to_date}.", :warn log "Won't renew until #{renew_on} (#{@options[:days_valid]} days before)", :warn exit 2 end false end
directory()
click to toggle source
# File lib/letsencrypt/cli/acme_wrapper.rb, line 166 def directory if @options[:test] "https://acme-staging-v02.api.letsencrypt.org/directory" else "https://acme-v02.api.letsencrypt.org/directory" end end
find_or_create_pkey(file_path, name, length)
click to toggle source
# File lib/letsencrypt/cli/acme_wrapper.rb, line 178 def find_or_create_pkey(file_path, name, length) if File.exists?(file_path) log "existing account key found" OpenSSL::PKey::RSA.new File.read file_path else log "creating new private key to #{file_path}..." private_key = OpenSSL::PKey::RSA.new(length) File.write(file_path, private_key.to_s) File.chmod(0400, file_path) private_key end end