def import_key(key):
    """Import an ASCII Armor key.

    A Radix64 format keyid is also supported for backwards
    compatibility, but should never be used; the key retrieval
    mechanism is insecure and subject to man-in-the-middle attacks
    voiding all signature checks using that key.

    :param keyid: The key in ASCII armor format,
                  including BEGIN and END markers.
    :raises: GPGKeyError if the key could not be imported
    """
    key = key.strip()
    if '-' in key or '\n' in key:
        # Send everything not obviously a keyid to GPG to import, as
        # we trust its validation better than our own. eg. handling
        # comments before the key.
        log("PGP key found (looks like ASCII Armor format)", level=DEBUG)
        if ('-----BEGIN PGP PUBLIC KEY BLOCK-----' in key
                and '-----END PGP PUBLIC KEY BLOCK-----' in key):
            log("Importing ASCII Armor PGP key", level=DEBUG)
            with NamedTemporaryFile() as keyfile:
                with open(keyfile.name, 'w') as fd:
                    fd.write(key)
                    fd.write("\n")
                cmd = ['apt-key', 'add', keyfile.name]
                try:
                    subprocess.check_call(cmd)
                except subprocess.CalledProcessError:
                    error = "Error importing PGP key '{}'".format(key)
                    log(error)
                    raise GPGKeyError(error)
        else:
            raise GPGKeyError("ASCII armor markers missing from GPG key")
    else:
        # We should only send things obviously not a keyid offsite
        # via this unsecured protocol, as it may be a secret or part
        # of one.
        log("PGP key found (looks like Radix64 format)", level=WARNING)
        log(
            "INSECURLY importing PGP key from keyserver; "
            "full key not provided.",
            level=WARNING)
        cmd = [
            'apt-key', 'adv', '--keyserver', 'hkp://keyserver.ubuntu.com:80',
            '--recv-keys', key
        ]
        try:
            _run_with_retries(cmd)
        except subprocess.CalledProcessError:
            error = "Error importing PGP key '{}'".format(key)
            log(error)
            raise GPGKeyError(error)
예제 #2
0
def _get_keyid_by_gpg_key(key_material):
    """Get a GPG key fingerprint by GPG key material.
    Gets a GPG key fingerprint (40-digit, 160-bit) by the ASCII armor-encoded
    or binary GPG key material. Can be used, for example, to generate file
    names for keys passed via charm options.

    :param key_material: ASCII armor-encoded or binary GPG key material
    :type key_material: bytes
    :raises: GPGKeyError if invalid key material has been provided
    :returns: A GPG key fingerprint
    :rtype: str
    """
    # Use the same gpg command for both Xenial and Bionic
    cmd = 'gpg --with-colons --with-fingerprint'
    ps = subprocess.Popen(cmd.split(),
                          stdout=subprocess.PIPE,
                          stderr=subprocess.PIPE,
                          stdin=subprocess.PIPE)
    out, err = ps.communicate(input=key_material)
    if six.PY3:
        out = out.decode('utf-8')
        err = err.decode('utf-8')
    if 'gpg: no valid OpenPGP data found.' in err:
        raise GPGKeyError('Invalid GPG key material provided')
    # from gnupg2 docs: fpr :: Fingerprint (fingerprint is in field 10)
    return re.search(r"^fpr:{9}([0-9A-F]{40}):$", out, re.MULTILINE).group(1)
예제 #3
0
def _get_keyid_by_gpg_key(key_material):
    """Get a GPG key fingerprint by GPG key material.
    Gets a GPG key fingerprint (40-digit, 160-bit) by the ASCII armor-encoded
    or binary GPG key material. Can be used, for example, to generate file
    names for keys passed via charm options.

    :param key_material: ASCII armor-encoded or binary GPG key material
    :type key_material: bytes
    :raises: GPGKeyError if invalid key material has been provided
    :returns: A GPG key fingerprint
    :rtype: str
    """
    # trusty, xenial and bionic handling differs due to gpg 1.x to 2.x change
    release = get_distrib_codename()
    is_gpgv2_distro = CompareHostReleases(release) >= "bionic"
    if is_gpgv2_distro:
        # --import is mandatory, otherwise fingerprint is not printed
        cmd = 'gpg --with-colons --import-options show-only --import --dry-run'
    else:
        cmd = 'gpg --with-colons --with-fingerprint'
    ps = subprocess.Popen(cmd.split(),
                          stdout=subprocess.PIPE,
                          stderr=subprocess.PIPE,
                          stdin=subprocess.PIPE)
    out, err = ps.communicate(input=key_material)
    if six.PY3:
        out = out.decode('utf-8')
        err = err.decode('utf-8')
    if 'gpg: no valid OpenPGP data found.' in err:
        raise GPGKeyError('Invalid GPG key material provided')
    # from gnupg2 docs: fpr :: Fingerprint (fingerprint is in field 10)
    return re.search(r"^fpr:{9}([0-9A-F]{40}):$", out, re.MULTILINE).group(1)
