模块 Gem::Security

签名 gem 包

Gem::Security 实现了 gem 包的加密签名。下面的部分是使用签名 gem 包和生成你自己的签名 gem 包的分步指南。

演练

构建你的证书

为了开始签名你的 gem 包,你需要构建一个私钥和一个自签名证书。以下是如何操作:

# build a private key and certificate for yourself:
$ gem cert --build you@example.com

这可能需要几秒钟到一两分钟的时间,具体取决于你的计算机速度(公钥算法并不是世界上最快的加密算法)。完成后,你将在当前目录中看到文件 “gem-private_key.pem” 和 “gem-public_cert.pem”。

首先:如果你在该目录中还没有密钥和证书,请将这两个文件都移动到 ~/.gem。确保文件权限使其他人无法读取密钥(默认情况下,文件会安全保存)。

隐藏你的私钥;如果它被泄露,其他人可以以你的身份签署软件包(注意:PKI 有办法减轻密钥被盗的风险;稍后会详细介绍)。

签名 Gem 包

在 RubyGems 2 和更新的版本中,签名 gem 包无需额外操作。RubyGems 将自动在你的主目录中找到你的密钥和证书,并使用它们来签名新打包的 gem 包。

如果你的证书不是自签名的(由第三方签名),RubyGems 将尝试从受信任的证书加载证书链。使用 gem cert --add signing_cert.pem 将你的签名者添加为受信任的证书。有关证书链的更多信息,请参见下文。

如果你构建你的 gem 包,它将自动被签名。如果你查看你的 gem 文件内部,你会看到添加了几个新文件:

$ tar tf your-gem-1.0.gem
metadata.gz
metadata.gz.sig # metadata signature
data.tar.gz
data.tar.gz.sig # data signature
checksums.yaml.gz
checksums.yaml.gz.sig # checksums signature

手动签名 gem 包

如果你希望将密钥存储在单独的安全位置,你需要手动设置你的 gem 包进行签名。为此,在打包你的 gem 包之前,在 gemspec 中设置 signing_keycert_chain

s.signing_key = '/secure/path/to/gem-private_key.pem'
s.cert_chain = %w[/secure/path/to/gem-public_cert.pem]

当你使用这些选项设置打包你的 gem 包时,RubyGems 将自动从安全路径加载你的密钥和证书。

签名 gem 包和安全策略

现在让我们验证签名。继续并安装 gem 包,但添加以下选项:-P HighSecurity,像这样:

# install the gem with using the security policy "HighSecurity"
$ sudo gem install your.gem -P HighSecurity

-P 选项设置你的安全策略——我们稍后会讨论这个问题。嗯,这是怎么回事?

$ gem install -P HighSecurity your-gem-1.0.gem
ERROR:  While executing gem ... (Gem::Security::Exception)
    root cert /CN=you/DC=example is not trusted

罪魁祸首是安全策略。RubyGems 有几种不同的安全策略。让我们稍作休息,回顾一下安全策略。以下是可用的安全策略列表,以及每个策略的简要说明:

RubyGems 拒绝安装你闪亮的新签名 gem 包的原因是它来自不受信任的来源。好吧,你的代码是无懈可击的(自然),所以你需要将自己添加为受信任的来源:

# add trusted certificate
gem cert --add ~/.gem/gem-public_cert.pem

你现在已将你的公共证书添加为受信任的来源。现在,你可以安装由你的私钥签名的软件包,而没有任何麻烦。让我们再次尝试上面的安装命令:

# install the gem with using the HighSecurity policy (and this time
# without any shenanigans)
$ gem install -P HighSecurity your-gem-1.0.gem
Successfully installed your-gem-1.0
1 gem installed

这次 RubyGems 将接受你的签名包并开始安装。

在你等待 RubyGems 发挥魔力的同时,请通过运行 gem help cert 查看其他一些安全命令:

Options:
  -a, --add CERT                   Add a trusted certificate.
  -l, --list [FILTER]              List trusted certificates where the
                                   subject contains FILTER
  -r, --remove FILTER              Remove trusted certificates where the
                                   subject contains FILTER
  -b, --build EMAIL_ADDR           Build private key and self-signed
                                   certificate for EMAIL_ADDR
  -C, --certificate CERT           Signing certificate for --sign
  -K, --private-key KEY            Key for --sign or --build
  -A, --key-algorithm ALGORITHM    Select key algorithm for --build from RSA, DSA, or EC. Defaults to RSA.
  -s, --sign CERT                  Signs CERT with the key from -K
                                   and the certificate from -C
  -d, --days NUMBER_OF_DAYS        Days before the certificate expires
  -R, --re-sign                    Re-signs the certificate from -C with the key from -K

我们已经介绍了 --build 选项,而 --add--list--remove 命令似乎相当简单;它们允许你添加、列出和删除受信任证书列表中的证书。但是这个 --sign 选项是什么意思?

证书链

