Commit 69e511c4 authored by Alexis Reigel's avatar Alexis Reigel

cache the gpg commit signature

we store the result of the gpg commit verification in the db because the
gpg verification is an expensive operation.
parent 8236b12d
...@@ -239,29 +239,14 @@ class Commit ...@@ -239,29 +239,14 @@ class Commit
@signature = nil @signature = nil
signature, signed_text = @raw.signature(project.repository) cached_signature = GpgSignature.find_by(commit_sha: sha)
return cached_signature if cached_signature.present?
return unless signature && signed_text gpg_commit = Gitlab::Gpg::Commit.new(self)
Gitlab::Gpg.using_tmp_keychain do return unless gpg_commit.has_signature?
# first we need to get the keyid from the signature...
GPGME::Crypto.new.verify(signature, signed_text: signed_text) do |verified_signature|
@signature = verified_signature
end
# ... then we query the gpg key belonging to the keyid.
gpg_key = GpgKey.find_by(primary_keyid: @signature.fingerprint)
return @signature unless gpg_key
Gitlab::Gpg::CurrentKeyChain.add(gpg_key.key)
GPGME::Crypto.new.verify(signature, signed_text: signed_text) do |verified_signature|
@signature = verified_signature
end
end
@signature @signature = gpg_commit.signature
end end
def revert_branch_name def revert_branch_name
......
- if signature - if signature
%a.btn.disabled.btn-xs{ class: ('btn-success' if signature.valid?) } %a.btn.disabled.btn-xs{ class: ('btn-success' if signature.valid_signature?) }
%i.fa.fa-key{ class: ('fa-inverse' if signature.valid?) } %i.fa.fa-key{ class: ('fa-inverse' if signature.valid_signature?) }
= signature.valid? ? 'Verified': 'Unverified' = signature.valid_signature? ? 'Verified' : 'Unverified'
module Gitlab
module Gpg
class Commit
attr_reader :commit
def initialize(commit)
@commit = commit
@signature_text, @signed_text = commit.raw.signature(commit.project.repository)
end
def has_signature?
@signature_text && @signed_text
end
def signature
Gitlab::Gpg.using_tmp_keychain do
# first we need to get the keyid from the signature to query the gpg
# key belonging to the keyid.
# This way we can add the key to the temporary keychain and extract
# the proper signature.
gpg_key = GpgKey.find_by(primary_keyid: verified_signature.fingerprint)
if gpg_key
Gitlab::Gpg::CurrentKeyChain.add(gpg_key.key)
end
create_cached_signature!(gpg_key)
end
end
private
def verified_signature
GPGME::Crypto.new.verify(@signature_text, signed_text: @signed_text) do |verified_signature|
return verified_signature
end
end
def create_cached_signature!(gpg_key)
GpgSignature.create!(
commit_sha: commit.sha,
project: commit.project,
gpg_key: gpg_key,
gpg_key_primary_keyid: gpg_key&.primary_keyid,
valid_signature: !!(gpg_key && verified_signature&.valid?)
)
end
end
end
end
require 'rails_helper'
RSpec.describe Gitlab::Gpg::Commit do
describe '#signature' do
let!(:project) { create :project, :repository, path: 'sample-project' }
context 'known public key' do
it 'returns a valid signature' do
gpg_key = create :gpg_key, key: GpgHelpers::User1.public_key
raw_commit = double(:raw_commit, signature: [
GpgHelpers::User1.signed_commit_signature,
GpgHelpers::User1.signed_commit_base_data
], sha: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33')
allow(raw_commit).to receive :save!
commit = create :commit,
git_commit: raw_commit,
project: project
expect(described_class.new(commit).signature).to have_attributes(
commit_sha: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33',
project: project,
gpg_key: gpg_key,
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
valid_signature: true
)
end
end
context 'unknown public key' do
it 'returns an invalid signature', :gpg do
raw_commit = double(:raw_commit, signature: [
GpgHelpers::User1.signed_commit_signature,
GpgHelpers::User1.signed_commit_base_data
], sha: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33')
allow(raw_commit).to receive :save!
commit = create :commit,
git_commit: raw_commit,
project: project
expect(described_class.new(commit).signature).to have_attributes(
commit_sha: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33',
project: project,
gpg_key: nil,
gpg_key_primary_keyid: nil,
valid_signature: false
)
end
end
end
end
...@@ -421,36 +421,78 @@ eos ...@@ -421,36 +421,78 @@ eos
end end
context 'signed commit', :gpg do context 'signed commit', :gpg do
it 'returns a valid signature if the public key is known' do context 'known public key' do
it 'returns a valid signature' do
create :gpg_key, key: GpgHelpers::User1.public_key create :gpg_key, key: GpgHelpers::User1.public_key
raw_commit = double(:raw_commit, signature: [ raw_commit = double(:raw_commit, signature: [
GpgHelpers::User1.signed_commit_signature, GpgHelpers::User1.signed_commit_signature,
GpgHelpers::User1.signed_commit_base_data GpgHelpers::User1.signed_commit_base_data
]) ], sha: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33')
allow(raw_commit).to receive :save! allow(raw_commit).to receive :save!
commit = create :commit, commit = create :commit,
git_commit: raw_commit, git_commit: raw_commit,
project: project project: project
expect(commit.signature).to be_a GPGME::Signature expect(commit.signature.valid_signature?).to be_truthy
expect(commit.signature.valid?).to be_truthy
end end
it 'returns the cached validation result on second call', :gpg do
create :gpg_key, key: GpgHelpers::User1.public_key
raw_commit = double(:raw_commit, signature: [
GpgHelpers::User1.signed_commit_signature,
GpgHelpers::User1.signed_commit_base_data
], sha: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33')
allow(raw_commit).to receive :save!
commit = create :commit,
git_commit: raw_commit,
project: project
expect(Gitlab::Gpg::Commit).to receive(:new).and_call_original
expect(commit.signature.valid_signature?).to be_truthy
# second call returns the cache
expect(Gitlab::Gpg::Commit).not_to receive(:new).and_call_original
expect(commit.signature.valid_signature?).to be_truthy
end
end
context 'unknown public key' do
it 'returns an invalid signature if the public key is unknown', :gpg do it 'returns an invalid signature if the public key is unknown', :gpg do
raw_commit = double(:raw_commit, signature: [ raw_commit = double(:raw_commit, signature: [
GpgHelpers::User1.signed_commit_signature, GpgHelpers::User1.signed_commit_signature,
GpgHelpers::User1.signed_commit_base_data GpgHelpers::User1.signed_commit_base_data
]) ], sha: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33')
allow(raw_commit).to receive :save!
commit = create :commit,
git_commit: raw_commit,
project: project
expect(commit.signature.valid_signature?).to be_falsey
end
it 'returns the cached validation result on second call', :gpg do
raw_commit = double(:raw_commit, signature: [
GpgHelpers::User1.signed_commit_signature,
GpgHelpers::User1.signed_commit_base_data
], sha: '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33')
allow(raw_commit).to receive :save! allow(raw_commit).to receive :save!
commit = create :commit, commit = create :commit,
git_commit: raw_commit, git_commit: raw_commit,
project: project project: project
expect(commit.signature).to be_a GPGME::Signature expect(Gitlab::Gpg::Commit).to receive(:new).and_call_original
expect(commit.signature.valid?).to be_falsey expect(commit.signature.valid_signature?).to be_falsey
# second call returns the cache
expect(Gitlab::Gpg::Commit).not_to receive(:new).and_call_original
expect(commit.signature.valid_signature?).to be_falsey
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