예제 #4
0
def import_key(keyid):
    """Import a key in either ASCII Armor or Radix64 format.

    `keyid` is either the keyid to fetch from a PGP server, or
    the key in ASCII armor foramt.

    :param keyid: String of key (or key id).
    :raises: GPGKeyError if the key could not be imported
    """
    key = keyid.strip()
    if (key.startswith('-----BEGIN PGP PUBLIC KEY BLOCK-----')
            and key.endswith('-----END PGP PUBLIC KEY BLOCK-----')):
        log("PGP key found (looks like ASCII Armor format)", level=DEBUG)
        log("Importing ASCII Armor PGP key", level=DEBUG)
        with NamedTemporaryFile() as keyfile:
            with open(keyfile.name, 'w') as fd:
                fd.write(key)
                fd.write("\n")
            cmd = ['apt-key', 'add', keyfile.name]
            try:
                subprocess.check_call(cmd)
            except subprocess.CalledProcessError:
                error = "Error importing PGP key '{}'".format(key)
                log(error)
                raise GPGKeyError(error)
    else:
        log("PGP key found (looks like Radix64 format)", level=DEBUG)
        log("Importing PGP key from keyserver", level=DEBUG)
        cmd = [
            'apt-key', 'adv', '--keyserver', 'hkp://keyserver.ubuntu.com:80',
            '--recv-keys', key
        ]
        try:
            subprocess.check_call(cmd)
        except subprocess.CalledProcessError:
            error = "Error importing PGP key '{}'".format(key)
            log(error)
            raise GPGKeyError(error)
예제 #5
0
def import_key(key):
    """Import an ASCII Armor key.

    A Radix64 format keyid is also supported for backwards
    compatibility. In this case Ubuntu keyserver will be
    queried for a key via HTTPS by its keyid. This method
    is less preferable because https proxy servers may
    require traffic decryption which is equivalent to a
    man-in-the-middle attack (a proxy server impersonates
    keyserver TLS certificates and has to be explicitly
    trusted by the system).

    :param key: A GPG key in ASCII armor format,
                  including BEGIN and END markers or a keyid.
    :type key: (bytes, str)
    :raises: GPGKeyError if the key could not be imported
    """
    key = key.strip()
    if '-' in key or '\n' in key:
        # Send everything not obviously a keyid to GPG to import, as
        # we trust its validation better than our own. eg. handling
        # comments before the key.
        log("PGP key found (looks like ASCII Armor format)", level=DEBUG)
        if ('-----BEGIN PGP PUBLIC KEY BLOCK-----' in key
                and '-----END PGP PUBLIC KEY BLOCK-----' in key):
            log("Writing provided PGP key in the binary format", level=DEBUG)
            if six.PY3:
                key_bytes = key.encode('utf-8')
            else:
                key_bytes = key
            key_name = _get_keyid_by_gpg_key(key_bytes)
            key_gpg = _dearmor_gpg_key(key_bytes)
            _write_apt_gpg_keyfile(key_name=key_name, key_material=key_gpg)
        else:
            raise GPGKeyError("ASCII armor markers missing from GPG key")
    else:
        log("PGP key found (looks like Radix64 format)", level=WARNING)
        log(
            "SECURELY importing PGP key from keyserver; "
            "full key not provided.",
            level=WARNING)
        # as of bionic add-apt-repository uses curl with an HTTPS keyserver URL
        # to retrieve GPG keys. `apt-key adv` command is deprecated as is
        # apt-key in general as noted in its manpage. See lp:1433761 for more
        # history. Instead, /etc/apt/trusted.gpg.d is used directly to drop
        # gpg
        key_asc = _get_key_by_keyid(key)
        # write the key in GPG format so that apt-key list shows it
        key_gpg = _dearmor_gpg_key(key_asc)
        _write_apt_gpg_keyfile(key_name=key, key_material=key_gpg)
예제 #6
0
def _dearmor_gpg_key(key_asc):
    """Converts a GPG key in the ASCII armor format to the binary format.

    :param key_asc: A GPG key in ASCII armor format.
    :type key_asc: (str, bytes)
    :returns: A GPG key in binary format
    :rtype: (str, bytes)
    :raises: GPGKeyError
    """
    ps = subprocess.Popen(['gpg', '--dearmor'],
                          stdout=subprocess.PIPE,
                          stderr=subprocess.PIPE,
                          stdin=subprocess.PIPE)
    out, err = ps.communicate(input=key_asc)
    # no need to decode output as it is binary (invalid utf-8), only error
    err = err.decode('utf-8')
    if 'gpg: no valid OpenPGP data found.' in err:
        raise GPGKeyError('Invalid GPG key material. Check your network setup'
                          ' (MTU, routing, DNS) and/or proxy server settings'
                          ' as well as destination keyserver status.')
    else:
        return out