Commit fe21bb58 authored by Nick Thomas's avatar Nick Thomas

Only verify commit signatures if the user email is verified

Currently, users are able to create "verified" commit signatures for
emails they don't control.

Changelog: security
parent b9600eea
...@@ -23,7 +23,7 @@ module Gitlab ...@@ -23,7 +23,7 @@ module Gitlab
end end
def user def user
User.find_by_any_email(@email) strong_memoize(:user) { User.find_by_any_email(@email) }
end end
def verified_signature def verified_signature
...@@ -31,9 +31,13 @@ module Gitlab ...@@ -31,9 +31,13 @@ module Gitlab
end end
def verification_status def verification_status
return :unverified if x509_certificate.nil? || x509_certificate.revoked? return :unverified if
x509_certificate.nil? ||
x509_certificate.revoked? ||
!verified_signature ||
user.nil?
if verified_signature && certificate_email == @email if user.verified_emails.include?(@email) && certificate_email == @email
:verified :verified
else else
:unverified :unverified
......
...@@ -12,20 +12,30 @@ RSpec.describe Gitlab::X509::Signature do ...@@ -12,20 +12,30 @@ RSpec.describe Gitlab::X509::Signature do
end end
shared_examples "a verified signature" do shared_examples "a verified signature" do
it 'returns a verified signature if email does match' do let_it_be(:user) { create(:user, email: X509Helpers::User1.certificate_email) }
signature = described_class.new(
subject(:signature) do
described_class.new(
X509Helpers::User1.signed_commit_signature, X509Helpers::User1.signed_commit_signature,
X509Helpers::User1.signed_commit_base_data, X509Helpers::User1.signed_commit_base_data,
X509Helpers::User1.certificate_email, X509Helpers::User1.certificate_email,
X509Helpers::User1.signed_commit_time 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).to have_attributes(certificate_attributes)
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes) expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
expect(signature.verified_signature).to be_truthy expect(signature.verified_signature).to be_truthy
expect(signature.verification_status).to eq(:verified) expect(signature.verification_status).to eq(:verified)
end end
it "returns an unverified signature if the email matches but isn't confirmed" do
user.update!(confirmed_at: nil)
expect(signature.verification_status).to eq(:unverified)
end
it 'returns an unverified signature if email does not match' do it 'returns an unverified signature if email does not match' do
signature = described_class.new( signature = described_class.new(
X509Helpers::User1.signed_commit_signature, X509Helpers::User1.signed_commit_signature,
...@@ -55,13 +65,6 @@ RSpec.describe Gitlab::X509::Signature do ...@@ -55,13 +65,6 @@ RSpec.describe Gitlab::X509::Signature do
end end
it 'returns an unverified signature if certificate is revoked' do it 'returns an unverified signature if certificate is revoked' 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.verification_status).to eq(:verified) expect(signature.verification_status).to eq(:verified)
signature.x509_certificate.revoked! signature.x509_certificate.revoked!
...@@ -253,23 +256,25 @@ RSpec.describe Gitlab::X509::Signature do ...@@ -253,23 +256,25 @@ RSpec.describe Gitlab::X509::Signature do
end end
describe '#user' do describe '#user' do
signature = described_class.new( subject do
X509Helpers::User1.signed_tag_signature, described_class.new(
X509Helpers::User1.signed_tag_base_data, X509Helpers::User1.signed_tag_signature,
X509Helpers::User1.certificate_email, X509Helpers::User1.signed_tag_base_data,
X509Helpers::User1.signed_commit_time X509Helpers::User1.certificate_email,
) X509Helpers::User1.signed_commit_time
).user
end
context 'if email is assigned to a user' do context 'if email is assigned to a user' do
let!(:user) { create(:user, email: X509Helpers::User1.certificate_email) } let!(:user) { create(:user, email: X509Helpers::User1.certificate_email) }
it 'returns user' do it 'returns user' do
expect(signature.user).to eq(user) is_expected.to eq(user)
end end
end end
it 'if email is not assigned to a user, return nil' do it 'if email is not assigned to a user, return nil' do
expect(signature.user).to be_nil is_expected.to be_nil
end end
end end
...@@ -292,6 +297,17 @@ RSpec.describe Gitlab::X509::Signature do ...@@ -292,6 +297,17 @@ RSpec.describe Gitlab::X509::Signature do
end end
context 'verified signature' do context 'verified signature' do
let_it_be(:user) { create(:user, 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 context 'with trusted certificate store' do
before do before do
store = OpenSSL::X509::Store.new store = OpenSSL::X509::Store.new
...@@ -301,19 +317,18 @@ RSpec.describe Gitlab::X509::Signature do ...@@ -301,19 +317,18 @@ RSpec.describe Gitlab::X509::Signature do
end end
it 'returns a verified signature if email does match' do it 'returns a verified signature if email does match' do
signature = described_class.new(
X509Helpers::User1.signed_tag_signature,
X509Helpers::User1.signed_tag_base_data,
X509Helpers::User1.certificate_email,
X509Helpers::User1.signed_commit_time
)
expect(signature.x509_certificate).to have_attributes(certificate_attributes) expect(signature.x509_certificate).to have_attributes(certificate_attributes)
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes) expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
expect(signature.verified_signature).to be_truthy expect(signature.verified_signature).to be_truthy
expect(signature.verification_status).to eq(:verified) expect(signature.verification_status).to eq(:verified)
end end
it "returns an unverified signature if the email matches but isn't confirmed" do
user.update!(confirmed_at: nil)
expect(signature.verification_status).to eq(:unverified)
end
it 'returns an unverified signature if email does not match' do it 'returns an unverified signature if email does not match' do
signature = described_class.new( signature = described_class.new(
X509Helpers::User1.signed_tag_signature, X509Helpers::User1.signed_tag_signature,
...@@ -343,13 +358,6 @@ RSpec.describe Gitlab::X509::Signature do ...@@ -343,13 +358,6 @@ RSpec.describe Gitlab::X509::Signature do
end end
it 'returns an unverified signature if certificate is revoked' do it 'returns an unverified signature if certificate is revoked' do
signature = described_class.new(
X509Helpers::User1.signed_tag_signature,
X509Helpers::User1.signed_tag_base_data,
X509Helpers::User1.certificate_email,
X509Helpers::User1.signed_commit_time
)
expect(signature.verification_status).to eq(:verified) expect(signature.verification_status).to eq(:verified)
signature.x509_certificate.revoked! signature.x509_certificate.revoked!
...@@ -368,13 +376,6 @@ RSpec.describe Gitlab::X509::Signature do ...@@ -368,13 +376,6 @@ RSpec.describe Gitlab::X509::Signature do
end end
it 'returns an unverified signature' do it 'returns an unverified signature' do
signature = described_class.new(
X509Helpers::User1.signed_tag_signature,
X509Helpers::User1.signed_tag_base_data,
X509Helpers::User1.certificate_email,
X509Helpers::User1.signed_commit_time
)
expect(signature.x509_certificate).to have_attributes(certificate_attributes) expect(signature.x509_certificate).to have_attributes(certificate_attributes)
expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes) expect(signature.x509_certificate.x509_issuer).to have_attributes(issuer_attributes)
expect(signature.verified_signature).to be_falsey expect(signature.verified_signature).to be_falsey
......
...@@ -8,12 +8,13 @@ RSpec.describe 'gitlab:x509 namespace rake task' do ...@@ -8,12 +8,13 @@ RSpec.describe 'gitlab:x509 namespace rake task' do
end end
describe 'update_signatures' do describe 'update_signatures' do
subject { run_rake_task('gitlab:x509:update_signatures') } let(:user) { create(:user, email: X509Helpers::User1.certificate_email) }
let(:project) { create(:project, :repository, path: X509Helpers::User1.path, creator: user) }
let(:project) { create :project, :repository, path: X509Helpers::User1.path }
let(:x509_signed_commit) { project.commit_by(oid: '189a6c924013fc3fe40d6f1ec1dc20214183bc97') } let(:x509_signed_commit) { project.commit_by(oid: '189a6c924013fc3fe40d6f1ec1dc20214183bc97') }
let(:x509_commit) { Gitlab::X509::Commit.new(x509_signed_commit).signature } let(:x509_commit) { Gitlab::X509::Commit.new(x509_signed_commit).signature }
subject { run_rake_task('gitlab:x509:update_signatures') }
it 'changes from unverified to verified if the certificate store contains the root certificate' do it 'changes from unverified to verified if the certificate store contains the root certificate' do
x509_commit x509_commit
...@@ -22,21 +23,14 @@ RSpec.describe 'gitlab:x509 namespace rake task' do ...@@ -22,21 +23,14 @@ RSpec.describe 'gitlab:x509 namespace rake task' do
store.add_cert(certificate) store.add_cert(certificate)
allow(OpenSSL::X509::Store).to receive(:new).and_return(store) allow(OpenSSL::X509::Store).to receive(:new).and_return(store)
expect(x509_commit.verification_status).to eq('unverified')
expect_any_instance_of(Gitlab::X509::Commit).to receive(:update_signature!).and_call_original expect_any_instance_of(Gitlab::X509::Commit).to receive(:update_signature!).and_call_original
expect { subject }.to change { x509_commit.reload.verification_status }.from('unverified').to('verified')
subject
x509_commit.reload
expect(x509_commit.verification_status).to eq('verified')
end end
it 'returns if no signature is available' do it 'returns if no signature is available' do
expect_any_instance_of(Gitlab::X509::Commit) do |x509_commit| expect_any_instance_of(Gitlab::X509::Commit).not_to receive(:update_signature!)
expect(x509_commit).not_to receive(:update_signature!)
subject subject
end
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment