mirror of
https://github.com/zammad/zammad
synced 2026-05-24 09:48:36 +00:00
156 lines
6 KiB
Ruby
156 lines
6 KiB
Ruby
# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
|
|
|
|
require 'rails_helper'
|
|
|
|
RSpec.describe Setting::Validation::Saml::Security do
|
|
let(:setting_name) { 'auth_saml_credentials' }
|
|
|
|
let(:setting_value) do
|
|
{
|
|
idp_sso_target_url: 'https://self-signed.badssl.com/',
|
|
idp_slo_service_url: 'https://example.com',
|
|
name_identifier_format: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
|
|
idp_cert: '-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----',
|
|
ssl_verify: false,
|
|
security: security,
|
|
private_key: private_key_pem,
|
|
private_key_secret: private_key_secret,
|
|
certificate: certificate,
|
|
}
|
|
end
|
|
|
|
let(:security) { 'on' }
|
|
let(:private_key_pem) { OpenSSL::PKey::RSA.generate('2048').to_pem }
|
|
let(:private_key_secret) { '' }
|
|
|
|
let(:certificate) do
|
|
private_key = private_key_pem.blank? ? OpenSSL::PKey::RSA.generate('2048') : OpenSSL::PKey.read(private_key_pem)
|
|
create_certificate(private_key)
|
|
end
|
|
|
|
def create_certificate(private_key, expired: false, ca_cert: false, usable: true)
|
|
cert = OpenSSL::X509::Certificate.new
|
|
cert.subject = cert.issuer = OpenSSL::X509::Name.parse('/CN=Acme')
|
|
cert.not_before = Time.zone.now
|
|
cert.not_after = Time.zone.now + (365 * 24 * 60 * 60)
|
|
cert.public_key = private_key.public_key if private_key.respond_to?(:public_key)
|
|
cert.serial = 0x0
|
|
cert.version = 2
|
|
|
|
cert.not_after = Time.zone.now - (365 * 24 * 60 * 60) if expired
|
|
|
|
ef = OpenSSL::X509::ExtensionFactory.new
|
|
ef.subject_certificate = cert
|
|
ef.issuer_certificate = cert
|
|
|
|
certificate_extensions(cert, ef, ca_cert:, usable:)
|
|
|
|
cert.sign(private_key, OpenSSL::Digest.new('SHA256'))
|
|
|
|
cert.to_pem
|
|
end
|
|
|
|
def certificate_extensions(cert, extension_factory, ca_cert: false, usable: true)
|
|
cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:TRUE', true)) if ca_cert
|
|
cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash', false))
|
|
cert.add_extension(extension_factory.create_extension('keyUsage', usable ? 'digitalSignature,keyEncipherment' : 'cRLSign', true))
|
|
|
|
cert.add_extension(extension_factory.create_extension('authorityKeyIdentifier', 'keyid:always,issuer:always', false))
|
|
end
|
|
|
|
context 'with blank settings' do
|
|
it 'does not raise an error' do
|
|
expect { Setting.set(setting_name, {}) }.not_to raise_error
|
|
end
|
|
end
|
|
|
|
context 'with no security' do
|
|
let(:security) { 'off' }
|
|
|
|
it 'does not raise an error' do
|
|
expect { Setting.set(setting_name, {}) }.not_to raise_error
|
|
end
|
|
end
|
|
|
|
context 'with missing prerequisites' do
|
|
context 'when certificate is missing' do
|
|
let(:certificate) { '' }
|
|
|
|
it 'raises an error' do
|
|
expect { Setting.set(setting_name, setting_value) }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: No certificate found.')
|
|
end
|
|
end
|
|
|
|
context 'when private key is missing' do
|
|
let(:private_key_pem) { '' }
|
|
|
|
it 'raises an error' do
|
|
expect { Setting.set(setting_name, setting_value) }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: No private key found.')
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when private key has wrong type' do
|
|
let(:certificate) { create_certificate(OpenSSL::PKey::RSA.generate('2048')) }
|
|
let(:private_key_pem) { OpenSSL::PKey::EC.generate('prime256v1').to_pem }
|
|
|
|
it 'raises an error' do
|
|
expect { Setting.set(setting_name, setting_value) }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: The type of the private key is wrong.')
|
|
end
|
|
end
|
|
|
|
context 'when private key has wrong length' do
|
|
let(:private_key_pem) { OpenSSL::PKey::RSA.generate('1024').to_pem }
|
|
|
|
it 'raises an error' do
|
|
expect { Setting.set(setting_name, setting_value) }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: The length of the private key is too short.')
|
|
end
|
|
end
|
|
|
|
context 'when certificate contains non-parsable content' do
|
|
let(:certificate) { 'dummy' }
|
|
|
|
it 'raises an error' do
|
|
expect { Setting.set(setting_name, setting_value) }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: The certificate could not be parsed.')
|
|
end
|
|
end
|
|
|
|
context 'when certificate is a CA certificate' do
|
|
let(:certificate) { create_certificate(OpenSSL::PKey.read(private_key_pem), ca_cert: true) }
|
|
|
|
it 'raises an error' do
|
|
expect { Setting.set(setting_name, setting_value) }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: The certificate is not usable due to being a CA certificate.')
|
|
end
|
|
end
|
|
|
|
context 'when certificate is not usable (e.g. expired)' do
|
|
let(:certificate) { create_certificate(OpenSSL::PKey.read(private_key_pem), expired: true) }
|
|
|
|
it 'raises an error' do
|
|
expect { Setting.set(setting_name, setting_value) }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: The certificate is not usable (e.g. expired).')
|
|
end
|
|
end
|
|
|
|
context 'when certificate is not usable for signing/encrypting' do
|
|
let(:certificate) { create_certificate(OpenSSL::PKey.read(private_key_pem), usable: false) }
|
|
|
|
it 'raises an error' do
|
|
expect { Setting.set(setting_name, setting_value) }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: The certificate is not usable for signing and encryption.')
|
|
end
|
|
end
|
|
|
|
context 'when certificate does not match the private key' do
|
|
let(:private_key_pem) { OpenSSL::PKey::RSA.generate('2048').to_pem }
|
|
let(:certificate) { create_certificate(OpenSSL::PKey::RSA.generate('2048')) }
|
|
|
|
it 'raises an error' do
|
|
expect { Setting.set(setting_name, setting_value) }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: The certificate does not match the given private key.')
|
|
end
|
|
end
|
|
|
|
context 'with a valid certificate and private key' do
|
|
it 'does not raise an error' do
|
|
expect { Setting.set(setting_name, setting_value) }.not_to raise_error
|
|
end
|
|
end
|
|
end
|