| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| """Handle GnuPG keys used to trust signed repositories.""" |
|
|
| from __future__ import print_function |
|
|
| import errno |
| import os |
| import os.path |
| import shutil |
| import subprocess |
| import sys |
| import tempfile |
|
|
| import apt_pkg |
| from apt_pkg import gettext as _ |
|
|
| from typing import List, Optional, Tuple |
|
|
|
|
| class AptKeyError(Exception): |
| pass |
|
|
|
|
| class AptKeyIDTooShortError(AptKeyError): |
| """Internal class do not rely on it.""" |
|
|
|
|
| class TrustedKey(object): |
|
|
| """Represents a trusted key.""" |
|
|
| def __init__(self, name, keyid, date): |
| |
| self.raw_name = name |
| |
| self.name = _(name) |
| self.keyid = keyid |
| self.date = date |
|
|
| def __str__(self): |
| |
| return "%s\n%s %s" % (self.name, self.keyid, self.date) |
|
|
|
|
| def _call_apt_key_script(*args, **kwargs): |
| |
| """Run the apt-key script with the given arguments.""" |
| conf = None |
| cmd = [apt_pkg.config.find_file("Dir::Bin::Apt-Key", "/usr/bin/apt-key")] |
| cmd.extend(args) |
| env = os.environ.copy() |
| env["LANG"] = "C" |
| env["APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE"] = "1" |
| try: |
| if apt_pkg.config.find_dir("Dir") != "/": |
| |
| |
| |
| |
| conf = tempfile.NamedTemporaryFile( |
| prefix="apt-key", suffix=".conf") |
| conf.write(apt_pkg.config.dump().encode("UTF-8")) |
| conf.flush() |
| env["APT_CONFIG"] = conf.name |
| proc = subprocess.Popen(cmd, env=env, universal_newlines=True, |
| stdin=subprocess.PIPE, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE) |
|
|
| stdin = kwargs.get("stdin", None) |
|
|
| output, stderr = proc.communicate(stdin) |
|
|
| if proc.returncode: |
| raise AptKeyError( |
| "The apt-key script failed with return code %s:\n" |
| "%s\n" |
| "stdout: %s\n" |
| "stderr: %s" % ( |
| proc.returncode, " ".join(cmd), output, stderr)) |
| elif stderr: |
| sys.stderr.write(stderr) |
|
|
| return output.strip() |
| finally: |
| if conf is not None: |
| conf.close() |
|
|
|
|
| def add_key_from_file(filename): |
| |
| """Import a GnuPG key file to trust repositores signed by it. |
| |
| Keyword arguments: |
| filename -- the absolute path to the public GnuPG key file |
| """ |
| if not os.path.abspath(filename): |
| raise AptKeyError("An absolute path is required: %s" % filename) |
| if not os.access(filename, os.R_OK): |
| raise AptKeyError("Key file cannot be accessed: %s" % filename) |
| _call_apt_key_script("add", filename) |
|
|
|
|
| def add_key_from_keyserver(keyid, keyserver): |
| |
| """Import a GnuPG key file to trust repositores signed by it. |
| |
| Keyword arguments: |
| keyid -- the long keyid (fingerprint) of the key, e.g. |
| A1BD8E9D78F7FE5C3E65D8AF8B48AD6246925553 |
| keyserver -- the URL or hostname of the key server |
| """ |
| tmp_keyring_dir = tempfile.mkdtemp() |
| try: |
| _add_key_from_keyserver(keyid, keyserver, tmp_keyring_dir) |
| except Exception: |
| raise |
| finally: |
| |
| |
| def onerror(func, path, exc_info): |
| |
| if (isinstance(exc_info[1], OSError) and |
| exc_info[1].errno == errno.ENOENT): |
| return |
| raise |
|
|
| shutil.rmtree(tmp_keyring_dir, onerror=onerror) |
|
|
|
|
| def _add_key_from_keyserver(keyid, keyserver, tmp_keyring_dir): |
| |
| if len(keyid.replace(" ", "").replace("0x", "")) < (160 / 4): |
| raise AptKeyIDTooShortError( |
| "Only fingerprints (v4, 160bit) are supported") |
| |
| tmp_secret_keyring = os.path.join(tmp_keyring_dir, "secring.gpg") |
| tmp_keyring = os.path.join(tmp_keyring_dir, "pubring.gpg") |
| |
| gpg_default_options = [ |
| "gpg", |
| "--no-default-keyring", "--no-options", |
| "--homedir", tmp_keyring_dir, |
| ] |
| |
| res = subprocess.call(gpg_default_options + [ |
| "--secret-keyring", tmp_secret_keyring, |
| "--keyring", tmp_keyring, |
| "--keyserver", keyserver, |
| "--recv", keyid, |
| ]) |
| if res != 0: |
| raise AptKeyError("recv from '%s' failed for '%s'" % ( |
| keyserver, keyid)) |
| |
| |
| |
| |
| |
|
|
| |
| |
| tmp_export_keyring = os.path.join(tmp_keyring_dir, "export-keyring.gpg") |
| res = subprocess.call(gpg_default_options + [ |
| "--keyring", tmp_keyring, |
| "--output", tmp_export_keyring, |
| "--export", keyid, |
| ]) |
| if res != 0: |
| raise AptKeyError("export of '%s' failed", keyid) |
| |
| |
| |
| output = subprocess.Popen( |
| gpg_default_options + [ |
| "--keyring", tmp_export_keyring, |
| "--fingerprint", |
| "--batch", |
| "--fixed-list-mode", |
| "--with-colons", |
| ], |
| stdout=subprocess.PIPE, |
| universal_newlines=True).communicate()[0] |
| got_fingerprint = None |
| for line in output.splitlines(): |
| if line.startswith("fpr:"): |
| got_fingerprint = line.split(":")[9] |
| |
| break |
| |
| |
| signing_key_fingerprint = keyid.replace("0x", "").upper() |
| if got_fingerprint != signing_key_fingerprint: |
| |
| |
| raise AptKeyError( |
| "recv from '%s' failed for '%s'" % ( |
| keyserver, signing_key_fingerprint)) |
| |
| add_key_from_file(tmp_export_keyring) |
|
|
|
|
| def add_key(content): |
| |
| """Import a GnuPG key to trust repositores signed by it. |
| |
| Keyword arguments: |
| content -- the content of the GnuPG public key |
| """ |
| _call_apt_key_script("adv", "--quiet", "--batch", |
| "--import", "-", stdin=content) |
|
|
|
|
| def remove_key(fingerprint): |
| |
| """Remove a GnuPG key to no longer trust repositores signed by it. |
| |
| Keyword arguments: |
| fingerprint -- the fingerprint identifying the key |
| """ |
| _call_apt_key_script("rm", fingerprint) |
|
|
|
|
| def export_key(fingerprint): |
| |
| """Return the GnuPG key in text format. |
| |
| Keyword arguments: |
| fingerprint -- the fingerprint identifying the key |
| """ |
| return _call_apt_key_script("export", fingerprint) |
|
|
|
|
| def update(): |
| |
| """Update the local keyring with the archive keyring and remove from |
| the local keyring the archive keys which are no longer valid. The |
| archive keyring is shipped in the archive-keyring package of your |
| distribution, e.g. the debian-archive-keyring package in Debian. |
| """ |
| return _call_apt_key_script("update") |
|
|
|
|
| def net_update(): |
| |
| """Work similar to the update command above, but get the archive |
| keyring from an URI instead and validate it against a master key. |
| This requires an installed wget(1) and an APT build configured to |
| have a server to fetch from and a master keyring to validate. APT |
| in Debian does not support this command and relies on update |
| instead, but Ubuntu's APT does. |
| """ |
| return _call_apt_key_script("net-update") |
|
|
|
|
| def list_keys(): |
| |
| """Returns a list of TrustedKey instances for each key which is |
| used to trust repositories. |
| """ |
| |
| |
| output = _call_apt_key_script("adv", "--with-colons", "--batch", |
| "--fixed-list-mode", "--list-keys") |
| res = [] |
| for line in output.split("\n"): |
| fields = line.split(":") |
| if fields[0] == "pub": |
| keyid = fields[4] |
| if fields[0] == "uid": |
| uid = fields[9] |
| creation_date = fields[5] |
| key = TrustedKey(uid, keyid, creation_date) |
| res.append(key) |
| return res |
|
|
|
|
| if __name__ == "__main__": |
| |
| |
| lambda: _("Ubuntu Archive Automatic Signing Key <ftpmaster@ubuntu.com>") |
| lambda: _("Ubuntu CD Image Automatic Signing Key <cdimage@ubuntu.com>") |
|
|
| apt_pkg.init() |
| for trusted_key in list_keys(): |
| print(trusted_key) |
|
|