123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- # Copyright (C) 2012-2024 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
|