| Viewing file:  auth.py (9.35 KB)      -rw-r--r-- Select action/file-type:
 
  (+) |  (+) |  (+) | Code (+) | Session (+) |  (+) | SDB (+) |  (+) |  (+) |  (+) |  (+) |  (+) | 
 
#!/usr/bin/env python# -*- coding: utf-8 -*-
 # auth - authentication key management
 #
 #  Copyright (c) 2004 Canonical
 #  Copyright (c) 2012 Sebastian Heinlein
 #
 #  Author: Michael Vogt <mvo@debian.org>
 #          Sebastian Heinlein <devel@glatzor.de>
 #
 #  This program is free software; you can redistribute it and/or
 #  modify it under the terms of the GNU General Public License as
 #  published by the Free Software Foundation; either version 2 of the
 #  License, or (at your option) any later version.
 #
 #  This program 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 General Public License for more details.
 #
 #  You should have received a copy of the GNU General Public License
 #  along with this program; if not, write to the Free Software
 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 #  USA
 """Handle GnuPG keys used to trust signed repositories."""
 
 from __future__ import print_function
 
 import os
 import os.path
 import shutil
 import subprocess
 import sys
 import tempfile
 
 import apt_pkg
 from apt_pkg import gettext as _
 
 
 class AptKeyError(Exception):
 pass
 
 
 class TrustedKey(object):
 
 """Represents a trusted key."""
 
 def __init__(self, name, keyid, date):
 self.raw_name = name
 # Allow to translated some known keys
 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"
 try:
 if apt_pkg.config.find_dir("Dir") != "/":
 # If the key is to be installed into a chroot we have to export the
 # configuration from the chroot to the apt-key script by using
 # a temporary APT_CONFIG file. The apt-key script uses apt-config
 # shell internally
 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)
 
 content = kwargs.get("stdin", None)
 # py2 needs this encoded, py3.3 will crash if it is
 if sys.version_info.major < 3 and isinstance(content, unicode):
 content = content.encode("utf-8")
 
 output, stderr = proc.communicate(content)
 
 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)    # Forward 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:
 raise
 finally:
 shutil.rmtree(tmp_keyring_dir)
 
 
 def _add_key_from_keyserver(keyid, keyserver, tmp_keyring_dir):
 if len(keyid) < (160 / 8):
 raise AptKeyError("Only long keyids (v4, 160bit) are supported")
 # create a temp keyring dir
 tmp_secret_keyring = os.path.join(tmp_keyring_dir, "secring.gpg")
 tmp_keyring = os.path.join(tmp_keyring_dir, "pubring.gpg")
 # default options for gpg
 gpg_default_options = [
 "gpg",
 "--no-default-keyring", "--no-options",
 "--homedir", tmp_keyring_dir,
 ]
 # download the key to a temp keyring first
 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))
 # FIXME:
 # - with gnupg 1.4.18 the downloaded key is actually checked(!),
 #   i.e. gnupg will not import anything that the server sends
 #   into the keyring, so the below checks are now redundant *if*
 #   gnupg 1.4.18 is used
 
 # now export again using the long key id (to ensure that there is
 # really only this one key in our keyring) and not someone MITM us
 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)
 # now verify the fingerprint, this is probably redundant as we
 # exported by the fingerprint in the previous command but its
 # still good paranoia
 output = subprocess.Popen(
 gpg_default_options + [
 "--keyring", tmp_export_keyring,
 "--fingerprint",
 "--batch",
 "--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]
 # stop after the first to ensure no subkey trickery
 break
 # strip the leading "0x" is there is one and uppercase (as this is
 # what gnupg is using)
 signing_key_fingerprint = keyid.replace("0x", "").upper()
 if got_fingerprint != signing_key_fingerprint:
 # make the error match what gnupg >= 1.4.18 will output when
 # it checks the key itself before importing it
 raise AptKeyError(
 "recv from '%s' failed for '%s'" % (
 keyserver, signing_key_fingerprint))
 # finally add it
 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.
 """
 # The output of `apt-key list` is difficult to parse since the
 # --with-colons parameter isn't user
 output = _call_apt_key_script("adv", "--with-colons", "--batch",
 "--list-keys")
 res = []
 for line in output.split("\n"):
 fields = line.split(":")
 if fields[0] == "pub":
 key = TrustedKey(fields[9], fields[4][-8:], fields[5])
 res.append(key)
 return res
 
 if __name__ == "__main__":
 # Add some known keys we would like to see translated so that they get
 # picked up by gettext
 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)
 
 |