summaryrefslogtreecommitdiff
path: root/vendor/acme-client/lib/acme/client/certificate_request.rb
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/acme-client/lib/acme/client/certificate_request.rb')
-rw-r--r--vendor/acme-client/lib/acme/client/certificate_request.rb111
1 files changed, 111 insertions, 0 deletions
diff --git a/vendor/acme-client/lib/acme/client/certificate_request.rb b/vendor/acme-client/lib/acme/client/certificate_request.rb
new file mode 100644
index 0000000..8eae0c6
--- /dev/null
+++ b/vendor/acme-client/lib/acme/client/certificate_request.rb
@@ -0,0 +1,111 @@
+class Acme::Client::CertificateRequest
+ extend Forwardable
+
+ DEFAULT_KEY_LENGTH = 2048
+ DEFAULT_DIGEST = OpenSSL::Digest::SHA256
+ SUBJECT_KEYS = {
+ common_name: 'CN',
+ country_name: 'C',
+ organization_name: 'O',
+ organizational_unit: 'OU',
+ state_or_province: 'ST',
+ locality_name: 'L'
+ }.freeze
+
+ SUBJECT_TYPES = {
+ 'CN' => OpenSSL::ASN1::UTF8STRING,
+ 'C' => OpenSSL::ASN1::UTF8STRING,
+ 'O' => OpenSSL::ASN1::UTF8STRING,
+ 'OU' => OpenSSL::ASN1::UTF8STRING,
+ 'ST' => OpenSSL::ASN1::UTF8STRING,
+ 'L' => OpenSSL::ASN1::UTF8STRING
+ }.freeze
+
+ attr_reader :private_key, :common_name, :names, :subject
+
+ def_delegators :csr, :to_pem, :to_der
+
+ def initialize(common_name: nil, names: [], private_key: generate_private_key, subject: {}, digest: DEFAULT_DIGEST.new)
+ @digest = digest
+ @private_key = private_key
+ @subject = normalize_subject(subject)
+ @common_name = common_name || @subject[SUBJECT_KEYS[:common_name]] || @subject[:common_name]
+ @names = names.to_a.dup
+ normalize_names
+ @subject[SUBJECT_KEYS[:common_name]] ||= @common_name
+ validate_subject
+ end
+
+ def csr
+ @csr ||= generate
+ end
+
+ private
+
+ def generate_private_key
+ OpenSSL::PKey::RSA.new(DEFAULT_KEY_LENGTH)
+ end
+
+ def normalize_subject(subject)
+ @subject = subject.each_with_object({}) do |(key, value), hash|
+ hash[SUBJECT_KEYS.fetch(key, key)] = value.to_s
+ end
+ end
+
+ def normalize_names
+ if @common_name
+ @names.unshift(@common_name) unless @names.include?(@common_name)
+ else
+ raise ArgumentError, 'No common name and no list of names given' if @names.empty?
+ @common_name = @names.first
+ end
+ end
+
+ def validate_subject
+ validate_subject_attributes
+ validate_subject_common_name
+ end
+
+ def validate_subject_attributes
+ extra_keys = @subject.keys - SUBJECT_KEYS.keys - SUBJECT_KEYS.values
+ return if extra_keys.empty?
+ raise ArgumentError, "Unexpected subject attributes given: #{extra_keys.inspect}"
+ end
+
+ def validate_subject_common_name
+ return if @common_name == @subject[SUBJECT_KEYS[:common_name]]
+ raise ArgumentError, 'Conflicting common name given in arguments and subject'
+ end
+
+ def generate
+ OpenSSL::X509::Request.new.tap do |csr|
+ csr.public_key = @private_key.public_key
+ csr.subject = generate_subject
+ csr.version = 2
+ add_extension(csr)
+ csr.sign @private_key, @digest
+ end
+ end
+
+ def generate_subject
+ OpenSSL::X509::Name.new(
+ @subject.map {|name, value|
+ [name, value, SUBJECT_TYPES[name]]
+ }
+ )
+ end
+
+ def add_extension(csr)
+ return if @names.size <= 1
+
+ extension = OpenSSL::X509::ExtensionFactory.new.create_extension(
+ 'subjectAltName', @names.map { |name| "DNS:#{name}" }.join(', '), false
+ )
+ csr.add_attribute(
+ OpenSSL::X509::Attribute.new(
+ 'extReq',
+ OpenSSL::ASN1::Set.new([OpenSSL::ASN1::Sequence.new([extension])])
+ )
+ )
+ end
+end