Class: LetsencryptPlugin::CertGenerator

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ CertGenerator

Returns a new instance of CertGenerator.



23
24
25
26
# File 'lib/letsencrypt_plugin.rb', line 23

def initialize(options = {})
  @options = options
  @options.freeze
end

Instance Attribute Details

#clientObject



50
51
52
53
54
55
# File 'lib/letsencrypt_plugin.rb', line 50

def client
  @client ||= Acme::Client.new(private_key: load_private_key, endpoint: @options[:endpoint])
rescue StandardError => e
  Rails.logger.error(e.to_s)
  raise e
end

#optionsObject (readonly)

Returns the value of attribute options.



20
21
22
# File 'lib/letsencrypt_plugin.rb', line 20

def options
  @options
end

Instance Method Details

#authorize(domain = common_domain_name) ⇒ Object



102
103
104
105
# File 'lib/letsencrypt_plugin.rb', line 102

def authorize(domain = common_domain_name)
  Rails.logger.info("Sending authorization request for: #{domain}...")
  @authorization = client.authorize(domain: domain)
end

#authorize_and_handle_challenge(domains) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
# File 'lib/letsencrypt_plugin.rb', line 28

def authorize_and_handle_challenge(domains)
  result = false
  domains.each do |domain|
    authorize(domain)
    handle_challenge
    request_challenge_verification
    result = valid_verification_status
    break unless result
  end
  result
end

#common_domain_nameObject



98
99
100
# File 'lib/letsencrypt_plugin.rb', line 98

def common_domain_name
  @domain ||= @options[:domain].split(' ').first.to_s
end

#generate_certificateObject



40
41
42
43
44
45
46
47
48
# File 'lib/letsencrypt_plugin.rb', line 40

def generate_certificate
  register
  domains = @options[:domain].split(' ')
  return unless authorize_and_handle_challenge(domains)
  # We can now request a certificate
  Rails.logger.info('Creating CSR...')
  save_certificate(@client.new_certificate(Acme::Client::CertificateRequest.new(names: domains)))
  Rails.logger.info('Certificate has been generated.')
end

#handle_challengeObject



116
117
118
119
# File 'lib/letsencrypt_plugin.rb', line 116

def handle_challenge
  @challenge = @authorization.http01
  store_challenge(@challenge)
end

#load_private_keyObject



73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/letsencrypt_plugin.rb', line 73

def load_private_key
  Rails.logger.info('Loading private key...')
  if @options[:private_key_in_db].nil? || !@options[:private_key_in_db]
    private_key = open_priv_key
  else
    settings = LetsencryptPlugin::Setting.first
    raise 'Empty private_key field in settings table!' if settings.private_key.nil?
    private_key = OpenSSL::PKey::RSA.new(settings.private_key)
  end

  raise "Invalid key size: #{private_key.n.num_bits}." \
    ' Required size is between 2048 - 4096 bits' unless valid_key_size?(private_key)
  private_key
end

#open_priv_keyObject



67
68
69
70
71
# File 'lib/letsencrypt_plugin.rb', line 67

def open_priv_key
  private_key_path = privkey_path
  raise "Can not open private key: #{private_key_path}" unless File.exist?(private_key_path) && !File.directory?(private_key_path)
  OpenSSL::PKey::RSA.new(File.read(private_key_path))
end

#privkey_pathObject



61
62
63
64
65
# File 'lib/letsencrypt_plugin.rb', line 61

def privkey_path
  raise 'Private key is not set, please check your '\
    'config/letsencrypt_plugin.yml file!' if @options[:private_key].nil? || @options[:private_key].empty?
  File.join(Rails.root, @options[:private_key])
end

#registerObject



88
89
90
91
92
93
94
95
96
# File 'lib/letsencrypt_plugin.rb', line 88

def register
  Rails.logger.info('Trying to register at Let\'s Encrypt service...')
  registration = client.register(contact: "mailto:#{@options[:email]}")
  registration.agree_terms
  Rails.logger.info('Registration succeed.')
rescue => e
  Rails.logger.info("#{e.class} - #{e.message}")
  Rails.logger.info('Already registered.')
end

#request_challenge_verificationObject



121
122
123
# File 'lib/letsencrypt_plugin.rb', line 121

def request_challenge_verification
  @challenge.request_verification
end

#save_certificate(certificate) ⇒ Object

Save the certificate and key



145
146
147
148
149
150
151
152
# File 'lib/letsencrypt_plugin.rb', line 145

def save_certificate(certificate)
  begin
    return HerokuOutput.new(common_domain_name, certificate).output unless ENV['DYNO'].nil?
    output_dir = File.join(Rails.root, @options[:output_cert_dir])
    return FileOutput.new(common_domain_name, certificate, output_dir).output if File.directory?(output_dir)
    Rails.logger.error("Output directory: '#{output_dir}' does not exist!")
  end unless certificate.nil?
end

#store_challenge(challenge) ⇒ Object



107
108
109
110
111
112
113
114
# File 'lib/letsencrypt_plugin.rb', line 107

def store_challenge(challenge)
  if @options[:challenge_dir_name].nil? || @options[:challenge_dir_name].empty?
    DatabaseStore.new(challenge.file_content).store
  else
    FileStore.new(challenge.file_content, @options[:challenge_dir_name]).store
  end
  sleep(2)
end

#valid_key_size?(key) ⇒ Boolean

Returns:

  • (Boolean)


57
58
59
# File 'lib/letsencrypt_plugin.rb', line 57

def valid_key_size?(key)
  key.n.num_bits >= 2048 && key.n.num_bits <= 4096
end

#valid_verification_statusObject



134
135
136
137
138
139
140
141
142
# File 'lib/letsencrypt_plugin.rb', line 134

def valid_verification_status
  wait_for_status(@challenge)
  begin
    Rails.logger.error('Challenge verification failed! ' \
      "Error: #{@challenge.error['type']}: #{@challenge.error['detail']}")
    return false
  end unless @challenge.verify_status == 'valid'
  true
end

#wait_for_status(challenge) ⇒ Object



125
126
127
128
129
130
131
132
# File 'lib/letsencrypt_plugin.rb', line 125

def wait_for_status(challenge)
  Rails.logger.info('Waiting for challenge status...')
  counter = 0
  while challenge.verify_status == 'pending' && counter < 10
    sleep(1)
    counter += 1
  end
end