Skip to content

Commit

Permalink
Cleaning up the whole GNUPGHOME mess
Browse files Browse the repository at this point in the history
  • Loading branch information
gershnik committed Jun 7, 2024
1 parent b8e0b0b commit ff8c2ce
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 48 deletions.
6 changes: 1 addition & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ jobs:
- name: Preprare Keys
shell: bash
run: |
export GNUPGHOME=$GITHUB_WORKSPACE/.gpg
mkdir -p $GNUPGHOME
echo -n "${{ secrets.PGP_KEY }}" | base64 --decode | gpg --import --batch --pinentry-mode=loopback
echo -n "${{ secrets.BSD_KEY }}" > $GITHUB_WORKSPACE/bsd-key
Expand All @@ -52,7 +50,5 @@ jobs:

- name: Test
shell: bash
run: |
export GNUPGHOME=$GITHUB_WORKSPACE/.gpg
nox --sessions test --force-python ${{ steps.setup-python.outputs.python-path }}
run: nox --sessions test --force-python ${{ steps.setup-python.outputs.python-path }}

6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ dist = repo.add_distribution('jammy',
repo.assign_package(package1, dist, component='main')
repo.assign_package(package2, dist, component='main')

signer = PgpSigner(Path.home() / '.gnupg', 'name_of_key_to_use', 'password_of_that_key')
signer = PgpSigner('name_of_key_to_use', 'password_of_that_key')

repo.export(Path('/path/of/new/repo'), signer)

Expand All @@ -88,7 +88,7 @@ repo = RpmRepo()
repo.add_package(Path('/path/to/awesome-3.14-1.el9.x86_64.rpm'))
repo.add_package(Path('/path/to/awesome-3.14-1.el9.aarch64.rpm'))

signer = PgpSigner(Path.home() / '.gnupg', 'name_of_key_to_use', 'password_of_that_key')
signer = PgpSigner('name_of_key_to_use', 'password_of_that_key')

repo.export(Path('/path/of/new/repo'), signer)

Expand All @@ -105,7 +105,7 @@ repo = PacmanRepo('myrepo')
# otherwise new signature will be generated at export time
repo.add_package(Path('/path/to/awesome-3.14-1-x86_64.pkg.tar.zst'))

signer = PgpSigner(Path.home() / '.gnupg', 'name_of_key_to_use', 'password_of_that_key')
signer = PgpSigner('name_of_key_to_use', 'password_of_that_key')

repo.export(Path('/path/of/new/repo'), signer)

Expand Down
81 changes: 47 additions & 34 deletions src/repopulator/pgp_signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import subprocess
from pathlib import Path
from typing import Optional

class PgpSigner:
"""Implementation of PGP signing
Expand All @@ -20,18 +21,18 @@ class PgpSigner:
You are required to supply key name and password for signing. Signing is done non-interactively without any
user prompts.
"""
def __init__(self, homedir: Path, key_name: str, key_pwd: str):
def __init__(self, *, key_name: str, key_pwd: str, homedir: Optional[str | Path] = None):
"""Constructor for PgpSigner class
Args:
homedir: GPG home directory. This is normally Path.home() / '.gpg' but can be set to anything for
custom configuration
key_name: name or identifier of the key to use
key_pwd: password of the key
homedir: GPG home directory. If not specified the gpg defaults are used (including
honoring GNUPGHOME environment variable)
"""
self.__homedir = homedir
self.__keyName = key_name
self.__keyPwd = key_pwd
self.__key_name = key_name
self.__key_pwd = key_pwd

def sign_external(self, path: Path, sig_path: Path):
"""Signs a given file producing text (aka "armored") signature in a separate file
Expand All @@ -40,14 +41,17 @@ def sign_external(self, path: Path, sig_path: Path):
path: file to sign
sig_path: path to write the signature to
"""
subprocess.run(['gpg', '--batch', '--quiet', '--pinentry-mode=loopback',
'--homedir', self.__homedir,
'--armor', '--detach-sign', '--sign',
'--default-key', self.__keyName,
'--passphrase', self.__keyPwd,
'--digest-algo', 'sha512',
'-o', sig_path, path
], check=True)
command = ['gpg', '--batch', '--quiet', '--pinentry-mode=loopback']
if self.__homedir is not None:
command += ['--homedir', self.__homedir]
command += [
'--armor', '--detach-sign', '--sign',
'--default-key', self.__key_name,
'--passphrase', self.__key_pwd,
'--digest-algo', 'sha512',
'-o', sig_path, path
]
subprocess.run(command, check=True)

def binary_sign_external(self, path: Path, sig_path: Path):
"""Signs a given file producing binary signature in a separate file
Expand All @@ -56,14 +60,17 @@ def binary_sign_external(self, path: Path, sig_path: Path):
path: file to sign
sig_path: path to write the signature to
"""
subprocess.run(['gpg', '--batch', '--quiet', '--pinentry-mode=loopback',
'--homedir', self.__homedir,
'--detach-sign', '--sign',
'--default-key', self.__keyName,
'--passphrase', self.__keyPwd,
'--digest-algo', 'sha512',
'-o', sig_path, path
], check=True)
command = ['gpg', '--batch', '--quiet', '--pinentry-mode=loopback']
if self.__homedir is not None:
command += ['--homedir', self.__homedir]
command += [
'--detach-sign', '--sign',
'--default-key', self.__key_name,
'--passphrase', self.__key_pwd,
'--digest-algo', 'sha512',
'-o', sig_path, path
]
subprocess.run(command, check=True)

def sign_inline(self, path: Path, out_path: Path):
"""Adds a signature to a given text file
Expand All @@ -72,23 +79,29 @@ def sign_inline(self, path: Path, out_path: Path):
path: file to sign
out_path: path to write the signed content to
"""
subprocess.run(['gpg', '--batch', '--quiet', '--pinentry-mode=loopback',
'--homedir', self.__homedir,
'--armor', '--detach-sign', '--sign', '--clearsign',
'--default-key', self.__keyName,
'--passphrase', self.__keyPwd,
'--digest-algo', 'sha512',
'-o', out_path, path
], check=True)
command = ['gpg', '--batch', '--quiet', '--pinentry-mode=loopback']
if self.__homedir is not None:
command += ['--homedir', self.__homedir]
command += [
'--armor', '--detach-sign', '--sign', '--clearsign',
'--default-key', self.__key_name,
'--passphrase', self.__key_pwd,
'--digest-algo', 'sha512',
'-o', out_path, path
]
subprocess.run(command, check=True)

def export_public_key(self, path: Path):
"""Utility method to export the public key of the signing key into a file
Args:
path: path of the file to write the public key to
"""
subprocess.run(['gpg', '--batch', '--quiet',
'--homedir', self.__homedir,
'--output', path,
'--armor', '--export', self.__keyName
], check=True)
command = ['gpg', '--batch', '--quiet']
if self.__homedir is not None:
command += ['--homedir', self.__homedir]
command += [
'--output', path,
'--armor', '--export', self.__key_name
]
subprocess.run(command, check=True)

9 changes: 3 additions & 6 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,11 @@ def should_populate(request) -> bool:
should_populate: bool = request.config.option.populate_expected
return should_populate

def find_gpg_home():
return subprocess.run(['gpgconf', '--list-dirs', 'homedir'], check=True, capture_output=True).stdout.decode().strip()

@pytest.fixture
def pgp_signer():
return PgpSigner(Path(os.environ.get('GNUPGHOME', find_gpg_home())),
os.environ['PGP_KEY_NAME'],
os.environ['PGP_KEY_PASSWD'])
return PgpSigner(key_name=os.environ['PGP_KEY_NAME'],
key_pwd = os.environ['PGP_KEY_PASSWD'],
homedir=os.environ.get('GNUPGHOME'))

@pytest.fixture
def pki_signer():
Expand Down

0 comments on commit ff8c2ce

Please sign in to comment.