【问题标题】:Download key with `gpg --recv-key` and simultaneously check fingerprint in a script使用 `gpg --recv-key` 下载密钥并同时在脚本中检查指纹
【发布时间】:2014-10-06 13:51:11
【问题描述】:

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

我想将它与apt-key 结合使用以将 PPA 添加到 docker 容器中。

【问题讨论】:

    标签: docker apt pgp gnupg


    【解决方案1】:

    解决方案:

    #!/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 中使用的命令(以添加 rust 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 总是引用标准输出,所以我们将使用它。然后我们必须找出--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之后的第一个参数是导入的key的个数。

    在输出中,gpg escapes newlines,因此可以匹配以[GNUPG:] 开头的行,以断言我们在攻击者控制的字符串中不匹配(例如,密钥中的名称字段可能包含 @ 987654335@ 并通过创建匹配来欺骗我们。

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

    由于我们想要匹配输出两次,我们将其保存到一个临时文件中(通过使用mktemp 创建一个空的临时文件并在该文件中重新路由输出)。如果grep 不匹配任何行,则返回非零错误代码。我们可以通过 set -e 指示 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
    

    如何使用 apt 添加存储库密钥: apt-key 具有adv 命令将命令行参数直接交给gpg(运行上述命令而不输出重新路由以查看apt-key 生成的实际gpg 命令)。所以我们可以简单地将gpgapt-key adv 交换来对存储库密钥环进行操作。

    【讨论】:

      【解决方案2】:

      实际上,我遇到过此页面,正在寻找类似于 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 部分和所有 cmets(以 # 开头的行)。此外,如果不在 Dockerfile 中运行,导出 GNUPGHOME 可能会干扰正常的 gpg 行为。为了避免这种情况,要么在子 shell 中运行整个命令 - 用括号 (command) 包裹,要么在之后运行 unset - unset GNUPGHOME。因此,使用 curl 从 keyserver 检索密钥(普通的 http 查询和特定的 URL 参数用于模拟 HKP 协议)并导入到临时密钥环。然后通过使用特定的 KEYID 参数导出它,我们可以证明密钥身份,以防密钥服务器的答案被篡改。密钥被导入到不同的 apt keyring /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

      【讨论】:

        猜你喜欢
        • 2020-02-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-28
        • 2016-11-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多