python-pid/archive/repolib/key.py

205 lines
6.9 KiB
Python
Raw Normal View History

2023-02-23 21:05:14 +01:00
#!/usr/bin/python3
"""
Copyright (c) 2022, Ian Santopietro
All rights reserved.
This file is part of RepoLib.
RepoLib is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
RepoLib is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with RepoLib. If not, see <https://www.gnu.org/licenses/>.
"""
import logging
import shutil
import dbus
import gnupg
from pathlib import Path
from urllib import request
from . import util
SKS_KEYSERVER = 'https://keyserver.ubuntu.com/'
SKS_KEYLOOKUP_PATH = 'pks/lookup?op=get&options=mr&exact=on&search=0x'
class KeyFileError(util.RepoError):
""" Exceptions related to apt key files."""
def __init__(self, *args, code=1, **kwargs):
"""Exceptions related to apt key files.
Arguments:
code (:obj:`int`, optional, default=1): Exception error code.
"""
super().__init__(*args, **kwargs)
self.code = code
class SourceKey:
"""A signing key for an apt source."""
def __init__(self, name:str = '') -> None:
self.log = logging.getLogger(__name__)
self.tmp_path = Path()
self.path = Path()
self.gpg = gnupg.GPG()
self.data = b''
if name:
self.reset_path(name=name)
self.setup_gpg()
def reset_path(self, name: str = '', path:str = '', suffix: str = 'archive-keyring') -> None:
"""Set the path for this key
Arguments:
suffix(str): The suffix to append to the end of the name to get the
file name (default: 'archive-keyring')
name(str): The name of the source
path(str): The entire path to the key
"""
self.log.info('Setting path')
if not name and not path:
raise KeyFileError('A name is required to set the path for this key')
if name:
file_name = f'{name}-{suffix}.gpg'
self.tmp_path = util.TEMP_DIR / file_name
self.path = util.KEYS_DIR / file_name
elif path:
self.path = Path(path)
self.tmp_path = util.TEMP_DIR / self.path.name
self.setup_gpg()
self.log.debug('Key Path: %s', self.path)
self.log.debug('Temp Path: %s', self.tmp_path)
def setup_gpg(self) -> None:
"""Set up the GPG object for this key."""
self.log.info('Setting up GPG')
self.log.debug('Copying %s to %s', self.path, self.tmp_path)
try:
shutil.copy2(self.path, self.tmp_path)
except FileNotFoundError:
pass
self.gpg = gnupg.GPG(keyring=str(self.tmp_path))
self.log.debug('GPG Setup: %s', self.gpg.keyring)
def save_gpg(self) -> None:
"""Saves the key to disk."""
self.log.info('Saving key file %s from %s', self.path, self.tmp_path)
self.log.debug('Key contents: %s', self.gpg.list_keys())
self.log.debug('Temp key exists? %s', self.tmp_path.exists())
if not util.KEYS_DIR.exists():
try:
util.KEYS_DIR.mkdir(parents=True)
except PermissionError:
self.log.error(
'Key destination path does not exist and cannot be created '
'Failures expected now.'
)
try:
shutil.copy(self.tmp_path, self.path)
except PermissionError:
bus = dbus.SystemBus()
privileged_object = bus.get_object('org.pop_os.repolib', '/Repo')
privileged_object.install_signing_key(
str(self.tmp_path),
str(self.path)
)
def delete_key(self) -> None:
"""Deletes the key file from disk."""
try:
self.tmp_path.unlink()
self.path.unlink()
except PermissionError:
bus = dbus.SystemBus()
privileged_object = bus.get_object('org.pop_os.repolib', '/Repo')
privileged_object.delete_signing_key(str(self.path))
except FileNotFoundError:
pass
def load_key_data(self, **kwargs) -> None:
"""Loads the key data from disk into the object for processing.
Each of the keyword options specifies one place to look to import key
data. Once one is successfully imported, the method returns, so passing
multiple won't import multiple keys.
Keyword Arguments:
raw(bytes): Raw data to import to the keyring
ascii(str): ASCII-armored key data to import directly
url(str): A URL to download key data from
fingerprint(str): A key fingerprint to download from `keyserver`
keyserver(str): A keyserver to download from.
keypath(str): The path on the keyserver from which to download.
NOTE: The keyserver and keypath args only affect the operation of the
`fingerprint` keyword.
"""
if self.path.exists():
with open(self.path, mode='rb') as keyfile:
self.data = keyfile.read()
return
self.tmp_path.touch()
if 'raw' in kwargs:
self.data = kwargs['raw']
self.gpg.import_keys(self.data)
return
if 'ascii' in kwargs:
self.gpg.import_keys(kwargs['ascii'])
if self.tmp_path.exists():
with open(self.tmp_path, mode='rb') as keyfile:
self.data = keyfile.read()
return
if 'url' in kwargs:
req = request.Request(kwargs['url'])
with request.urlopen(req) as response:
self.data = response.read().decode('UTF-8')
self.gpg.import_keys(self.data)
return
if 'fingerprint' in kwargs:
if not 'keyserver' in kwargs:
kwargs['keyserver'] = SKS_KEYSERVER
if not 'keypath' in kwargs:
kwargs['keypath'] = SKS_KEYLOOKUP_PATH
key_url = kwargs['keyserver'] + kwargs['keypath'] + kwargs['fingerprint']
req = request.Request(key_url)
with request.urlopen(req) as response:
self.data = response.read().decode('UTF-8')
self.gpg.import_keys(self.data)
return
raise TypeError(
f'load_key_data() got an unexpected keyword argument "{kwargs.keys()}',
' Expected keyword arguments are: [raw, ascii, url, fingerprint]'
)