使用`gpg --recv-key`下载密钥,同时在脚本中检查指纹

时间:2014-10-06 13:51:12

标签: docker apt pgp gnupg

如何通过gpg --recv-key导入密钥并自动检查指纹?理想情况下,我会直接使用gpg --recv-key $fingerprint,但是gpg only recently added a check,所收到的密钥实际上具有正确的指纹,而不是盲目地信任密钥服务器,并且修复程序没有落在我关心的所有发行版中关于(例如Docker Ubuntu Image仍有旧的gpg版本)。

我希望将它与apt-key结合使用,将PPA添加到泊坞窗容器中。

2 个答案:

答案 0 :(得分:3)

解决方案:

#!/bin/bash
set -e
tempName=$(mktemp)
gpg --status-fd 1 --keyserver keyserver.ubuntu.com --recv-keys $fingerprint 1> $tempName 2>/dev/null
grep "^\[GNUPG\:\] IMPORT_OK "[[:digit:]]*" "$fingerprint"$" $tempName
grep "^\[GNUPG\:\] IMPORT_RES 1" $tempName

如果无法下载密钥或密钥服务器返回恶意密钥,则此脚本将返回非零退出代码。注意:恶意密钥仍然存在于gpg密钥环中,因此如果您在Dockerfile之外使用它,您可能希望之后恢复原始密钥环。用于Dockerfile的命令(以生成PPA为例):

RUN echo "deb http://ppa.launchpad.net/hansjorg/rust/ubuntu trusty main" >> /etc/apt/sources.list
RUN echo "deb-src http://ppa.launchpad.net/hansjorg/rust/ubuntu trusty main" >> /etc/apt/sources.list
RUN bash -c 'set -e;tempName=$(mktemp);apt-key adv --status-fd 1 --keyserver keyserver.ubuntu.com --recv-keys C03264CD6CADC10BFD6E708B37FD5E80BD6B6386 1> $tempName 2>/dev/null;grep "^\[GNUPG\:\] IMPORT_OK [[:digit:]]* C03264CD6CADC10BFD6E708B37FD5E80BD6B6386$" $tempName;grep "^\[GNUPG\:\] IMPORT_RES 1" $tempName'

说明:

要考虑的第一个构建块是GnuPGs --status-fd选项。它告诉gpg将机器可读输出写入给定的文件描述符。文件描述符1总是引用stdout,因此我们将使用它。然后我们将不得不找出--status-fd的输出结果。该文档不在联机帮助页中,而是在doc/DETAILS中。示例输出如下所示:

# gpg --status-fd 1 --keyserver keyserver.ubuntu.com --recv-keys BD6B6386 2>/dev/null
[GNUPG:] IMPORTED 37FD5E80BD6B6386 Launchpad PPA for Hans Jørgen Hoel
[GNUPG:] IMPORT_OK 1 C03264CD6CADC10BFD6E708B37FD5E80BD6B6386
[GNUPG:] IMPORT_RES 1 0 1 1 0 0 0 0 0 0 0 0 0 0

因此,我们正在寻找IMPORT_OKIMPORT_RES行。 IMPORT_OK之后的第二个参数是导入密钥的实际指纹。 IMPORT_RES之后的第一个参数是导入的键数。

在输出中,gpg escapes newlines,因此可以匹配以[GNUPG:]开头的行来断言我们在攻击者控制的字符串中不匹配(例如,键中的名称字段)否则可以包含\n[GNUPG:] IMPORT_OK 1 C03264CD6CADC10BFD6E708B37FD5E80BD6B6386]并通过创建匹配来欺骗我们。

使用grep,我们可以通过[GNUPG] sometext匹配以grep "^\[GNUPG\:\]"开头的行,并使用grep "^\[GNUPG\:\] sometext$"^$代表整行。和一行结束)。根据文档,IMPORT_OK后面的任何数字对我们都没问题,因此我们会与"[[:digit:]]*"匹配。因此,作为正则表达式,我们得到"^\[GNUPG\:\] IMPORT_OK "[[:digit:]]*" "$fingerprint"$""^\[GNUPG\:\] IMPORT_RES 1"

由于我们希望将输出匹配两次,我们将其保存到一个临时文件中(通过创建一个带有mktemp的空临时文件并重新路由该文件中的输出)。如果grep与任何行匹配,则返回非零错误代码。我们可以通过指示bash通过set -e中止任何错误来使用它。总的来说,我们最终得到:

set -e
tempName=$(mktemp)
gpg --status-fd 1 --keyserver keyserver.ubuntu.com --recv-keys $fingerprint 1> $tempName 2>/dev/null
grep "^\[GNUPG\:\] IMPORT_OK "[[:digit:]]*" "$fingerprint"$" $tempName
grep "^\[GNUPG\:\] IMPORT_RES 1" $tempName

如何使用apt添加存储库密钥: apt-key使用adv命令将命令行参数直接移交给gpg(运行上述命令而不输出重新路由,以查看apt-key生成的实际gpg命令)。因此,我们只需与gpg交换apt-key adv即可对存储库密钥环进行操作。

答案 1 :(得分:0)

实际上我已经遇到过这个页面,正在寻找类似于OP描述的问题的解决方案。提到gpg --recv-key $fingerprint解决方案是可以的,并且应该在大多数常见发行版中得到gpg的支持。但在我的情况下,我有另一个限制。不久之前,Gpg的网络通信功能已移至单独的包dirmngr。从Yakkety Yak(即this bug)开始,默认的Ubuntu安装中不包含该软件包,您必须手动安装它才能使上述命令工作。只要我的Docker镜像是非常小的Ubuntu设置,我就试图避免这种情况。所以我找到了一些可能有用的替代解决方案。即我正在以这种方式从位于nginx.org的存储库安装Nginx服务器:

RUN set -Eeuxo pipefail; \
    # The keyring is placed in temporary directory
    export GNUPGHOME="$(mktemp -d)"; \
    # Nginx public key (used for signing packages and repositories)
    NGINX_GPGKEY=0x573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; \
    # Pool of high-available keyservers
    KEYSERVER=ha.pool.sks-keyservers.net:11371; \
    # HKP protocol can be easily represented as HTTP query. The key is imported into temporary keyring.
    curl -LfSs "http://$KEYSERVER/pks/lookup?op=get&search=$NGINX_GPGKEY&options=mr&exact=on" | gpg --import -; \
    # Additional check that imported key is the right one before copying it to apt trusted database
    gpg --export "$NGINX_GPGKEY" | apt-key --keyring /etc/apt/trusted.gpg.d/nginx.gpg add -; \
    # Adding nginx.org repository to the sources list
    echo "deb [arch=amd64] http://nginx.org/packages/mainline/ubuntu/ $(. /etc/lsb-release; echo $DISTRIB_CODENAME) nginx" > /etc/apt/sources.list.d/nginx.list; \
    # Installing Nginx
    apt-get update; \
    apt-get install -y --no-install-recommends --no-install-suggests nginx; \
    # Removing apt cached files
    apt-get clean; \
    rm -rf /var/lib/apt/lists/*; \
    # Removing temporary keyring
    rm -rf "$GNUPGHOME"

代码摘自Dockerfile,在shell中运行它删除RUN部分和所有注释(以#开头的行)。此外,如果没有在Dockerfile中运行导出GNUPGHOME可能会干扰正常的gpg行为。为了避免这种情况,要么在子shell中运行整个命令 - 用括号(command)换行,要么在之后运行未设置 - unset GNUPGHOME。因此,使用curl从密钥服务器检索密钥(普通的http查询和特定的URL参数用于模拟HKP协议)并导入临时密钥环。然后通过使用特定的KEYID参数导出它,我们在密钥服务器的答案被篡改的情况下校对密钥身份。密钥将导入到不同的密钥环/etc/apt/trusted.gpg.d/nginx.gpg,以便以后通过删除文件轻松删除它。实际上没有必要使用apt-key,您可以改为将gpg --export输出重定向到文件:gpg --export "$NGINX_GPGKEY" > /etc/apt/trusted.gpg.d/nginx.gpg

命令是不言自明的,只是一些额外的评论:可以找到HKP协议描述herethere也很好地解释为什么要使用初始set