feat: SMIME signed notification emails
- Add mail interceptor the signs outgoing email with SMIME - Add lib and helpers to work with SMIME data - New configuration params for setting up SMIME key and cert files
This commit is contained in:
parent
d8966abd20
commit
0dcb9d21ef
|
@ -75,6 +75,8 @@ eslint-report.html
|
|||
/.rspec
|
||||
/plugins/*
|
||||
/.gitlab_pages_secret
|
||||
/.gitlab_smime_key
|
||||
/.gitlab_smime_cert
|
||||
package-lock.json
|
||||
/junit_*.xml
|
||||
/coverage-frontend/
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Notification emails can be signed with SMIME
|
||||
merge_request: 30644
|
||||
author: Diego Louzán
|
||||
type: added
|
|
@ -95,6 +95,15 @@ production: &base
|
|||
email_display_name: GitLab
|
||||
email_reply_to: noreply@example.com
|
||||
email_subject_suffix: ''
|
||||
email_smime:
|
||||
# Uncomment and set to true if you need to enable email S/MIME signing (default: false)
|
||||
# enabled: false
|
||||
# S/MIME private key file in PEM format, unencrypted
|
||||
# Default is '.gitlab_smime_key' relative to Rails.root (i.e. root of the GitLab app).
|
||||
# key_file: /home/git/gitlab/.gitlab_smime_key
|
||||
# S/MIME public certificate key in PEM format, will be attached to signed messages
|
||||
# Default is '.gitlab_smime_cert' relative to Rails.root (i.e. root of the GitLab app).
|
||||
# cert_file: /home/git/gitlab/.gitlab_smime_cert
|
||||
|
||||
# Email server smtp settings are in config/initializers/smtp_settings.rb.sample
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
require_relative '../settings'
|
||||
require_relative '../object_store_settings'
|
||||
require_relative '../smime_signature_settings'
|
||||
|
||||
# Default settings
|
||||
Settings['ldap'] ||= Settingslogic.new({})
|
||||
|
@ -171,6 +172,7 @@ Settings.gitlab['email_from'] ||= ENV['GITLAB_EMAIL_FROM'] || "gitlab@#{Settings
|
|||
Settings.gitlab['email_display_name'] ||= ENV['GITLAB_EMAIL_DISPLAY_NAME'] || 'GitLab'
|
||||
Settings.gitlab['email_reply_to'] ||= ENV['GITLAB_EMAIL_REPLY_TO'] || "noreply@#{Settings.gitlab.host}"
|
||||
Settings.gitlab['email_subject_suffix'] ||= ENV['GITLAB_EMAIL_SUBJECT_SUFFIX'] || ""
|
||||
Settings.gitlab['email_smime'] = SmimeSignatureSettings.parse(Settings.gitlab['email_smime'])
|
||||
Settings.gitlab['base_url'] ||= Settings.__send__(:build_base_gitlab_url)
|
||||
Settings.gitlab['url'] ||= Settings.__send__(:build_gitlab_url)
|
||||
Settings.gitlab['user'] ||= 'git'
|
||||
|
|
|
@ -10,3 +10,8 @@ ActionMailer::Base.register_interceptors(
|
|||
)
|
||||
|
||||
ActionMailer::Base.register_observer(::Gitlab::Email::Hook::DeliveryMetricsObserver)
|
||||
|
||||
if Gitlab.config.gitlab.email_enabled && Gitlab.config.gitlab.email_smime.enabled
|
||||
ActionMailer::Base.register_interceptor(::Gitlab::Email::Hook::SmimeSignatureInterceptor)
|
||||
Gitlab::AppLogger.debug "S/MIME signing of outgoing emails enabled"
|
||||
end
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# Set default values for email_smime settings
|
||||
class SmimeSignatureSettings
|
||||
def self.parse(email_smime)
|
||||
email_smime ||= Settingslogic.new({})
|
||||
email_smime['enabled'] = false unless email_smime['enabled']
|
||||
email_smime['key_file'] ||= Rails.root.join('.gitlab_smime_key')
|
||||
email_smime['cert_file'] ||= Rails.root.join('.gitlab_smime_cert')
|
||||
|
||||
email_smime
|
||||
end
|
||||
end
|
|
@ -64,6 +64,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
|
|||
- [External Classification Policy Authorization](../user/admin_area/settings/external_authorization.md) **(PREMIUM ONLY)**
|
||||
- [Upload a license](../user/admin_area/license.md): Upload a license to unlock features that are in paid tiers of GitLab. **(STARTER ONLY)**
|
||||
- [Admin Area](../user/admin_area/index.md): for self-managed instance-wide configuration and maintenance.
|
||||
- [S/MIME Signing](smime_signing_email.md): how to sign all outgoing notification emails with S/MIME
|
||||
|
||||
#### Customizing GitLab's appearance
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
# Signing outgoing email with S/MIME
|
||||
|
||||
Notification emails sent by Gitlab can be signed with S/MIME for improved
|
||||
security.
|
||||
|
||||
> **Note:**
|
||||
Please be aware that S/MIME certificates and TLS/SSL certificates are not the
|
||||
same and are used for different purposes: TLS creates a secure channel, whereas
|
||||
S/MIME signs and/or encrypts the message itself
|
||||
|
||||
## Enable S/MIME signing
|
||||
|
||||
This setting must be explicitly enabled and a single pair of key and certificate
|
||||
files must be provided in `gitlab.rb` or `gitlab.yml` if you are using Omnibus
|
||||
GitLab or installed GitLab from source respectively:
|
||||
|
||||
```yaml
|
||||
email_smime:
|
||||
enabled: true
|
||||
key_file: /etc/pki/smime/private/gitlab.key
|
||||
cert_file: /etc/pki/smime/certs/gitlab.crt
|
||||
```
|
||||
|
||||
- Both files must be provided PEM-encoded.
|
||||
- The key file must be unencrypted so that Gitlab can read it without user
|
||||
intervention.
|
||||
|
||||
NOTE: **Note:** Be mindful of the access levels for your private keys and visibility to
|
||||
third parties.
|
||||
|
||||
### How to convert S/MIME PKCS#12 / PFX format to PEM encoding
|
||||
|
||||
Typically S/MIME certificates are handled in binary PKCS#12 format (`.pfx` or `.p12`
|
||||
extensions), which contain the following in a single encrypted file:
|
||||
|
||||
- Server certificate
|
||||
- Intermediate certificates (if any)
|
||||
- Private key
|
||||
|
||||
In order to export the required files in PEM encoding from the PKCS#12 file,
|
||||
the `openssl` command can be used:
|
||||
|
||||
```bash
|
||||
#-- Extract private key in PEM encoding (no password, unencrypted)
|
||||
$ openssl pkcs12 -in gitlab.p12 -nocerts -nodes -out gitlab.key
|
||||
|
||||
#-- Extract certificates in PEM encoding (full certs chain including CA)
|
||||
$ openssl pkcs12 -in gitlab.p12 -nokeys -out gitlab.crt
|
||||
```
|
|
@ -5,6 +5,10 @@
|
|||
To view rendered emails "sent" in your development instance, visit
|
||||
[`/rails/letter_opener`](http://localhost:3000/rails/letter_opener).
|
||||
|
||||
Please note that [S/MIME signed](../administration/smime_signing_email.md) emails
|
||||
[cannot be currently previewed](https://github.com/fgrehm/letter_opener_web/issues/96) with
|
||||
`letter_opener`.
|
||||
|
||||
## Mailer previews
|
||||
|
||||
Rails provides a way to preview our mailer templates in HTML and plaintext using
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Email
|
||||
module Hook
|
||||
class SmimeSignatureInterceptor
|
||||
# Sign emails with SMIME if enabled
|
||||
class << self
|
||||
def delivering_email(message)
|
||||
signed_message = Gitlab::Email::Smime::Signer.sign(
|
||||
cert: certificate.cert,
|
||||
key: certificate.key,
|
||||
data: message.encoded)
|
||||
signed_email = Mail.new(signed_message)
|
||||
|
||||
overwrite_body(message, signed_email)
|
||||
overwrite_headers(message, signed_email)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def certificate
|
||||
@certificate ||= Gitlab::Email::Smime::Certificate.from_files(key_path, cert_path)
|
||||
end
|
||||
|
||||
def key_path
|
||||
Gitlab.config.gitlab.email_smime.key_file
|
||||
end
|
||||
|
||||
def cert_path
|
||||
Gitlab.config.gitlab.email_smime.cert_file
|
||||
end
|
||||
|
||||
def overwrite_body(message, signed_email)
|
||||
# since this is a multipart email, assignment to nil is important,
|
||||
# otherwise Message#body will add a new mail part
|
||||
message.body = nil
|
||||
message.body = signed_email.body.encoded
|
||||
end
|
||||
|
||||
def overwrite_headers(message, signed_email)
|
||||
message.content_disposition = signed_email.content_disposition
|
||||
message.content_transfer_encoding = signed_email.content_transfer_encoding
|
||||
message.content_type = signed_email.content_type
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Email
|
||||
module Smime
|
||||
class Certificate
|
||||
include OpenSSL
|
||||
|
||||
attr_reader :key, :cert
|
||||
|
||||
def key_string
|
||||
@key.to_s
|
||||
end
|
||||
|
||||
def cert_string
|
||||
@cert.to_pem
|
||||
end
|
||||
|
||||
def self.from_strings(key_string, cert_string)
|
||||
key = PKey::RSA.new(key_string)
|
||||
cert = X509::Certificate.new(cert_string)
|
||||
new(key, cert)
|
||||
end
|
||||
|
||||
def self.from_files(key_path, cert_path)
|
||||
from_strings(File.read(key_path), File.read(cert_path))
|
||||
end
|
||||
|
||||
def initialize(key, cert)
|
||||
@key = key
|
||||
@cert = cert
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'openssl'
|
||||
|
||||
module Gitlab
|
||||
module Email
|
||||
module Smime
|
||||
# Tooling for signing and verifying data with SMIME
|
||||
class Signer
|
||||
include OpenSSL
|
||||
|
||||
def self.sign(cert:, key:, data:)
|
||||
signed_data = PKCS7.sign(cert, key, data, nil, PKCS7::DETACHED)
|
||||
PKCS7.write_smime(signed_data)
|
||||
end
|
||||
|
||||
# return nil if data cannot be verified, otherwise the signed content data
|
||||
def self.verify_signature(cert:, ca_cert: nil, signed_data:)
|
||||
store = X509::Store.new
|
||||
store.set_default_paths
|
||||
store.add_cert(ca_cert) if ca_cert
|
||||
|
||||
signed_smime = PKCS7.read_smime(signed_data)
|
||||
signed_smime if signed_smime.verify([cert], store)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,56 @@
|
|||
require 'fast_spec_helper'
|
||||
|
||||
describe SmimeSignatureSettings do
|
||||
describe '.parse' do
|
||||
let(:default_smime_key) { Rails.root.join('.gitlab_smime_key') }
|
||||
let(:default_smime_cert) { Rails.root.join('.gitlab_smime_cert') }
|
||||
|
||||
it 'sets correct default values to disabled' do
|
||||
parsed_settings = described_class.parse(nil)
|
||||
|
||||
expect(parsed_settings['enabled']).to be(false)
|
||||
expect(parsed_settings['key_file']).to eq(default_smime_key)
|
||||
expect(parsed_settings['cert_file']).to eq(default_smime_cert)
|
||||
end
|
||||
|
||||
context 'when providing custom values' do
|
||||
it 'sets correct default values to disabled' do
|
||||
custom_settings = Settingslogic.new({})
|
||||
|
||||
parsed_settings = described_class.parse(custom_settings)
|
||||
|
||||
expect(parsed_settings['enabled']).to be(false)
|
||||
expect(parsed_settings['key_file']).to eq(default_smime_key)
|
||||
expect(parsed_settings['cert_file']).to eq(default_smime_cert)
|
||||
end
|
||||
|
||||
it 'enables smime with default key and cert' do
|
||||
custom_settings = Settingslogic.new({
|
||||
'enabled' => true
|
||||
})
|
||||
|
||||
parsed_settings = described_class.parse(custom_settings)
|
||||
|
||||
expect(parsed_settings['enabled']).to be(true)
|
||||
expect(parsed_settings['key_file']).to eq(default_smime_key)
|
||||
expect(parsed_settings['cert_file']).to eq(default_smime_cert)
|
||||
end
|
||||
|
||||
it 'enables smime with custom key and cert' do
|
||||
custom_key = '/custom/key'
|
||||
custom_cert = '/custom/cert'
|
||||
custom_settings = Settingslogic.new({
|
||||
'enabled' => true,
|
||||
'key_file' => custom_key,
|
||||
'cert_file' => custom_cert
|
||||
})
|
||||
|
||||
parsed_settings = described_class.parse(custom_settings)
|
||||
|
||||
expect(parsed_settings['enabled']).to be(true)
|
||||
expect(parsed_settings['key_file']).to eq(custom_key)
|
||||
expect(parsed_settings['cert_file']).to eq(custom_cert)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,46 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe 'ActionMailer hooks' do
|
||||
describe 'smime signature interceptor' do
|
||||
before do
|
||||
class_spy(ActionMailer::Base).as_stubbed_const
|
||||
end
|
||||
|
||||
it 'is disabled by default' do
|
||||
load Rails.root.join('config/initializers/action_mailer_hooks.rb')
|
||||
|
||||
expect(ActionMailer::Base).not_to(
|
||||
have_received(:register_interceptor).with(Gitlab::Email::Hook::SmimeSignatureInterceptor))
|
||||
end
|
||||
|
||||
describe 'interceptor testbed' do
|
||||
where(:email_enabled, :email_smime_enabled, :smime_interceptor_enabled) do
|
||||
[
|
||||
[false, false, false],
|
||||
[false, true, false],
|
||||
[true, false, false],
|
||||
[true, true, true]
|
||||
]
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
stub_config_setting(email_enabled: email_enabled)
|
||||
stub_config_setting(email_smime: { enabled: email_smime_enabled })
|
||||
end
|
||||
|
||||
it 'is enabled depending on settings' do
|
||||
load Rails.root.join('config/initializers/action_mailer_hooks.rb')
|
||||
|
||||
if smime_interceptor_enabled
|
||||
expect(ActionMailer::Base).to(
|
||||
have_received(:register_interceptor).with(Gitlab::Email::Hook::SmimeSignatureInterceptor))
|
||||
else
|
||||
expect(ActionMailer::Base).not_to(
|
||||
have_received(:register_interceptor).with(Gitlab::Email::Hook::SmimeSignatureInterceptor))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -13,9 +13,6 @@ describe Gitlab::Email::Hook::DisableEmailInterceptor do
|
|||
end
|
||||
|
||||
after do
|
||||
# Removing interceptor from the list because unregister_interceptor is
|
||||
# implemented in later version of mail gem
|
||||
# See: https://github.com/mikel/mail/pull/705
|
||||
Mail.unregister_interceptor(described_class)
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Email::Hook::SmimeSignatureInterceptor do
|
||||
include SmimeHelper
|
||||
|
||||
# cert generation is an expensive operation and they are used read-only,
|
||||
# so we share them as instance variables in all tests
|
||||
before :context do
|
||||
@root_ca = generate_root
|
||||
@cert = generate_cert(root_ca: @root_ca)
|
||||
end
|
||||
|
||||
let(:root_certificate) do
|
||||
Gitlab::Email::Smime::Certificate.new(@root_ca[:key], @root_ca[:cert])
|
||||
end
|
||||
|
||||
let(:certificate) do
|
||||
Gitlab::Email::Smime::Certificate.new(@cert[:key], @cert[:cert])
|
||||
end
|
||||
|
||||
let(:mail) do
|
||||
ActionMailer::Base.mail(to: 'test@example.com', from: 'info@example.com', body: 'signed hello')
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Gitlab::Email::Smime::Certificate).to receive_messages(from_files: certificate)
|
||||
|
||||
Mail.register_interceptor(described_class)
|
||||
mail.deliver_now
|
||||
end
|
||||
|
||||
after do
|
||||
Mail.unregister_interceptor(described_class)
|
||||
end
|
||||
|
||||
it 'signs the email appropriately with SMIME' do
|
||||
expect(mail.header['To'].value).to eq('test@example.com')
|
||||
expect(mail.header['From'].value).to eq('info@example.com')
|
||||
expect(mail.header['Content-Type'].value).to match('multipart/signed').and match('protocol="application/x-pkcs7-signature"')
|
||||
|
||||
# verify signature and obtain pkcs7 encoded content
|
||||
p7enc = Gitlab::Email::Smime::Signer.verify_signature(
|
||||
cert: certificate.cert,
|
||||
ca_cert: root_certificate.cert,
|
||||
signed_data: mail.encoded)
|
||||
|
||||
# envelope in a Mail object and obtain the body
|
||||
decoded_mail = Mail.new(p7enc.data)
|
||||
|
||||
expect(decoded_mail.body.encoded).to eq('signed hello')
|
||||
end
|
||||
end
|
|
@ -0,0 +1,77 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Email::Smime::Certificate do
|
||||
include SmimeHelper
|
||||
|
||||
# cert generation is an expensive operation and they are used read-only,
|
||||
# so we share them as instance variables in all tests
|
||||
before :context do
|
||||
@root_ca = generate_root
|
||||
@cert = generate_cert(root_ca: @root_ca)
|
||||
end
|
||||
|
||||
describe 'testing environment setup' do
|
||||
describe 'generate_root' do
|
||||
subject { @root_ca }
|
||||
|
||||
it 'generates a root CA that expires a long way in the future' do
|
||||
expect(subject[:cert].not_after).to be > 999.years.from_now
|
||||
end
|
||||
end
|
||||
|
||||
describe 'generate_cert' do
|
||||
subject { @cert }
|
||||
|
||||
it 'generates a cert properly signed by the root CA' do
|
||||
expect(subject[:cert].issuer).to eq(@root_ca[:cert].subject)
|
||||
end
|
||||
|
||||
it 'generates a cert that expires soon' do
|
||||
expect(subject[:cert].not_after).to be < 60.minutes.from_now
|
||||
end
|
||||
|
||||
it 'generates a cert intended for email signing' do
|
||||
expect(subject[:cert].extensions).to include(an_object_having_attributes(oid: 'extendedKeyUsage', value: match('E-mail Protection')))
|
||||
end
|
||||
|
||||
context 'passing in INFINITE_EXPIRY' do
|
||||
subject { generate_cert(root_ca: @root_ca, expires_in: SmimeHelper::INFINITE_EXPIRY) }
|
||||
|
||||
it 'generates a cert that expires a long way in the future' do
|
||||
expect(subject[:cert].not_after).to be > 999.years.from_now
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.from_strings' do
|
||||
it 'parses correctly a certificate and key' do
|
||||
parsed_cert = described_class.from_strings(@cert[:key].to_s, @cert[:cert].to_pem)
|
||||
|
||||
common_cert_tests(parsed_cert, @cert, @root_ca)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.from_files' do
|
||||
it 'parses correctly a certificate and key' do
|
||||
allow(File).to receive(:read).with('a_key').and_return(@cert[:key].to_s)
|
||||
allow(File).to receive(:read).with('a_cert').and_return(@cert[:cert].to_pem)
|
||||
|
||||
parsed_cert = described_class.from_files('a_key', 'a_cert')
|
||||
|
||||
common_cert_tests(parsed_cert, @cert, @root_ca)
|
||||
end
|
||||
end
|
||||
|
||||
def common_cert_tests(parsed_cert, cert, root_ca)
|
||||
expect(parsed_cert.cert).to be_a(OpenSSL::X509::Certificate)
|
||||
expect(parsed_cert.cert.subject).to eq(cert[:cert].subject)
|
||||
expect(parsed_cert.cert.issuer).to eq(root_ca[:cert].subject)
|
||||
expect(parsed_cert.cert.not_before).to eq(cert[:cert].not_before)
|
||||
expect(parsed_cert.cert.not_after).to eq(cert[:cert].not_after)
|
||||
expect(parsed_cert.cert.extensions).to include(an_object_having_attributes(oid: 'extendedKeyUsage', value: match('E-mail Protection')))
|
||||
expect(parsed_cert.key).to be_a(OpenSSL::PKey::RSA)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Email::Smime::Signer do
|
||||
include SmimeHelper
|
||||
|
||||
it 'signs data appropriately with SMIME' do
|
||||
root_certificate = generate_root
|
||||
certificate = generate_cert(root_ca: root_certificate)
|
||||
|
||||
signed_content = described_class.sign(
|
||||
cert: certificate[:cert],
|
||||
key: certificate[:key],
|
||||
data: 'signed content')
|
||||
expect(signed_content).not_to be_nil
|
||||
|
||||
p7enc = described_class.verify_signature(
|
||||
cert: certificate[:cert],
|
||||
ca_cert: root_certificate[:cert],
|
||||
signed_data: signed_content)
|
||||
|
||||
expect(p7enc).not_to be_nil
|
||||
expect(p7enc.data).to eq('signed content')
|
||||
end
|
||||
end
|
|
@ -0,0 +1,55 @@
|
|||
module SmimeHelper
|
||||
include OpenSSL
|
||||
|
||||
INFINITE_EXPIRY = 1000.years
|
||||
SHORT_EXPIRY = 30.minutes
|
||||
|
||||
def generate_root
|
||||
issue(signed_by: nil, expires_in: INFINITE_EXPIRY, certificate_authority: true)
|
||||
end
|
||||
|
||||
def generate_cert(root_ca:, expires_in: SHORT_EXPIRY)
|
||||
issue(signed_by: root_ca, expires_in: expires_in, certificate_authority: false)
|
||||
end
|
||||
|
||||
# returns a hash { key:, cert: } containing a generated key, cert pair
|
||||
def issue(email_address: 'test@example.com', signed_by:, expires_in:, certificate_authority:)
|
||||
key = OpenSSL::PKey::RSA.new(4096)
|
||||
public_key = key.public_key
|
||||
|
||||
subject = if certificate_authority
|
||||
X509::Name.parse("/CN=EU")
|
||||
else
|
||||
X509::Name.parse("/CN=#{email_address}")
|
||||
end
|
||||
|
||||
cert = X509::Certificate.new
|
||||
cert.subject = subject
|
||||
|
||||
cert.issuer = signed_by&.fetch(:cert, nil)&.subject || subject
|
||||
|
||||
cert.not_before = Time.now
|
||||
cert.not_after = expires_in.from_now
|
||||
cert.public_key = public_key
|
||||
cert.serial = 0x0
|
||||
cert.version = 2
|
||||
|
||||
extension_factory = X509::ExtensionFactory.new
|
||||
if certificate_authority
|
||||
extension_factory.subject_certificate = cert
|
||||
extension_factory.issuer_certificate = cert
|
||||
cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
|
||||
cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:TRUE', true))
|
||||
cert.add_extension(extension_factory.create_extension('keyUsage', 'cRLSign,keyCertSign', true))
|
||||
else
|
||||
cert.add_extension(extension_factory.create_extension('subjectAltName', "email:#{email_address}", false))
|
||||
cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE', true))
|
||||
cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature,keyEncipherment', true))
|
||||
cert.add_extension(extension_factory.create_extension('extendedKeyUsage', 'clientAuth,emailProtection', false))
|
||||
end
|
||||
|
||||
cert.sign(signed_by&.fetch(:key, nil) || key, Digest::SHA256.new)
|
||||
|
||||
{ key: key, cert: cert }
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue