class Acme::PKI
Constants
- DEFAULT_ACCOUNT_KEY
- DEFAULT_ACCOUNT_KEY_TYPE
- DEFAULT_DIRECTORY
- DEFAULT_ENDPOINT
- DEFAULT_KEY_TYPE
- DEFAULT_RENEW_DURATION
- VERSION
Public Class Methods
new(directory: DEFAULT_DIRECTORY, account_key: DEFAULT_ACCOUNT_KEY, endpoint: DEFAULT_ENDPOINT)
click to toggle source
# File lib/acme/pki.rb, line 31 def initialize(directory: DEFAULT_DIRECTORY, account_key: DEFAULT_ACCOUNT_KEY, endpoint: DEFAULT_ENDPOINT) @directory = directory @challenge_dir = ENV['ACME_CHALLENGE'] || File.join(@directory, 'acme-challenge') @account_key_file = self.key DEFAULT_ACCOUNT_KEY, 'key' @account_key = if File.exists? @account_key_file open(@account_key_file, 'r') { |f| OpenSSL::PKey.read f } else nil end @endpoint = endpoint end
Public Instance Methods
client()
click to toggle source
# File lib/acme/pki.rb, line 166 def client unless @account_key puts 'No account key available'.colorize :yellow puts 'Please register yourself before'.colorize :red exit -1 end @client ||= Acme::Client.new private_key: @account_key, directory: @endpoint end
crt(name)
click to toggle source
# File lib/acme/pki.rb, line 51 def crt(name) self.file name, 'crt' end
csr(name)
click to toggle source
# File lib/acme/pki.rb, line 47 def csr(name) self.file name, 'csr' end
domains(csr)
click to toggle source
# File lib/acme/pki.rb, line 196 def domains(csr) domains = [] cn = csr.subject.to_a.first { |n, _, _| n == 'CN' } domains << cn[1] if cn attribute = csr.attributes.detect { |a| %w(extReq msExtReq).include? a.oid } if attribute set = OpenSSL::ASN1.decode attribute.value seq = set.value.first sans = seq.value.collect { |s| OpenSSL::X509::Extension.new(s).to_a } .detect { |e| e.first == 'subjectAltName' } if sans sans = sans[1] sans = sans.split(/\s*,\s*/) .collect { |s| s.split /\s*:\s*/ } .select { |t, _| t == 'DNS' } .collect { |_, v| v } domains.concat sans end end domains.uniq end
file(name, extension = nil)
click to toggle source
# File lib/acme/pki.rb, line 189 def file(name, extension = nil) return nil unless name name = name.split('.').reverse.join('.') name = "#{name}.#{extension}" if extension File.join @directory, name end
generate_crt(crt, csr: nil)
click to toggle source
# File lib/acme/pki.rb, line 138 def generate_crt(crt, csr: nil) csr = crt unless csr short_csr = csr crt = self.crt crt csr = self.csr csr self.generate_csr short_csr unless File.exist? csr self.internal_generate_crt crt, csr: csr end
generate_csr(csr, domains: [], add: [], remove: [], key: nil)
click to toggle source
# File lib/acme/pki.rb, line 92 def generate_csr(csr, domains: [], add: [], remove: [], key: nil) key = csr unless key csr_file = self.csr csr key_file = self.key key self.generate_key key unless File.exist? key_file domains = if add.empty? && remove.empty? [csr, *domains] else tmp = OpenSSL::X509::Request.new File.read csr_file domains = self.domains tmp domains - remove + add end.collect { |d| SimpleIDN.to_ascii d } self.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, extension = 'pem', type: DEFAULT_KEY_TYPE)
click to toggle source
# File lib/acme/pki.rb, line 67 def generate_key(name, extension = 'pem', type: DEFAULT_KEY_TYPE) key_file = self.key name, extension type, size = type log = case type when :rsa "RSA #{size} bits" when :ecc "ECC #{size} curve" end key = self.process "Generating #{log} private key into #{key_file}" do key = case type when :rsa OpenSSL::PKey::RSA.new size when :ecc OpenSSL::PKey::EC.new(size).generate_key end open(key_file, 'w') { |f| f.write key.to_pem } key end self.key_info key [key_file, key] end
humanize(secs)
click to toggle source
# File lib/acme/pki.rb, line 294 def humanize(secs) [[60, :seconds], [60, :minutes], [24, :hours], [30, :days], [12, :months]].map { |count, name| if secs > 0 secs, n = secs.divmod count "#{n.to_i} #{name}" end }.compact.reverse.join(' ') end
internal_generate_crt(crt, csr: nil)
click to toggle source
# File lib/acme/pki.rb, line 275 def internal_generate_crt(crt, csr: nil) csr = crt unless csr csr_file = csr csr = OpenSSL::X509::Request.new File.read csr domains = self.domains csr order = client.new_order identifiers: domains order.authorizations.each { |a| self.authorize a } crt = self.process "Generating CRT #{crt} from CSR #{csr_file}" do order.finalize csr: csr certificate = order.certificate File.write crt, certificate OpenSSL::X509::Certificate.new certificate end self.certifificate_info crt end
key(name, extension = 'pem')
click to toggle source
# File lib/acme/pki.rb, line 43 def key(name, extension = 'pem') self.file name, extension end
process(line, io: STDOUT) { || ... }
click to toggle source
# File lib/acme/pki.rb, line 175 def process(line, io: STDOUT) io.print "#{line}..." io.flush result = yield io.puts " [#{'OK'.colorize :green}]" result rescue Exception io.puts " [#{'KO'.colorize :red}]" raise end
register(mail)
click to toggle source
# File lib/acme/pki.rb, line 55 def register(mail) @account_key_file, @account_key = self.generate_key DEFAULT_ACCOUNT_KEY, 'key', type: DEFAULT_ACCOUNT_KEY_TYPE tos = self.client.meta['termsOfService'] $stdout.print "Are you agree with Let's Encrypt terms of service available at #{tos}? [yN] " $stdout.flush accept = $stdin.gets.chomp.downcase == 'y' exit unless accept self.process("Registering account key #{@account_key_file}") do self.client.new_account contact: "mailto:#{mail}", terms_of_service_agreed: accept end end
renew(crt, csr: nil, duration: DEFAULT_RENEW_DURATION)
click to toggle source
# File lib/acme/pki.rb, line 147 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 self.internal_generate_crt crt, csr: csr true end