Commit d0c5b1dd authored by Mathieu Parent's avatar Mathieu Parent

Add Packages::Debian::GenerateDistributionKeyService

parent 74b7b3c3
# frozen_string_literal: true
module Packages
module Debian
class GenerateDistributionKeyService
include Gitlab::Utils::StrongMemoize
def initialize(current_user:, params: {})
@current_user = current_user
@params = params
end
def execute
raise ArgumentError, 'Please provide a user' unless current_user.is_a?(User)
generate_key
end
private
attr_reader :current_user, :params
def passphrase
strong_memoize(:passphrase) do
params[:passphrase] || ::User.random_password
end
end
def pinentry_script_content
escaped_passphrase = Shellwords.escape(passphrase)
<<~EOF
#!/bin/sh
echo OK Pleased to meet you
while read -r cmd; do
case "$cmd" in
GETPIN) echo D #{escaped_passphrase}; echo OK;;
*) echo OK;;
esac
done
EOF
end
def using_pinentry
Gitlab::Gpg.using_tmp_keychain do
home_dir = Gitlab::Gpg.current_home_dir
File.write("#{home_dir}/pinentry.sh", pinentry_script_content, mode: 'w', perm: 0755)
File.write("#{home_dir}/gpg-agent.conf", "pinentry-program #{home_dir}/pinentry.sh\n", mode: 'w')
GPGME::Ctx.new(armor: true, offline: true) do |ctx|
yield ctx
end
end
end
def generate_key_params
# https://www.gnupg.org/documentation/manuals/gnupg/Unattended-GPG-key-generation.html
'<GnupgKeyParms format="internal">' + "\n" +
{
'Key-Type': params[:key_type] || 'RSA',
'Key-Length': params[:key_length] || 4096,
'Key-Usage': params[:key_usage] || 'sign',
'Name-Real': params[:name_real] || 'GitLab Debian repository',
'Name-Email': params[:name_email] || Gitlab.config.gitlab.email_reply_to,
'Name-Comment': params[:name_comment] || 'GitLab Debian repository automatic signing key',
'Expire-Date': params[:expire_date] || 0,
'Passphrase': passphrase
}.map { |k, v| "#{k}: #{v}\n" }.join +
'</GnupgKeyParms>'
end
def generate_key
using_pinentry do |ctx|
# Generate key
ctx.generate_key generate_key_params
key = ctx.keys.first # rubocop:disable Gitlab/KeysFirstAndValuesFirst
fingerprint = key.fingerprint
# Export private key
data = GPGME::Data.new
ctx.export_keys fingerprint, data, GPGME::EXPORT_MODE_SECRET
data.seek 0
private_key = data.read
# Export public key
data = GPGME::Data.new
ctx.export_keys fingerprint, data
data.seek 0
public_key = data.read
{
private_key: private_key,
public_key: public_key,
passphrase: passphrase,
fingerprint: fingerprint
}
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Packages::Debian::GenerateDistributionKeyService do
let_it_be(:user) { create(:user) }
let(:params) { {} }
subject { described_class.new(current_user: user, params: params) }
let(:response) { subject.execute }
context 'with a user' do
it 'returns an Hash', :aggregate_failures do
expect(GPGME::Ctx).to receive(:new).with(armor: true, offline: true).and_call_original
expect(User).to receive(:random_password).with(no_args).and_call_original
expect(response).to be_a Hash
expect(response.keys).to contain_exactly(:private_key, :public_key, :fingerprint, :passphrase)
expect(response[:private_key]).to start_with('-----BEGIN PGP PRIVATE KEY BLOCK-----')
expect(response[:public_key]).to start_with('-----BEGIN PGP PUBLIC KEY BLOCK-----')
expect(response[:fingerprint].length).to eq(40)
expect(response[:passphrase].length).to be > 10
end
end
context 'without a user' do
let(:user) { nil }
it 'raises an ArgumentError' do
expect { response }.to raise_error(ArgumentError, 'Please provide a user')
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