Merge branch '36829-gpg-commit-not-verified-if-signed-with-a-subkey' into 'master'
Add support for GPG subkeys in signature verification Closes #36829 See merge request gitlab-org/gitlab-ce!14517
This commit is contained in:
commit
dd42cb5f8f
|
|
@ -108,6 +108,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
.subkeys-list {
|
||||
@include basic-list;
|
||||
|
||||
li {
|
||||
padding: 3px 0;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.key-list-item {
|
||||
.key-list-item-info {
|
||||
@media (min-width: $screen-sm-min) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ class Profiles::GpgKeysController < Profiles::ApplicationController
|
|||
before_action :set_gpg_key, only: [:destroy, :revoke]
|
||||
|
||||
def index
|
||||
@gpg_keys = current_user.gpg_keys
|
||||
@gpg_keys = current_user.gpg_keys.with_subkeys
|
||||
@gpg_key = GpgKey.new
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ class GpgKey < ActiveRecord::Base
|
|||
|
||||
belongs_to :user
|
||||
has_many :gpg_signatures
|
||||
has_many :subkeys, class_name: 'GpgKeySubkey'
|
||||
|
||||
scope :with_subkeys, -> { includes(:subkeys) }
|
||||
|
||||
validates :user, presence: true
|
||||
|
||||
|
|
@ -36,10 +39,12 @@ class GpgKey < ActiveRecord::Base
|
|||
|
||||
before_validation :extract_fingerprint, :extract_primary_keyid
|
||||
after_commit :update_invalid_gpg_signatures, on: :create
|
||||
after_create :generate_subkeys
|
||||
|
||||
def primary_keyid
|
||||
super&.upcase
|
||||
end
|
||||
alias_method :keyid, :primary_keyid
|
||||
|
||||
def fingerprint
|
||||
super&.upcase
|
||||
|
|
@ -49,6 +54,10 @@ class GpgKey < ActiveRecord::Base
|
|||
super(value&.strip)
|
||||
end
|
||||
|
||||
def keyids
|
||||
[keyid].concat(subkeys.map(&:keyid))
|
||||
end
|
||||
|
||||
def user_infos
|
||||
@user_infos ||= Gitlab::Gpg.user_infos_from_key(key)
|
||||
end
|
||||
|
|
@ -82,10 +91,11 @@ class GpgKey < ActiveRecord::Base
|
|||
|
||||
def revoke
|
||||
GpgSignature
|
||||
.where(gpg_key: self)
|
||||
.with_key_and_subkeys(self)
|
||||
.where.not(verification_status: GpgSignature.verification_statuses[:unknown_key])
|
||||
.update_all(
|
||||
gpg_key_id: nil,
|
||||
gpg_key_subkey_id: nil,
|
||||
verification_status: GpgSignature.verification_statuses[:unknown_key],
|
||||
updated_at: Time.zone.now
|
||||
)
|
||||
|
|
@ -106,4 +116,12 @@ class GpgKey < ActiveRecord::Base
|
|||
# only allows one key
|
||||
self.primary_keyid = Gitlab::Gpg.primary_keyids_from_key(key).first
|
||||
end
|
||||
|
||||
def generate_subkeys
|
||||
gpg_subkeys = Gitlab::Gpg.subkeys_from_key(key)
|
||||
|
||||
gpg_subkeys[primary_keyid]&.each do |subkey_data|
|
||||
subkeys.create!(keyid: subkey_data[:keyid], fingerprint: subkey_data[:fingerprint])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
class GpgKeySubkey < ActiveRecord::Base
|
||||
include ShaAttribute
|
||||
|
||||
sha_attribute :keyid
|
||||
sha_attribute :fingerprint
|
||||
|
||||
belongs_to :gpg_key
|
||||
|
||||
validates :gpg_key_id, presence: true
|
||||
validates :fingerprint, :keyid, presence: true, uniqueness: true
|
||||
|
||||
delegate :key, :user, :user_infos, :verified?, :verified_user_infos,
|
||||
:verified_and_belongs_to_email?, to: :gpg_key
|
||||
|
||||
def keyid
|
||||
super&.upcase
|
||||
end
|
||||
|
||||
def fingerprint
|
||||
super&.upcase
|
||||
end
|
||||
end
|
||||
|
|
@ -15,11 +15,42 @@ class GpgSignature < ActiveRecord::Base
|
|||
|
||||
belongs_to :project
|
||||
belongs_to :gpg_key
|
||||
belongs_to :gpg_key_subkey
|
||||
|
||||
validates :commit_sha, presence: true
|
||||
validates :project_id, presence: true
|
||||
validates :gpg_key_primary_keyid, presence: true
|
||||
|
||||
def self.with_key_and_subkeys(gpg_key)
|
||||
subkey_ids = gpg_key.subkeys.pluck(:id)
|
||||
|
||||
where(
|
||||
arel_table[:gpg_key_id].eq(gpg_key.id).or(
|
||||
arel_table[:gpg_key_subkey_id].in(subkey_ids)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def gpg_key=(model)
|
||||
case model
|
||||
when GpgKey
|
||||
super
|
||||
when GpgKeySubkey
|
||||
self.gpg_key_subkey = model
|
||||
when NilClass
|
||||
super
|
||||
self.gpg_key_subkey = nil
|
||||
end
|
||||
end
|
||||
|
||||
def gpg_key
|
||||
if gpg_key_id
|
||||
super
|
||||
elsif gpg_key_subkey_id
|
||||
gpg_key_subkey
|
||||
end
|
||||
end
|
||||
|
||||
def gpg_key_primary_keyid
|
||||
super&.upcase
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,6 +7,13 @@
|
|||
|
||||
.description
|
||||
%code= key.fingerprint
|
||||
- if key.subkeys.present?
|
||||
.subkeys
|
||||
%span.bold Subkeys:
|
||||
%ul.subkeys-list
|
||||
- key.subkeys.each do |subkey|
|
||||
%li
|
||||
%code= subkey.fingerprint
|
||||
.pull-right
|
||||
%span.key-created-at
|
||||
created #{time_ago_with_tooltip(key.created_at)}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add support for GPG subkeys in signature verification
|
||||
merge_request: 14517
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
class CreateGpgKeySubkeys < ActiveRecord::Migration
|
||||
DOWNTIME = false
|
||||
|
||||
def up
|
||||
create_table :gpg_key_subkeys do |t|
|
||||
t.references :gpg_key, null: false, index: true, foreign_key: { on_delete: :cascade }
|
||||
|
||||
t.binary :keyid
|
||||
t.binary :fingerprint
|
||||
|
||||
t.index :keyid, unique: true, length: Gitlab::Database.mysql? ? 20 : nil
|
||||
t.index :fingerprint, unique: true, length: Gitlab::Database.mysql? ? 20 : nil
|
||||
end
|
||||
|
||||
add_reference :gpg_signatures, :gpg_key_subkey, index: true, foreign_key: { on_delete: :nullify }
|
||||
end
|
||||
|
||||
def down
|
||||
remove_reference(:gpg_signatures, :gpg_key_subkey, index: true, foreign_key: true)
|
||||
|
||||
drop_table :gpg_key_subkeys
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class ScheduleCreateGpgKeySubkeysFromGpgKeys < ActiveRecord::Migration
|
||||
disable_ddl_transaction!
|
||||
|
||||
DOWNTIME = false
|
||||
MIGRATION = 'CreateGpgKeySubkeysFromGpgKeys'
|
||||
|
||||
class GpgKey < ActiveRecord::Base
|
||||
self.table_name = 'gpg_keys'
|
||||
|
||||
include EachBatch
|
||||
end
|
||||
|
||||
def up
|
||||
GpgKey.select(:id).each_batch do |gpg_keys|
|
||||
jobs = gpg_keys.pluck(:id).map do |id|
|
||||
[MIGRATION, [id]]
|
||||
end
|
||||
|
||||
BackgroundMigrationWorker.perform_bulk(jobs)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
end
|
||||
end
|
||||
16
db/schema.rb
16
db/schema.rb
|
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20171004121444) do
|
||||
ActiveRecord::Schema.define(version: 20171005130944) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
|
@ -580,6 +580,16 @@ ActiveRecord::Schema.define(version: 20171004121444) do
|
|||
|
||||
add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree
|
||||
|
||||
create_table "gpg_key_subkeys", force: :cascade do |t|
|
||||
t.integer "gpg_key_id", null: false
|
||||
t.binary "keyid"
|
||||
t.binary "fingerprint"
|
||||
end
|
||||
|
||||
add_index "gpg_key_subkeys", ["fingerprint"], name: "index_gpg_key_subkeys_on_fingerprint", unique: true, using: :btree
|
||||
add_index "gpg_key_subkeys", ["gpg_key_id"], name: "index_gpg_key_subkeys_on_gpg_key_id", using: :btree
|
||||
add_index "gpg_key_subkeys", ["keyid"], name: "index_gpg_key_subkeys_on_keyid", unique: true, using: :btree
|
||||
|
||||
create_table "gpg_keys", force: :cascade do |t|
|
||||
t.datetime_with_timezone "created_at", null: false
|
||||
t.datetime_with_timezone "updated_at", null: false
|
||||
|
|
@ -603,11 +613,13 @@ ActiveRecord::Schema.define(version: 20171004121444) do
|
|||
t.text "gpg_key_user_name"
|
||||
t.text "gpg_key_user_email"
|
||||
t.integer "verification_status", limit: 2, default: 0, null: false
|
||||
t.integer "gpg_key_subkey_id"
|
||||
end
|
||||
|
||||
add_index "gpg_signatures", ["commit_sha"], name: "index_gpg_signatures_on_commit_sha", unique: true, using: :btree
|
||||
add_index "gpg_signatures", ["gpg_key_id"], name: "index_gpg_signatures_on_gpg_key_id", using: :btree
|
||||
add_index "gpg_signatures", ["gpg_key_primary_keyid"], name: "index_gpg_signatures_on_gpg_key_primary_keyid", using: :btree
|
||||
add_index "gpg_signatures", ["gpg_key_subkey_id"], name: "index_gpg_signatures_on_gpg_key_subkey_id", using: :btree
|
||||
add_index "gpg_signatures", ["project_id"], name: "index_gpg_signatures_on_project_id", using: :btree
|
||||
|
||||
create_table "identities", force: :cascade do |t|
|
||||
|
|
@ -1727,7 +1739,9 @@ ActiveRecord::Schema.define(version: 20171004121444) do
|
|||
add_foreign_key "events", "projects", on_delete: :cascade
|
||||
add_foreign_key "events", "users", column: "author_id", name: "fk_edfd187b6f", on_delete: :cascade
|
||||
add_foreign_key "forked_project_links", "projects", column: "forked_to_project_id", name: "fk_434510edb0", on_delete: :cascade
|
||||
add_foreign_key "gpg_key_subkeys", "gpg_keys", on_delete: :cascade
|
||||
add_foreign_key "gpg_keys", "users", on_delete: :cascade
|
||||
add_foreign_key "gpg_signatures", "gpg_key_subkeys", on_delete: :nullify
|
||||
add_foreign_key "gpg_signatures", "gpg_keys", on_delete: :nullify
|
||||
add_foreign_key "gpg_signatures", "projects", on_delete: :cascade
|
||||
add_foreign_key "issue_assignees", "issues", name: "fk_b7d881734a", on_delete: :cascade
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
class Gitlab::BackgroundMigration::CreateGpgKeySubkeysFromGpgKeys
|
||||
class GpgKey < ActiveRecord::Base
|
||||
self.table_name = 'gpg_keys'
|
||||
|
||||
include EachBatch
|
||||
include ShaAttribute
|
||||
|
||||
sha_attribute :primary_keyid
|
||||
sha_attribute :fingerprint
|
||||
|
||||
has_many :subkeys, class_name: 'GpgKeySubkey'
|
||||
end
|
||||
|
||||
class GpgKeySubkey < ActiveRecord::Base
|
||||
self.table_name = 'gpg_key_subkeys'
|
||||
|
||||
include ShaAttribute
|
||||
|
||||
sha_attribute :keyid
|
||||
sha_attribute :fingerprint
|
||||
end
|
||||
|
||||
def perform(gpg_key_id)
|
||||
gpg_key = GpgKey.find_by(id: gpg_key_id)
|
||||
|
||||
return if gpg_key.nil?
|
||||
return if gpg_key.subkeys.any?
|
||||
|
||||
create_subkeys(gpg_key)
|
||||
update_signatures(gpg_key)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_subkeys(gpg_key)
|
||||
gpg_subkeys = Gitlab::Gpg.subkeys_from_key(gpg_key.key)
|
||||
|
||||
gpg_subkeys[gpg_key.primary_keyid.upcase]&.each do |subkey_data|
|
||||
gpg_key.subkeys.build(keyid: subkey_data[:keyid], fingerprint: subkey_data[:fingerprint])
|
||||
end
|
||||
|
||||
# Improve latency by doing all INSERTs in a single call
|
||||
GpgKey.transaction do
|
||||
gpg_key.save!
|
||||
end
|
||||
end
|
||||
|
||||
def update_signatures(gpg_key)
|
||||
return unless gpg_key.subkeys.exists?
|
||||
|
||||
InvalidGpgSignatureUpdateWorker.perform_async(gpg_key.id)
|
||||
end
|
||||
end
|
||||
|
|
@ -34,6 +34,21 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def subkeys_from_key(key)
|
||||
using_tmp_keychain do
|
||||
fingerprints = CurrentKeyChain.fingerprints_from_key(key)
|
||||
raw_keys = GPGME::Key.find(:public, fingerprints)
|
||||
|
||||
raw_keys.each_with_object({}) do |raw_key, grouped_subkeys|
|
||||
primary_subkey_id = raw_key.primary_subkey.keyid
|
||||
|
||||
grouped_subkeys[primary_subkey_id] = raw_key.subkeys[1..-1].map do |s|
|
||||
{ keyid: s.keyid, fingerprint: s.fingerprint }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def user_infos_from_key(key)
|
||||
using_tmp_keychain do
|
||||
fingerprints = CurrentKeyChain.fingerprints_from_key(key)
|
||||
|
|
|
|||
|
|
@ -43,7 +43,9 @@ module Gitlab
|
|||
# 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)
|
||||
# NOTE: the invoked method is #fingerprint but it's only returning
|
||||
# 16 characters (the format used by keyid) instead of 40.
|
||||
gpg_key = find_gpg_key(verified_signature.fingerprint)
|
||||
|
||||
if gpg_key
|
||||
Gitlab::Gpg::CurrentKeyChain.add(gpg_key.key)
|
||||
|
|
@ -74,7 +76,7 @@ module Gitlab
|
|||
commit_sha: @commit.sha,
|
||||
project: @commit.project,
|
||||
gpg_key: gpg_key,
|
||||
gpg_key_primary_keyid: gpg_key&.primary_keyid || verified_signature.fingerprint,
|
||||
gpg_key_primary_keyid: gpg_key&.keyid || verified_signature.fingerprint,
|
||||
gpg_key_user_name: user_infos[:name],
|
||||
gpg_key_user_email: user_infos[:email],
|
||||
verification_status: verification_status
|
||||
|
|
@ -98,6 +100,10 @@ module Gitlab
|
|||
def user_infos(gpg_key)
|
||||
gpg_key&.verified_user_infos&.first || gpg_key&.user_infos&.first || {}
|
||||
end
|
||||
|
||||
def find_gpg_key(keyid)
|
||||
GpgKey.find_by(primary_keyid: keyid) || GpgKeySubkey.find_by(keyid: keyid)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ module Gitlab
|
|||
GpgSignature
|
||||
.select(:id, :commit_sha, :project_id)
|
||||
.where('gpg_key_id IS NULL OR verification_status <> ?', GpgSignature.verification_statuses[:verified])
|
||||
.where(gpg_key_primary_keyid: @gpg_key.primary_keyid)
|
||||
.where(gpg_key_primary_keyid: @gpg_key.keyids)
|
||||
.find_each { |sig| sig.gpg_commit.update_signature!(sig) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
require_relative '../support/gpg_helpers'
|
||||
|
||||
FactoryGirl.define do
|
||||
factory :gpg_key_subkey do
|
||||
gpg_key
|
||||
|
||||
sequence(:keyid) { |n| "keyid-#{n}" }
|
||||
sequence(:fingerprint) { |n| "fingerprint-#{n}" }
|
||||
end
|
||||
end
|
||||
|
|
@ -4,5 +4,9 @@ FactoryGirl.define do
|
|||
factory :gpg_key do
|
||||
key GpgHelpers::User1.public_key
|
||||
user
|
||||
|
||||
factory :gpg_key_with_subkeys do
|
||||
key GpgHelpers::User1.public_key_with_extra_signing_key
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ FactoryGirl.define do
|
|||
commit_sha { Digest::SHA1.hexdigest(SecureRandom.hex) }
|
||||
project
|
||||
gpg_key
|
||||
gpg_key_primary_keyid { gpg_key.primary_keyid }
|
||||
gpg_key_primary_keyid { gpg_key.keyid }
|
||||
verification_status :verified
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -20,6 +20,18 @@ feature 'Profile > GPG Keys' do
|
|||
expect(page).to have_content('bette.cartwright@example.net Unverified')
|
||||
expect(page).to have_content(GpgHelpers::User2.fingerprint)
|
||||
end
|
||||
|
||||
scenario 'with multiple subkeys' do
|
||||
fill_in('Key', with: GpgHelpers::User3.public_key)
|
||||
click_button('Add key')
|
||||
|
||||
expect(page).to have_content('john.doe@example.com Unverified')
|
||||
expect(page).to have_content(GpgHelpers::User3.fingerprint)
|
||||
|
||||
GpgHelpers::User3.subkey_fingerprints.each do |fingerprint|
|
||||
expect(page).to have_content(fingerprint)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scenario 'User sees their key' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::BackgroundMigration::CreateGpgKeySubkeysFromGpgKeys, :migration, schema: 20171005130944 do
|
||||
context 'when GpgKey exists' do
|
||||
let!(:gpg_key) { create(:gpg_key, key: GpgHelpers::User3.public_key) }
|
||||
|
||||
before do
|
||||
GpgKeySubkey.destroy_all
|
||||
end
|
||||
|
||||
it 'generate the subkeys' do
|
||||
expect do
|
||||
described_class.new.perform(gpg_key.id)
|
||||
end.to change { gpg_key.subkeys.count }.from(0).to(2)
|
||||
end
|
||||
|
||||
it 'schedules the signature update worker' do
|
||||
expect(InvalidGpgSignatureUpdateWorker).to receive(:perform_async).with(gpg_key.id)
|
||||
|
||||
described_class.new.perform(gpg_key.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when GpgKey does not exist' do
|
||||
it 'does not do anything' do
|
||||
expect(Gitlab::Gpg).not_to receive(:subkeys_from_key)
|
||||
expect(InvalidGpgSignatureUpdateWorker).not_to receive(:perform_async)
|
||||
|
||||
described_class.new.perform(123)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -63,6 +63,45 @@ describe Gitlab::Gpg::Commit do
|
|||
it_behaves_like 'returns the cached signature on second call'
|
||||
end
|
||||
|
||||
context 'commit signed with a subkey' do
|
||||
let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User3.emails.first }
|
||||
|
||||
let!(:user) { create(:user, email: GpgHelpers::User3.emails.first) }
|
||||
|
||||
let!(:gpg_key) do
|
||||
create :gpg_key, key: GpgHelpers::User3.public_key, user: user
|
||||
end
|
||||
|
||||
let(:gpg_key_subkey) do
|
||||
gpg_key.subkeys.find_by(fingerprint: '0522DD29B98F167CD8421752E38FFCAF75ABD92A')
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Rugged::Commit).to receive(:extract_signature)
|
||||
.with(Rugged::Repository, commit_sha)
|
||||
.and_return(
|
||||
[
|
||||
GpgHelpers::User3.signed_commit_signature,
|
||||
GpgHelpers::User3.signed_commit_base_data
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns a valid signature' do
|
||||
expect(described_class.new(commit).signature).to have_attributes(
|
||||
commit_sha: commit_sha,
|
||||
project: project,
|
||||
gpg_key: gpg_key_subkey,
|
||||
gpg_key_primary_keyid: gpg_key_subkey.keyid,
|
||||
gpg_key_user_name: GpgHelpers::User3.names.first,
|
||||
gpg_key_user_email: GpgHelpers::User3.emails.first,
|
||||
verification_status: 'verified'
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'returns the cached signature on second call'
|
||||
end
|
||||
|
||||
context 'user email does not match the committer email, but is the same user' do
|
||||
let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User2.emails.first }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,17 +2,16 @@ require 'rails_helper'
|
|||
|
||||
RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
|
||||
describe '#run' do
|
||||
let!(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
|
||||
let!(:project) { create :project, :repository, path: 'sample-project' }
|
||||
let(:signature) { [GpgHelpers::User1.signed_commit_signature, GpgHelpers::User1.signed_commit_base_data] }
|
||||
let(:committer_email) { GpgHelpers::User1.emails.first }
|
||||
let!(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
|
||||
let!(:project) { create :project, :repository, path: 'sample-project' }
|
||||
let!(:raw_commit) do
|
||||
raw_commit = double(
|
||||
:raw_commit,
|
||||
signature: [
|
||||
GpgHelpers::User1.signed_commit_signature,
|
||||
GpgHelpers::User1.signed_commit_base_data
|
||||
],
|
||||
signature: signature,
|
||||
sha: commit_sha,
|
||||
committer_email: GpgHelpers::User1.emails.first
|
||||
committer_email: committer_email
|
||||
)
|
||||
|
||||
allow(raw_commit).to receive :save!
|
||||
|
|
@ -29,12 +28,7 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater 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
|
||||
]
|
||||
)
|
||||
.and_return(signature)
|
||||
end
|
||||
|
||||
context 'gpg signature did have an associated gpg key which was removed later' do
|
||||
|
|
@ -183,5 +177,34 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'gpg signature did not have an associated gpg subkey' do
|
||||
let(:signature) { [GpgHelpers::User3.signed_commit_signature, GpgHelpers::User3.signed_commit_base_data] }
|
||||
let(:committer_email) { GpgHelpers::User3.emails.first }
|
||||
let!(:user) { create :user, email: GpgHelpers::User3.emails.first }
|
||||
|
||||
let!(:invalid_gpg_signature) do
|
||||
create :gpg_signature,
|
||||
project: project,
|
||||
commit_sha: commit_sha,
|
||||
gpg_key: nil,
|
||||
gpg_key_primary_keyid: GpgHelpers::User3.subkey_fingerprints.last[24..-1],
|
||||
verification_status: 'unknown_key'
|
||||
end
|
||||
|
||||
it 'updates the signature to being valid when the missing gpg key is added' do
|
||||
# InvalidGpgSignatureUpdater is called by the after_create hook
|
||||
gpg_key = create(:gpg_key, key: GpgHelpers::User3.public_key, user: user)
|
||||
subkey = gpg_key.subkeys.last
|
||||
|
||||
expect(invalid_gpg_signature.reload).to have_attributes(
|
||||
project: project,
|
||||
commit_sha: commit_sha,
|
||||
gpg_key_subkey_id: subkey.id,
|
||||
gpg_key_primary_keyid: subkey.keyid,
|
||||
verification_status: 'verified'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -28,6 +28,23 @@ describe Gitlab::Gpg do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.subkeys_from_key' do
|
||||
it 'returns the subkeys by primary key' do
|
||||
all_subkeys = described_class.subkeys_from_key(GpgHelpers::User1.public_key)
|
||||
subkeys = all_subkeys[GpgHelpers::User1.primary_keyid]
|
||||
|
||||
expect(subkeys).to be_present
|
||||
expect(subkeys.first[:keyid]).to be_present
|
||||
expect(subkeys.first[:fingerprint]).to be_present
|
||||
end
|
||||
|
||||
it 'returns an empty array when there are not subkeys' do
|
||||
all_subkeys = described_class.subkeys_from_key(GpgHelpers::User4.public_key)
|
||||
|
||||
expect(all_subkeys[GpgHelpers::User4.primary_keyid]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe '.user_infos_from_key' do
|
||||
it 'returns the names and emails' do
|
||||
user_infos = described_class.user_infos_from_key(GpgHelpers::User1.public_key)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
require 'spec_helper'
|
||||
require Rails.root.join('db', 'post_migrate', '20171005130944_schedule_create_gpg_key_subkeys_from_gpg_keys')
|
||||
|
||||
describe ScheduleCreateGpgKeySubkeysFromGpgKeys, :migration, :sidekiq do
|
||||
matcher :be_scheduled_migration do |*expected|
|
||||
match do |migration|
|
||||
BackgroundMigrationWorker.jobs.any? do |job|
|
||||
job['args'] == [migration, expected]
|
||||
end
|
||||
end
|
||||
|
||||
failure_message do |migration|
|
||||
"Migration `#{migration}` with args `#{expected.inspect}` not scheduled!"
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
create(:gpg_key, id: 1, key: GpgHelpers::User1.public_key)
|
||||
create(:gpg_key, id: 2, key: GpgHelpers::User3.public_key)
|
||||
# Delete all subkeys so they can be recreated
|
||||
GpgKeySubkey.destroy_all
|
||||
end
|
||||
|
||||
it 'correctly schedules background migrations' do
|
||||
Sidekiq::Testing.fake! do
|
||||
migrate!
|
||||
|
||||
expect(described_class::MIGRATION).to be_scheduled_migration(1)
|
||||
expect(described_class::MIGRATION).to be_scheduled_migration(2)
|
||||
expect(BackgroundMigrationWorker.jobs.size).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
it 'schedules background migrations' do
|
||||
Sidekiq::Testing.inline! do
|
||||
expect(GpgKeySubkey.count).to eq(0)
|
||||
|
||||
migrate!
|
||||
|
||||
expect(GpgKeySubkey.count).to eq(3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -3,6 +3,7 @@ require 'rails_helper'
|
|||
describe GpgKey do
|
||||
describe "associations" do
|
||||
it { is_expected.to belong_to(:user) }
|
||||
it { is_expected.to have_many(:subkeys) }
|
||||
end
|
||||
|
||||
describe "validation" do
|
||||
|
|
@ -38,6 +39,14 @@ describe GpgKey do
|
|||
expect(gpg_key.primary_keyid).to eq GpgHelpers::User1.primary_keyid
|
||||
end
|
||||
end
|
||||
|
||||
describe 'generate_subkeys' do
|
||||
it 'extracts the subkeys from the gpg key' do
|
||||
gpg_key = create(:gpg_key, key: GpgHelpers::User1.public_key_with_extra_signing_key)
|
||||
|
||||
expect(gpg_key.subkeys.count).to eq(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#key=' do
|
||||
|
|
@ -182,5 +191,29 @@ describe GpgKey do
|
|||
|
||||
expect(unrelated_gpg_key.destroyed?).to be false
|
||||
end
|
||||
|
||||
it 'deletes all the associated subkeys' do
|
||||
gpg_key = create :gpg_key, key: GpgHelpers::User3.public_key
|
||||
|
||||
expect(gpg_key.subkeys).to be_present
|
||||
|
||||
gpg_key.revoke
|
||||
|
||||
expect(gpg_key.subkeys(true)).to be_blank
|
||||
end
|
||||
|
||||
it 'invalidates all signatures associated to the subkeys' do
|
||||
gpg_key = create :gpg_key, key: GpgHelpers::User3.public_key
|
||||
gpg_key_subkey = gpg_key.subkeys.last
|
||||
gpg_signature = create :gpg_signature, verification_status: :verified, gpg_key: gpg_key_subkey
|
||||
|
||||
gpg_key.revoke
|
||||
|
||||
expect(gpg_signature.reload).to have_attributes(
|
||||
verification_status: 'unknown_key',
|
||||
gpg_key: nil,
|
||||
gpg_key_subkey: nil
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe GpgKeySubkey do
|
||||
subject { build(:gpg_key_subkey) }
|
||||
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:gpg_key) }
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
it { is_expected.to validate_presence_of(:gpg_key_id) }
|
||||
it { is_expected.to validate_presence_of(:fingerprint) }
|
||||
it { is_expected.to validate_presence_of(:keyid) }
|
||||
end
|
||||
end
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe GpgSignature do
|
||||
let(:gpg_key) { create(:gpg_key) }
|
||||
let(:gpg_key_subkey) { create(:gpg_key_subkey) }
|
||||
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:project) }
|
||||
it { is_expected.to belong_to(:gpg_key) }
|
||||
it { is_expected.to belong_to(:gpg_key_subkey) }
|
||||
end
|
||||
|
||||
describe 'validation' do
|
||||
|
|
@ -25,4 +29,26 @@ RSpec.describe GpgSignature do
|
|||
gpg_signature.commit
|
||||
end
|
||||
end
|
||||
|
||||
describe '#gpg_key=' do
|
||||
it 'supports the assignment of a GpgKey' do
|
||||
gpg_signature = create(:gpg_signature, gpg_key: gpg_key)
|
||||
|
||||
expect(gpg_signature.gpg_key).to be_an_instance_of(GpgKey)
|
||||
end
|
||||
|
||||
it 'supports the assignment of a GpgKeySubkey' do
|
||||
gpg_signature = create(:gpg_signature, gpg_key: gpg_key_subkey)
|
||||
|
||||
expect(gpg_signature.gpg_key).to be_an_instance_of(GpgKeySubkey)
|
||||
end
|
||||
|
||||
it 'clears gpg_key and gpg_key_subkey_id when passing nil' do
|
||||
gpg_signature = create(:gpg_signature, gpg_key: gpg_key_subkey)
|
||||
gpg_signature.update_attribute(:gpg_key, nil)
|
||||
|
||||
expect(gpg_signature.gpg_key_id).to be_nil
|
||||
expect(gpg_signature.gpg_key_subkey_id).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,4 +18,14 @@ describe GpgKeys::CreateService do
|
|||
it 'creates a gpg key' do
|
||||
expect { subject.execute }.to change { user.gpg_keys.where(params).count }.by(1)
|
||||
end
|
||||
|
||||
context 'when the public key contains subkeys' do
|
||||
let(:params) { attributes_for(:gpg_key_with_subkeys) }
|
||||
|
||||
it 'generates the gpg subkeys' do
|
||||
gpg_key = subject.execute
|
||||
|
||||
expect(gpg_key.subkeys.count).to eq(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -92,6 +92,46 @@ module GpgHelpers
|
|||
KEY
|
||||
end
|
||||
|
||||
def public_key_with_extra_signing_key
|
||||
<<~KEY.strip
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v1
|
||||
|
||||
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
|
||||
DMVGWrg4m1NF8N6yMPMP
|
||||
=RB1y
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
KEY
|
||||
end
|
||||
|
||||
def primary_keyid
|
||||
fingerprint[-16..-1]
|
||||
end
|
||||
|
|
@ -201,4 +241,277 @@ module GpgHelpers
|
|||
['bette.cartwright@example.com', 'bette.cartwright@example.net']
|
||||
end
|
||||
end
|
||||
|
||||
# GPG Key with extra signing key
|
||||
module User3
|
||||
extend self
|
||||
|
||||
def signed_commit_signature
|
||||
<<~SIGNATURE
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQEzBAABCAAdFiEEBSLdKbmPFnzYQhdS44/8r3Wr2SoFAlnNlT8ACgkQ44/8r3Wr
|
||||
2SqP1wf9FC4J2S8LIHs/fpxgkYzsyCp5lCbS7JuoD4pqmI2KWyBx+vi9/3mZPCsm
|
||||
Fj9f0vFEtNOb39GNGZbaA8DdGw30/WAS6kI6yco0WSK53KHrLw9Kqd+3e/NAVSsl
|
||||
991Gq4n8X1U5izSH+gZOMtEEUBGqIlZKgRrEh7lhNcz0G7JTF2VCE4NNtZdq7GDA
|
||||
N6jOQxDGUwi9wQBYORQzIBc3NihfhGloII1hXf0XzrgUY3zNYHTT7QipCxKAmH54
|
||||
skwW+wi8RpBedar4saf7fs5xZbP/0yyVz98MJMdHBL68++Xt1AIHoqrb7eWszqnd
|
||||
PCo/fnz1iHKCig602KLj0/zhADcNkg==
|
||||
=LsTi
|
||||
-----END PGP SIGNATURE-----
|
||||
SIGNATURE
|
||||
end
|
||||
|
||||
def signed_commit_base_data
|
||||
<<~SIGNEDDATA
|
||||
tree 86ec18bfe87ad42a782fdabd8310f9b7ac750f51
|
||||
parent 2d1096e3a0ecf1d2baf6dee036cc80775d4940ba
|
||||
author John Doe <john.doe@example.com> 1506645311 -0500
|
||||
committer John Doe <john.doe@example.com> 1506645311 -0500
|
||||
|
||||
Commit signed with subkey by John Doe
|
||||
SIGNEDDATA
|
||||
end
|
||||
|
||||
def public_key
|
||||
<<~KEY.strip
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
/jQYeOgFDKq20x86WulkvsUtBoaZJg==
|
||||
=Q5Z7
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
KEY
|
||||
end
|
||||
|
||||
def secret_key
|
||||
<<~SECRET
|
||||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
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
|
||||
Jg==
|
||||
=TKlF
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
||||
SECRET
|
||||
end
|
||||
|
||||
def fingerprint
|
||||
'EA3F8B88972A8FD474872F2F115B3AF1AAD3EDBA'
|
||||
end
|
||||
|
||||
def subkey_fingerprints
|
||||
%w(159AD5DDF199591D67D2B87AA3CEC5C0A7C270EC 0522DD29B98F167CD8421752E38FFCAF75ABD92A)
|
||||
end
|
||||
|
||||
def names
|
||||
['John Doe']
|
||||
end
|
||||
|
||||
def emails
|
||||
['john.doe@example.com']
|
||||
end
|
||||
end
|
||||
|
||||
# GPG Key containing just the main key
|
||||
module User4
|
||||
extend self
|
||||
|
||||
def public_key
|
||||
<<~KEY.strip
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQENBFnWcesBCAC6Y8FXl9ZJ9HPa6dIYcgQrvjIQcwoQCUEsaXNRpc+206RPCIXK
|
||||
aIYr0nTD8GeovMuUONXTj+DdueQU2GAAqHHOqvDDVXqRrW3xfWnSwix7sTuhG1Ew
|
||||
PLHYmjLENqaTsdyliEo3N8VWy2k0QRbC3R6xvop4Ooa87D5vcATIl0gYFtSiHIL+
|
||||
TervYvTG9Eq1qSLZHbe2x4IzeqX2luikPKokL7j8FTZaCmC5MezIUur1ulfyYY/j
|
||||
SkST/1aUFc5QXJJSZA0MYJWZX6x7Y3l7yl0dkHqmK8OTuo8RPWd3ybEiuvRsOL8K
|
||||
GAv/PmVJRGDAf7GGbwXXsE9MiZ5GzVPxHnexABEBAAG0G0pvaG4gRG9lIDxqb2hu
|
||||
QGV4YW1wbGUuY29tPokBTgQTAQgAOBYhBAh0izYM0lwuzJnVlAcBbPnhOj+bBQJZ
|
||||
1nHrAhsDBQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEAcBbPnhOj+bkywH/i4w
|
||||
OwpDxoTjUQlPlqGAGuzvWaPzSJndawgmMTr68oRsD+wlQmQQTR5eqxCpUIyV4aYb
|
||||
D697RYzoqbT4mlU49ymzfKSAxFe88r1XQWdm81DcofHVPmw2GBrIqaX3Du4Z7xkI
|
||||
Q9/S43orwknh5FoVwU8Nau7qBuv9vbw2apSkuA1oBj3spQ8hqwLavACyQ+fQloAT
|
||||
hSDNqPiCZj6L0dwM1HYiqVoN3Q7qjgzzeBzlXzljJoWblhxllvMK20bVoa7H+uR2
|
||||
lczFHfsX8VTIMjyTGP7R3oHN91DEahlQybVVNLmNSDKZM2P/0d28BRUmWxQJ4Ws3
|
||||
J4hOWDKnLMed3VOIWzM=
|
||||
=xVuW
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
KEY
|
||||
end
|
||||
|
||||
def secret_key
|
||||
<<~KEY.strip
|
||||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
lQPGBFnWcesBCAC6Y8FXl9ZJ9HPa6dIYcgQrvjIQcwoQCUEsaXNRpc+206RPCIXK
|
||||
aIYr0nTD8GeovMuUONXTj+DdueQU2GAAqHHOqvDDVXqRrW3xfWnSwix7sTuhG1Ew
|
||||
PLHYmjLENqaTsdyliEo3N8VWy2k0QRbC3R6xvop4Ooa87D5vcATIl0gYFtSiHIL+
|
||||
TervYvTG9Eq1qSLZHbe2x4IzeqX2luikPKokL7j8FTZaCmC5MezIUur1ulfyYY/j
|
||||
SkST/1aUFc5QXJJSZA0MYJWZX6x7Y3l7yl0dkHqmK8OTuo8RPWd3ybEiuvRsOL8K
|
||||
GAv/PmVJRGDAf7GGbwXXsE9MiZ5GzVPxHnexABEBAAH+BwMC4UwgHgH5Cp7meY39
|
||||
G5Q3GV2xtwADoaAvlOvPOLPK2fQqxQfb4WN4eZECp2wQuMRBMj52c4i9yphab1mQ
|
||||
vOzoPIRGvkcJoxG++OxQ0kRk0C0gX6wM6SGVdb1nQnfZnoJCCU3IwCaSGktkLDs1
|
||||
jwdI+VmXJbSugUbd25bakHQcE2BaNHuRBlQWQfFbhGBy0+uMfNDBZ6FRipBu47hO
|
||||
f/wm/xXuV8N8BSgvNR/qtAqSQI34CdsnWAhMYm9rqmTNyt0nq4dveX+E0YzVn4lH
|
||||
lOEa7cpYeuBwIL8L3EvSPNCICiJlF3gVqiYzyqRElnCkv1OGc0x3W5onY/agHgGZ
|
||||
KYyi/ubOdqqDgBR+eMt0JKSGH2EPxUAGFPY5F37u4erdxH86GzIinAExLSmADiVR
|
||||
KtxluZP6S2KLbETN5uVbrfa+HVcMbbUZaBHHtL+YbY8PqaFUIvIUR1HM2SK7IrFw
|
||||
KuQ8ibRgooyP7VgMNiPzlFpY4NXUv+FXIrNJ6ELuIaENi0izJ7aIbVBM8SijDz6u
|
||||
5EEmodnDvmU2hmQNZJ17TxggE7oeT0rKdDGHM5zBvqZ3deqE9sgKx/aTKcj61ID3
|
||||
M80ZkHPDFazUCohLpYgFN20bYYSmxU4LeNFy8YEiuic8QQKaAFxSf9Lf87UFQwyF
|
||||
dduI1RWEbjMsbEJXwlmGM02ssQHsgoVKwZxijq5A5R1Ul6LowazQ8obPiwRS4NZ4
|
||||
Z+QKDon79MMXiFEeh1jeG/MKKWPxFg3pdtCWhC7WdH4hfkBsCVKf+T58yB2Gzziy
|
||||
fOHvAl7v3PtdZgf1xikF8spGYGCWo4B2lxC79xIflKAb2U6myb5I4dpUYxzxoMxT
|
||||
zxHwxEie3NxzZGUyXSt3LqYe2r4CxWnOCXWjIxxRlLue1BE5Za1ycnDRjgUO24+Z
|
||||
uDQne6KLkhAotBtKb2huIERvZSA8am9obkBleGFtcGxlLmNvbT6JAU4EEwEIADgW
|
||||
IQQIdIs2DNJcLsyZ1ZQHAWz54To/mwUCWdZx6wIbAwULCQgHAgYVCAkKCwIEFgID
|
||||
AQIeAQIXgAAKCRAHAWz54To/m5MsB/4uMDsKQ8aE41EJT5ahgBrs71mj80iZ3WsI
|
||||
JjE6+vKEbA/sJUJkEE0eXqsQqVCMleGmGw+ve0WM6Km0+JpVOPcps3ykgMRXvPK9
|
||||
V0FnZvNQ3KHx1T5sNhgayKml9w7uGe8ZCEPf0uN6K8JJ4eRaFcFPDWru6gbr/b28
|
||||
NmqUpLgNaAY97KUPIasC2rwAskPn0JaAE4Ugzaj4gmY+i9HcDNR2IqlaDd0O6o4M
|
||||
83gc5V85YyaFm5YcZZbzCttG1aGux/rkdpXMxR37F/FUyDI8kxj+0d6BzfdQxGoZ
|
||||
UMm1VTS5jUgymTNj/9HdvAUVJlsUCeFrNyeITlgypyzHnd1TiFsz
|
||||
=/37z
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
||||
KEY
|
||||
end
|
||||
|
||||
def primary_keyid
|
||||
fingerprint[-16..-1]
|
||||
end
|
||||
|
||||
def fingerprint
|
||||
'08748B360CD25C2ECC99D59407016CF9E13A3F9B'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue