Commit 64855c8e authored by Alexis Reigel's avatar Alexis Reigel

match the committer's email against the gpg key

the updated verification of a gpg signature requires the committer's
email to also match the user's and the key's emails.
parent 508ff17b
...@@ -73,6 +73,10 @@ class GpgKey < ActiveRecord::Base ...@@ -73,6 +73,10 @@ class GpgKey < ActiveRecord::Base
emails_with_verified_status.any? { |_email, verified| verified } emails_with_verified_status.any? { |_email, verified| verified }
end end
def verified_and_belongs_to_email?(email)
emails_with_verified_status.any? { |key_email, verified| key_email == email && verified }
end
def update_invalid_gpg_signatures def update_invalid_gpg_signatures
InvalidGpgSignatureUpdateWorker.perform_async(self.id) InvalidGpgSignatureUpdateWorker.perform_async(self.id)
end end
......
...@@ -4,6 +4,14 @@ class GpgSignature < ActiveRecord::Base ...@@ -4,6 +4,14 @@ class GpgSignature < ActiveRecord::Base
sha_attribute :commit_sha sha_attribute :commit_sha
sha_attribute :gpg_key_primary_keyid sha_attribute :gpg_key_primary_keyid
enum verification_status: {
unverified: 0,
verified: 1,
other_user: 2,
unverified_key: 3,
unknown_key: 4
}
belongs_to :project belongs_to :project
belongs_to :gpg_key belongs_to :gpg_key
......
...@@ -68,6 +68,7 @@ module Gitlab ...@@ -68,6 +68,7 @@ module Gitlab
def attributes(gpg_key) def attributes(gpg_key)
user_infos = user_infos(gpg_key) user_infos = user_infos(gpg_key)
verification_status = verification_status(gpg_key)
{ {
commit_sha: @commit.sha, commit_sha: @commit.sha,
...@@ -76,12 +77,21 @@ module Gitlab ...@@ -76,12 +77,21 @@ module Gitlab
gpg_key_primary_keyid: gpg_key&.primary_keyid || verified_signature.fingerprint, gpg_key_primary_keyid: gpg_key&.primary_keyid || verified_signature.fingerprint,
gpg_key_user_name: user_infos[:name], gpg_key_user_name: user_infos[:name],
gpg_key_user_email: user_infos[:email], gpg_key_user_email: user_infos[:email],
valid_signature: gpg_signature_valid_signature_value(gpg_key) valid_signature: verification_status == GpgSignature.verification_statuses[:verified],
verification_status: verification_status
} }
end end
def gpg_signature_valid_signature_value(gpg_key) def verification_status(gpg_key)
!!(gpg_key && gpg_key.verified? && verified_signature.valid?) if gpg_key && gpg_key.verified_and_belongs_to_email?(@commit.committer_email) && verified_signature.valid?
GpgSignature.verification_statuses[:verified]
elsif gpg_key && gpg_key.verified? && verified_signature.valid?
GpgSignature.verification_statuses[:other_user]
elsif gpg_key
GpgSignature.verification_statuses[:unverified_key]
else
GpgSignature.verification_statuses[:unknown_key]
end
end end
def user_infos(gpg_key) def user_infos(gpg_key)
......
...@@ -3,59 +3,109 @@ require 'rails_helper' ...@@ -3,59 +3,109 @@ require 'rails_helper'
describe Gitlab::Gpg::Commit do describe Gitlab::Gpg::Commit do
describe '#signature' do describe '#signature' do
let!(:project) { create :project, :repository, path: 'sample-project' } let!(:project) { create :project, :repository, path: 'sample-project' }
let!(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' } let!(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
context 'unsigned commit' do context 'unsigned commit' do
let!(:commit) { create :commit, project: project, sha: commit_sha }
it 'returns nil' do it 'returns nil' do
expect(described_class.new(project, commit_sha).signature).to be_nil expect(described_class.new(commit).signature).to be_nil
end end
end end
context 'known and verified public key' do context 'known key' do
let!(:gpg_key) do context 'user matches the key uid' do
create :gpg_key, key: GpgHelpers::User1.public_key, user: create(:user, email: GpgHelpers::User1.emails.first) context 'user matches the committer' do
end let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User1.emails.first }
before do let!(:user) { create(:user, email: GpgHelpers::User1.emails.first) }
allow(Rugged::Commit).to receive(:extract_signature)
.with(Rugged::Repository, commit_sha) let!(:gpg_key) do
.and_return( create :gpg_key, key: GpgHelpers::User1.public_key, user: user
[ end
GpgHelpers::User1.signed_commit_signature,
GpgHelpers::User1.signed_commit_base_data before do
] allow(Rugged::Commit).to receive(:extract_signature)
) .with(Rugged::Repository, commit_sha)
end .and_return(
[
it 'returns a valid signature' do GpgHelpers::User1.signed_commit_signature,
expect(described_class.new(project, commit_sha).signature).to have_attributes( GpgHelpers::User1.signed_commit_base_data
commit_sha: commit_sha, ]
project: project, )
gpg_key: gpg_key, end
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
gpg_key_user_name: GpgHelpers::User1.names.first, it 'returns a valid signature' do
gpg_key_user_email: GpgHelpers::User1.emails.first, expect(described_class.new(commit).signature).to have_attributes(
valid_signature: true commit_sha: commit_sha,
) project: project,
gpg_key: gpg_key,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
gpg_key_user_name: GpgHelpers::User1.names.first,
gpg_key_user_email: GpgHelpers::User1.emails.first,
valid_signature: true,
verification_status: 'verified'
)
end
it 'returns the cached signature on second call' do
gpg_commit = described_class.new(commit)
expect(gpg_commit).to receive(:using_keychain).and_call_original
gpg_commit.signature
# consecutive call
expect(gpg_commit).not_to receive(:using_keychain).and_call_original
gpg_commit.signature
end
end
context 'user does not match the committer' do
let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User2.emails.first }
let(:user) { create(:user, email: GpgHelpers::User1.emails.first) }
let!(:gpg_key) do
create :gpg_key, key: GpgHelpers::User1.public_key, user: user
end
before do
allow(Rugged::Commit).to receive(:extract_signature)
.with(Rugged::Repository, commit_sha)
.and_return(
[
GpgHelpers::User1.signed_commit_signature,
GpgHelpers::User1.signed_commit_base_data
]
)
end
it 'returns an invalid signature' do
expect(described_class.new(commit).signature).to have_attributes(
commit_sha: commit_sha,
project: project,
gpg_key: gpg_key,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
gpg_key_user_name: GpgHelpers::User1.names.first,
gpg_key_user_email: GpgHelpers::User1.emails.first,
valid_signature: false,
verification_status: 'other_user'
)
end
end
end end
it 'returns the cached signature on second call' do context 'user does not match the key uid' do
gpg_commit = described_class.new(project, commit_sha) let!(:commit) { create :commit, project: project, sha: commit_sha }
expect(gpg_commit).to receive(:using_keychain).and_call_original
gpg_commit.signature
# consecutive call let(:user) { create(:user, email: GpgHelpers::User2.emails.first) }
expect(gpg_commit).not_to receive(:using_keychain).and_call_original
gpg_commit.signature
end
end
context 'known but unverified public key' do let!(:gpg_key) do
let!(:gpg_key) { create :gpg_key, key: GpgHelpers::User1.public_key } create :gpg_key, key: GpgHelpers::User1.public_key, user: user
end
before do before do
allow(Rugged::Commit).to receive(:extract_signature) allow(Rugged::Commit).to receive(:extract_signature)
.with(Rugged::Repository, commit_sha) .with(Rugged::Repository, commit_sha)
.and_return( .and_return(
[ [
...@@ -63,33 +113,26 @@ describe Gitlab::Gpg::Commit do ...@@ -63,33 +113,26 @@ describe Gitlab::Gpg::Commit do
GpgHelpers::User1.signed_commit_base_data GpgHelpers::User1.signed_commit_base_data
] ]
) )
end end
it 'returns an invalid signature' do it 'returns an invalid signature' do
expect(described_class.new(project, commit_sha).signature).to have_attributes( expect(described_class.new(commit).signature).to have_attributes(
commit_sha: commit_sha, commit_sha: commit_sha,
project: project, project: project,
gpg_key: gpg_key, gpg_key: gpg_key,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid, gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
gpg_key_user_name: GpgHelpers::User1.names.first, gpg_key_user_name: GpgHelpers::User1.names.first,
gpg_key_user_email: GpgHelpers::User1.emails.first, gpg_key_user_email: GpgHelpers::User1.emails.first,
valid_signature: false valid_signature: false,
) verification_status: 'unverified_key'
end )
end
it 'returns the cached signature on second call' do
gpg_commit = described_class.new(project, commit_sha)
expect(gpg_commit).to receive(:using_keychain).and_call_original
gpg_commit.signature
# consecutive call
expect(gpg_commit).not_to receive(:using_keychain).and_call_original
gpg_commit.signature
end end
end end
context 'unknown public key' do context 'unknown key' do
let!(:commit) { create :commit, project: project, sha: commit_sha }
before do before do
allow(Rugged::Commit).to receive(:extract_signature) allow(Rugged::Commit).to receive(:extract_signature)
.with(Rugged::Repository, commit_sha) .with(Rugged::Repository, commit_sha)
...@@ -102,19 +145,20 @@ describe Gitlab::Gpg::Commit do ...@@ -102,19 +145,20 @@ describe Gitlab::Gpg::Commit do
end end
it 'returns an invalid signature' do it 'returns an invalid signature' do
expect(described_class.new(project, commit_sha).signature).to have_attributes( expect(described_class.new(commit).signature).to have_attributes(
commit_sha: commit_sha, commit_sha: commit_sha,
project: project, project: project,
gpg_key: nil, gpg_key: nil,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid, gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
gpg_key_user_name: nil, gpg_key_user_name: nil,
gpg_key_user_email: nil, gpg_key_user_email: nil,
valid_signature: false valid_signature: false,
verification_status: 'unknown_key'
) )
end end
it 'returns the cached signature on second call' do it 'returns the cached signature on second call' do
gpg_commit = described_class.new(project, commit_sha) gpg_commit = described_class.new(commit)
expect(gpg_commit).to receive(:using_keychain).and_call_original expect(gpg_commit).to receive(:using_keychain).and_call_original
gpg_commit.signature gpg_commit.signature
......
...@@ -99,14 +99,14 @@ describe GpgKey do ...@@ -99,14 +99,14 @@ describe GpgKey do
end end
describe '#verified?' do describe '#verified?' do
it 'returns true one of the email addresses in the key belongs to the user' do it 'returns true if one of the email addresses in the key belongs to the user' do
user = create :user, email: 'bette.cartwright@example.com' user = create :user, email: 'bette.cartwright@example.com'
gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
expect(gpg_key.verified?).to be_truthy expect(gpg_key.verified?).to be_truthy
end end
it 'returns false if one of the email addresses in the key does not belong to the user' do it 'returns false if none of the email addresses in the key does not belong to the user' do
user = create :user, email: 'someone.else@example.com' user = create :user, email: 'someone.else@example.com'
gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
...@@ -114,6 +114,32 @@ describe GpgKey do ...@@ -114,6 +114,32 @@ describe GpgKey do
end end
end end
describe 'verified_and_belongs_to_email?' do
it 'returns false if none of the email addresses in the key does not belong to the user' do
user = create :user, email: 'someone.else@example.com'
gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
expect(gpg_key.verified?).to be_falsey
expect(gpg_key.verified_and_belongs_to_email?('someone.else@example.com')).to be_falsey
end
it 'returns false if one of the email addresses in the key belongs to the user and does not match the provided email' do
user = create :user, email: 'bette.cartwright@example.com'
gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
expect(gpg_key.verified?).to be_truthy
expect(gpg_key.verified_and_belongs_to_email?('bette.cartwright@example.net')).to be_falsey
end
it 'returns true if one of the email addresses in the key belongs to the user and matches the provided email' do
user = create :user, email: 'bette.cartwright@example.com'
gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
expect(gpg_key.verified?).to be_truthy
expect(gpg_key.verified_and_belongs_to_email?('bette.cartwright@example.com')).to be_truthy
end
end
describe 'notification', :mailer do describe 'notification', :mailer do
let(:user) { create(:user) } let(:user) { create(:user) }
......
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