为了回答这个问题,让我们看一下“证书链”,这是我之前提到过的一个概念。自签名证书存在一些问题:首先,自签名证书没有提供太多安全保障。当然,证书上写着 Yukihiro Matsumoto,但是我怎么知道它实际上是由 matz 本人生成和签名的,除非他亲自把证书给我?

第二个问题是可伸缩性。当然,如果有 50 个 gem 包作者,那么我有 50 个受信任的证书,没问题。如果有 500 个 gem 包作者呢?1000 个呢?必须不断添加新的受信任证书很麻烦,而且实际上会通过鼓励 RubyGems 用户盲目信任新证书而使信任系统变得不那么安全。

这就是证书链的用武之地。证书链在颁发证书和子证书之间建立任意长的信任链。因此,我们不按每个开发人员信任证书,而是使用 PKI 证书链的概念来构建逻辑信任层次结构。以下是基于(大致)地理位置的假设信任层次结构的示例:

                    --------------------------
                    | rubygems@rubygems.org |
                    --------------------------
                                |
              -----------------------------------
              |                                 |
  ----------------------------    -----------------------------
  |  seattlerb@seattlerb.org |    | dcrubyists@richkilmer.com |
  ----------------------------    -----------------------------
       |                |                 |             |
---------------   ----------------   -----------   --------------
|   drbrain   |   |   zenspider  |   | pabs@dc |   | tomcope@dc |
---------------   ----------------   -----------   --------------

现在,用户不需要 4 个受信任的证书(drbrain、zenspider、pabs@dc 和 tomecope@dc 各一个),实际上可以使用一个证书,即 “rubygems@rubygems.org” 证书。

它是这样工作的:

我安装了 “rdoc-3.12.gem”,这是一个由 “drbrain” 签名的软件包。我从未听说过 “drbrain”,但他的证书具有 “seattle.rb@seattlerb.org” 证书的有效签名,而该证书又具有 “rubygems@rubygems.org” 证书的有效签名。瞧!此时,我信任由 “drbrain” 签名的软件包要合理得多,因为我可以建立一条到我确实信任的 “rubygems@rubygems.org” 的链。

签名证书

--sign 选项允许所有这一切发生。开发人员使用 --build 选项创建他们的构建证书,然后将其带到他们下一个区域 Ruby 会议(在我们的假设示例中)并签署证书,该证书由持有区域 RubyGems 签名证书的人员在那里签名,该签名证书在下一个 RubyConf 上由顶级 RubyGems 证书的持有者签名。在每个点,颁发者都会运行相同的命令:

# sign a certificate with the specified key and certificate
# (note that this modifies client_cert.pem!)
$ gem cert -K /mnt/floppy/issuer-priv_key.pem -C issuer-pub_cert.pem
   --sign client_cert.pem

然后,已颁发证书的持有者(在本例中为你的朋友 “drbrain”)可以开始使用此签名证书来签名 RubyGems。顺便说一句,为了让其他所有人了解他的新签名证书,“drbrain” 会将他的新签名证书保存为 ~/.gem/gem-public_cert.pem

显然,这种 RubyGems 信任基础设施尚不存在。此外,在“现实世界”中,颁发者实际上是从证书请求生成子证书,而不是对现有证书进行签名。而且,我们假设的基础设施缺少证书撤销系统。这些可以在将来修复……

此时,你应该知道如何执行所有这些新的有趣的事情:

手动验证签名

如果你不信任 RubyGems,可以手动验证 gem 签名:

  1. 获取并解压 gem 包

    gem fetch some_signed_gem
    tar -xf some_signed_gem-1.0.gem
  2. 从 gemspec 获取公钥

    gem spec some_signed_gem-1.0.gem cert_chain | \
      ruby -rpsych -e 'puts Psych.load($stdin)' > public_key.crt
  3. 生成 data.tar.gz 的 SHA1 哈希

    openssl dgst -sha1 < data.tar.gz > my.hash
    
  4. 验证签名

    openssl rsautl -verify -inkey public_key.crt -certin \
      -in data.tar.gz.sig > verified.hash
  5. 将您的哈希值与已验证的哈希值进行比较

    diff -s verified.hash my.hash
  6. 使用 metadata.gz 重复第 5 步和第 6 步

OpenSSL 参考

由 –build 和 –sign 生成的 .pem 文件是 PEM 文件。以下是一些有用的 OpenSSL 命令,用于操作它们

# convert a PEM format X509 certificate into DER format:
# (note: Windows .cer files are X509 certificates in DER format)
$ openssl x509 -in input.pem -outform der -out output.der

# print out the certificate in a human-readable format:
$ openssl x509 -in input.pem -noout -text

您也可以对私钥文件执行相同的操作

# convert a PEM format RSA key into DER format:
$ openssl rsa -in input_key.pem -outform der -out output_key.der

# print out the key in a human readable format:
$ openssl rsa -in input_key.pem -noout -text

Bug/待办事项

原始作者

Paul Duncan <pabs@pablotron.org> pablotron.org/