421 lines
15 KiB
Ruby
421 lines
15 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
RSpec.describe Gitlab::X509::Signature do
|
|
let(:issuer_attributes) do
|
|
{
|
|
subject_key_identifier: X509Helpers::User1.issuer_subject_key_identifier,
|
|
subject: X509Helpers::User1.certificate_issuer,
|
|
crl_url: X509Helpers::User1.certificate_crl
|
|
}
|
|
end
|
|
|
|
shared_examples "a verified signature" do
|
|
let!(:user) { create(:user, email: X509Helpers::User1.certificate_email) }
|
|
|
|
subject(:signature) do
|
|
described_class.new(
|
|
X509Helpers::User1.signed_commit_signature,
|
|
X509Helpers::User1.signed_commit_base_data,
|
|
X509Helpers::User1.certificate_email,
|
|
X509Helpers::User1.signed_commit_time
|
|
)
|
|
end
|
|
|
|
it 'returns a verified signature if email does match' do
|
|
expect(signature.x509_certificate).to have_attributes(certificate_attributes)
|
|
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
|
|
expect(signature.verified_signature).to be_truthy
|
|
expect(signature.verification_status).to eq(:verified)
|
|
end
|
|
|
|
context "if the email matches but isn't confirmed" do
|
|
let!(:user) { create(:user, :unconfirmed, email: X509Helpers::User1.certificate_email) }
|
|
|
|
it "returns an unverified signature" do
|
|
expect(signature.verification_status).to eq(:unverified)
|
|
end
|
|
end
|
|
|
|
it 'returns an unverified signature if email does not match' do
|
|
signature = described_class.new(
|
|
X509Helpers::User1.signed_commit_signature,
|
|
X509Helpers::User1.signed_commit_base_data,
|
|
"gitlab@example.com",
|
|
X509Helpers::User1.signed_commit_time
|
|
)
|
|
|
|
expect(signature.x509_certificate).to have_attributes(certificate_attributes)
|
|
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
|
|
expect(signature.verified_signature).to be_truthy
|
|
expect(signature.verification_status).to eq(:unverified)
|
|
end
|
|
|
|
it 'returns an unverified signature if email does match and time is wrong' do
|
|
signature = described_class.new(
|
|
X509Helpers::User1.signed_commit_signature,
|
|
X509Helpers::User1.signed_commit_base_data,
|
|
X509Helpers::User1.certificate_email,
|
|
Time.new(2020, 2, 22)
|
|
)
|
|
|
|
expect(signature.x509_certificate).to have_attributes(certificate_attributes)
|
|
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
|
|
expect(signature.verified_signature).to be_falsey
|
|
expect(signature.verification_status).to eq(:unverified)
|
|
end
|
|
|
|
it 'returns an unverified signature if certificate is revoked' do
|
|
expect(signature.verification_status).to eq(:verified)
|
|
|
|
signature.x509_certificate.revoked!
|
|
|
|
expect(signature.verification_status).to eq(:unverified)
|
|
end
|
|
end
|
|
|
|
context 'commit signature' do
|
|
let(:certificate_attributes) do
|
|
{
|
|
subject_key_identifier: X509Helpers::User1.certificate_subject_key_identifier,
|
|
subject: X509Helpers::User1.certificate_subject,
|
|
email: X509Helpers::User1.certificate_email,
|
|
serial_number: X509Helpers::User1.certificate_serial
|
|
}
|
|
end
|
|
|
|
context 'verified signature' do
|
|
context 'with trusted certificate store' do
|
|
before do
|
|
store = OpenSSL::X509::Store.new
|
|
certificate = OpenSSL::X509::Certificate.new(X509Helpers::User1.trust_cert)
|
|
store.add_cert(certificate)
|
|
allow(OpenSSL::X509::Store).to receive(:new).and_return(store)
|
|
end
|
|
|
|
it_behaves_like "a verified signature"
|
|
end
|
|
|
|
context 'with the certificate defined by OpenSSL::X509::DEFAULT_CERT_FILE' do
|
|
before do
|
|
store = OpenSSL::X509::Store.new
|
|
certificate = OpenSSL::X509::Certificate.new(X509Helpers::User1.trust_cert)
|
|
file_path = Rails.root.join("tmp/cert.pem").to_s
|
|
|
|
File.open(file_path, "wb") do |f|
|
|
f.print certificate.to_pem
|
|
end
|
|
|
|
stub_const("OpenSSL::X509::DEFAULT_CERT_FILE", file_path)
|
|
|
|
allow(OpenSSL::X509::Store).to receive(:new).and_return(store)
|
|
end
|
|
|
|
it_behaves_like "a verified signature"
|
|
end
|
|
|
|
context 'without trusted certificate within store' do
|
|
before do
|
|
store = OpenSSL::X509::Store.new
|
|
allow(OpenSSL::X509::Store).to receive(:new)
|
|
.and_return(
|
|
store
|
|
)
|
|
end
|
|
|
|
it 'returns an unverified signature' do
|
|
signature = described_class.new(
|
|
X509Helpers::User1.signed_commit_signature,
|
|
X509Helpers::User1.signed_commit_base_data,
|
|
X509Helpers::User1.certificate_email,
|
|
X509Helpers::User1.signed_commit_time
|
|
)
|
|
|
|
expect(signature.x509_certificate).to have_attributes(certificate_attributes)
|
|
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
|
|
expect(signature.verified_signature).to be_falsey
|
|
expect(signature.verification_status).to eq(:unverified)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'invalid signature' do
|
|
it 'returns nil' do
|
|
signature = described_class.new(
|
|
X509Helpers::User1.signed_commit_signature.tr('A', 'B'),
|
|
X509Helpers::User1.signed_commit_base_data,
|
|
X509Helpers::User1.certificate_email,
|
|
X509Helpers::User1.signed_commit_time
|
|
)
|
|
expect(signature.x509_certificate).to be_nil
|
|
expect(signature.verified_signature).to be_falsey
|
|
expect(signature.verification_status).to eq(:unverified)
|
|
end
|
|
end
|
|
|
|
context 'invalid commit message' do
|
|
it 'returns nil' do
|
|
signature = described_class.new(
|
|
X509Helpers::User1.signed_commit_signature,
|
|
'x',
|
|
X509Helpers::User1.certificate_email,
|
|
X509Helpers::User1.signed_commit_time
|
|
)
|
|
expect(signature.x509_certificate).to be_nil
|
|
expect(signature.verified_signature).to be_falsey
|
|
expect(signature.verification_status).to eq(:unverified)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'certificate_crl' do
|
|
describe 'valid crlDistributionPoints' do
|
|
before do
|
|
allow_any_instance_of(Gitlab::X509::Signature).to receive(:get_certificate_extension).and_call_original
|
|
|
|
allow_any_instance_of(Gitlab::X509::Signature).to receive(:get_certificate_extension)
|
|
.with('crlDistributionPoints')
|
|
.and_return("\nFull Name:\n URI:http://ch.siemens.com/pki?ZZZZZZA2.crl\n URI:ldap://cl.siemens.net/CN=ZZZZZZA2,L=PKI?certificateRevocationList\n URI:ldap://cl.siemens.com/CN=ZZZZZZA2,o=Trustcenter?certificateRevocationList\n")
|
|
end
|
|
|
|
it 'creates an issuer' do
|
|
signature = described_class.new(
|
|
X509Helpers::User1.signed_commit_signature,
|
|
X509Helpers::User1.signed_commit_base_data,
|
|
X509Helpers::User1.certificate_email,
|
|
X509Helpers::User1.signed_commit_time
|
|
)
|
|
|
|
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
|
|
end
|
|
end
|
|
|
|
describe 'valid crlDistributionPoints providing multiple http URIs' do
|
|
before do
|
|
allow_any_instance_of(Gitlab::X509::Signature).to receive(:get_certificate_extension).and_call_original
|
|
|
|
allow_any_instance_of(Gitlab::X509::Signature).to receive(:get_certificate_extension)
|
|
.with('crlDistributionPoints')
|
|
.and_return("\nFull Name:\n URI:http://cdp1.pca.dfn.de/dfn-ca-global-g2/pub/crl/cacrl.crl\n\nFull Name:\n URI:http://cdp2.pca.dfn.de/dfn-ca-global-g2/pub/crl/cacrl.crl\n")
|
|
end
|
|
|
|
it 'extracts the first URI' do
|
|
signature = described_class.new(
|
|
X509Helpers::User1.signed_commit_signature,
|
|
X509Helpers::User1.signed_commit_base_data,
|
|
X509Helpers::User1.certificate_email,
|
|
X509Helpers::User1.signed_commit_time
|
|
)
|
|
|
|
expect(signature.x509_certificate.x509_issuer.crl_url).to eq("http://cdp1.pca.dfn.de/dfn-ca-global-g2/pub/crl/cacrl.crl")
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'email' do
|
|
describe 'subjectAltName with email, othername' do
|
|
before do
|
|
allow_any_instance_of(Gitlab::X509::Signature).to receive(:get_certificate_extension).and_call_original
|
|
|
|
allow_any_instance_of(Gitlab::X509::Signature).to receive(:get_certificate_extension)
|
|
.with('subjectAltName')
|
|
.and_return("email:gitlab@example.com, othername:<unsupported>")
|
|
end
|
|
|
|
it 'extracts email' do
|
|
signature = described_class.new(
|
|
X509Helpers::User1.signed_commit_signature,
|
|
X509Helpers::User1.signed_commit_base_data,
|
|
'gitlab@example.com',
|
|
X509Helpers::User1.signed_commit_time
|
|
)
|
|
|
|
expect(signature.x509_certificate.email).to eq("gitlab@example.com")
|
|
end
|
|
end
|
|
|
|
describe 'subjectAltName with othername, email' do
|
|
before do
|
|
allow_any_instance_of(Gitlab::X509::Signature).to receive(:get_certificate_extension).and_call_original
|
|
|
|
allow_any_instance_of(Gitlab::X509::Signature).to receive(:get_certificate_extension)
|
|
.with('subjectAltName')
|
|
.and_return("othername:<unsupported>, email:gitlab@example.com")
|
|
end
|
|
|
|
it 'extracts email' do
|
|
signature = described_class.new(
|
|
X509Helpers::User1.signed_commit_signature,
|
|
X509Helpers::User1.signed_commit_base_data,
|
|
'gitlab@example.com',
|
|
X509Helpers::User1.signed_commit_time
|
|
)
|
|
|
|
expect(signature.x509_certificate.email).to eq("gitlab@example.com")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#user' do
|
|
subject do
|
|
described_class.new(
|
|
X509Helpers::User1.signed_tag_signature,
|
|
X509Helpers::User1.signed_tag_base_data,
|
|
X509Helpers::User1.certificate_email,
|
|
X509Helpers::User1.signed_commit_time
|
|
).user
|
|
end
|
|
|
|
context 'if email is assigned to a user' do
|
|
let!(:user) { create(:user, email: X509Helpers::User1.certificate_email) }
|
|
|
|
it 'returns user' do
|
|
is_expected.to eq(user)
|
|
end
|
|
end
|
|
|
|
it 'if email is not assigned to a user, return nil' do
|
|
is_expected.to be_nil
|
|
end
|
|
end
|
|
|
|
context 'tag signature' do
|
|
let(:certificate_attributes) do
|
|
{
|
|
subject_key_identifier: X509Helpers::User1.tag_certificate_subject_key_identifier,
|
|
subject: X509Helpers::User1.certificate_subject,
|
|
email: X509Helpers::User1.certificate_email,
|
|
serial_number: X509Helpers::User1.tag_certificate_serial
|
|
}
|
|
end
|
|
|
|
let(:issuer_attributes) do
|
|
{
|
|
subject_key_identifier: X509Helpers::User1.tag_issuer_subject_key_identifier,
|
|
subject: X509Helpers::User1.tag_certificate_issuer,
|
|
crl_url: X509Helpers::User1.tag_certificate_crl
|
|
}
|
|
end
|
|
|
|
context 'verified signature' do
|
|
let_it_be(:user) { create(:user, :unconfirmed, email: X509Helpers::User1.certificate_email) }
|
|
|
|
subject(:signature) do
|
|
described_class.new(
|
|
X509Helpers::User1.signed_tag_signature,
|
|
X509Helpers::User1.signed_tag_base_data,
|
|
X509Helpers::User1.certificate_email,
|
|
X509Helpers::User1.signed_commit_time
|
|
)
|
|
end
|
|
|
|
context 'with trusted certificate store' do
|
|
before do
|
|
store = OpenSSL::X509::Store.new
|
|
certificate = OpenSSL::X509::Certificate.new X509Helpers::User1.trust_cert
|
|
store.add_cert(certificate)
|
|
allow(OpenSSL::X509::Store).to receive(:new).and_return(store)
|
|
end
|
|
|
|
context 'when user email is confirmed' do
|
|
before_all do
|
|
user.confirm
|
|
end
|
|
|
|
it 'returns a verified signature if email does match', :ggregate_failures do
|
|
expect(signature.x509_certificate).to have_attributes(certificate_attributes)
|
|
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
|
|
expect(signature.verified_signature).to be_truthy
|
|
expect(signature.verification_status).to eq(:verified)
|
|
end
|
|
|
|
it 'returns an unverified signature if email does not match', :aggregate_failures do
|
|
signature = described_class.new(
|
|
X509Helpers::User1.signed_tag_signature,
|
|
X509Helpers::User1.signed_tag_base_data,
|
|
"gitlab@example.com",
|
|
X509Helpers::User1.signed_commit_time
|
|
)
|
|
|
|
expect(signature.x509_certificate).to have_attributes(certificate_attributes)
|
|
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
|
|
expect(signature.verified_signature).to be_truthy
|
|
expect(signature.verification_status).to eq(:unverified)
|
|
end
|
|
|
|
it 'returns an unverified signature if email does match and time is wrong', :aggregate_failures do
|
|
signature = described_class.new(
|
|
X509Helpers::User1.signed_tag_signature,
|
|
X509Helpers::User1.signed_tag_base_data,
|
|
X509Helpers::User1.certificate_email,
|
|
Time.new(2020, 2, 22)
|
|
)
|
|
|
|
expect(signature.x509_certificate).to have_attributes(certificate_attributes)
|
|
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
|
|
expect(signature.verified_signature).to be_falsey
|
|
expect(signature.verification_status).to eq(:unverified)
|
|
end
|
|
|
|
it 'returns an unverified signature if certificate is revoked' do
|
|
expect(signature.verification_status).to eq(:verified)
|
|
|
|
signature.x509_certificate.revoked!
|
|
|
|
expect(signature.verification_status).to eq(:unverified)
|
|
end
|
|
end
|
|
|
|
it 'returns an unverified signature if the email matches but is not confirmed' do
|
|
expect(signature.verification_status).to eq(:unverified)
|
|
end
|
|
end
|
|
|
|
context 'without trusted certificate within store' do
|
|
before do
|
|
store = OpenSSL::X509::Store.new
|
|
allow(OpenSSL::X509::Store).to receive(:new)
|
|
.and_return(
|
|
store
|
|
)
|
|
end
|
|
|
|
it 'returns an unverified signature' do
|
|
expect(signature.x509_certificate).to have_attributes(certificate_attributes)
|
|
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
|
|
expect(signature.verified_signature).to be_falsey
|
|
expect(signature.verification_status).to eq(:unverified)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'invalid signature' do
|
|
it 'returns nil' do
|
|
signature = described_class.new(
|
|
X509Helpers::User1.signed_tag_signature.tr('A', 'B'),
|
|
X509Helpers::User1.signed_tag_base_data,
|
|
X509Helpers::User1.certificate_email,
|
|
X509Helpers::User1.signed_commit_time
|
|
)
|
|
expect(signature.x509_certificate).to be_nil
|
|
expect(signature.verified_signature).to be_falsey
|
|
expect(signature.verification_status).to eq(:unverified)
|
|
end
|
|
end
|
|
|
|
context 'invalid message' do
|
|
it 'returns nil' do
|
|
signature = described_class.new(
|
|
X509Helpers::User1.signed_tag_signature,
|
|
'x',
|
|
X509Helpers::User1.certificate_email,
|
|
X509Helpers::User1.signed_commit_time
|
|
)
|
|
expect(signature.x509_certificate).to be_nil
|
|
expect(signature.verified_signature).to be_falsey
|
|
expect(signature.verification_status).to eq(:unverified)
|
|
end
|
|
end
|
|
end
|
|
end
|