Help us learn about your current experience with the documentation. Take the survey.

使用 X.509 证书签名提交和标签

  • Tier: Free, Premium, Ultimate
  • Offering: GitLab Self-Managed, GitLab Dedicated

X.509 是一种由公共或私有公钥基础设施 (PKI) 颁发的公钥证书的标准格式。 个人 X.509 证书用于身份验证或签名目的,例如 S/MIME(安全/多用途互联网邮件扩展)。 然而,Git 也支持使用 X.509 证书对提交和标签进行签名,方式类似于使用 GPG (GnuPG,或 GNU Privacy Guard)。 主要区别在于 GitLab 确定开发者签名是否可信的方式:

  • 对于 X.509,将根证书颁发机构添加到 GitLab 信任存储中。 (信任存储是受信任的安全证书的存储库。)结合签名中所需的任何中间证书,开发者的证书可以链接回受信任的根证书。
  • 对于 GPG,开发者将他们的 GPG 密钥 添加到他们的账户中。

GitLab 使用自己的证书存储,因此定义了 信任链。 要让 GitLab 验证提交或标签:

  • 签名证书的电子邮件必须与 GitLab 中已验证的电子邮件地址匹配。
  • GitLab 实例需要从签名中的证书到 GitLab 证书存储中的受信任证书的完整信任链。 此链可能包括签名中提供的中间证书。你可能需要添加证书,如证书颁发机构根证书, 到 GitLab 证书存储
  • 签名时间必须在 证书有效期 内,通常最多三年。
  • 签名时间等于或晚于提交时间。

如果信任链中的根 CA 或中间证书过期并续期,提交可能会暂时显示为"未验证",直到你 重新验证它们

如果提交的状态已经确定并存储在数据库中, 请使用 Rake 任务 重新检查状态。 参考 故障排除部分。 GitLab 通过后台作业每天检查证书吊销列表。

已知问题

  • 没有 authorityKeyIdentifiersubjectKeyIdentifiercrlDistributionPoints 的证书显示为 未验证。我们 建议使用符合 RFC 5280 的 PKI 提供的证书。

  • 已验证 徽章不会显示在 GitLab SaaS 服务中, 因为 上传自定义证书颁发机构 (CA) 仅适用于 GitLab 自托管版本。

  • 在证书的扩展密钥用法 (EKU) 部分设置值,除了 Digital Signature 所需的密钥用法 (KU) 外, 很可能导致你的提交显示为 未验证。 要解决此问题,请将 emailProtection 添加到你的 EKU 列表中。 RFC 5280 指定了此限制。

    要诊断它,请遵循 使用 OpenSSL 进行 S/MIME 验证。 如果此更改不能解决问题, 请在 issue 440189 中提供反馈。

  • 在 GitLab 16.2 及更早版本中,如果你的签名证书的主题备用名称列表中有多个电子邮件地址, 只有第一个用于验证提交

配置已签名的提交

要对你的提交、标签或两者进行签名,你必须:

  1. 获取 X.509 密钥对
  2. 将你的 X.509 证书与 Git 关联
  3. 签名和验证提交
  4. 签名和验证标签

获取 X.509 密钥对

如果你的组织有公钥基础设施 (PKI),该 PKI 提供 S/MIME 密钥。如果你没有来自 PKI 的 S/MIME 密钥对,请创建你自己的自签名对或购买一对。

将你的 X.509 证书与 Git 关联

要利用 X.509 签名,你需要 Git 2.19.0 或更高版本。你可以 使用命令 git --version 检查你的 Git 版本。

如果你有正确的版本,可以继续配置 Git。

配置 Git 使用你的密钥进行签名:

signingkey=$( gpgsm --list-secret-keys | egrep '(key usage|ID)' | grep -B 1 digitalSignature | awk '/ID/ {print $2}' )
git config --global user.signingkey $signingkey
git config --global gpg.format x509

要配置 Windows 或 macOS:

  1. 通过以下方式安装 S/MIME Sign

    • 下载安装程序。
    • 在 macOS 上运行 brew install smimesign
  2. 通过运行 smimesign --list-keys 获取你的证书 ID。

  3. 通过运行 git config --global user.signingkey <ID> 设置你的签名密钥,将 <ID> 替换为证书 ID。

  4. 使用以下命令配置 X.509:

    git config --global gpg.x509.program smimesign
    git config --global gpg.format x509

签名和验证提交

将你的 X.509 证书与 Git 关联 后, 你可以对你的提交进行签名:

  1. 创建 Git 提交时,添加 -S 标志:

    git commit -S -m "feat: x509 signed commits"
  2. 推送到 GitLab,并使用 --show-signature 标志检查你的提交是否已验证:

    git log --show-signature
  3. 如果你不想每次提交时都输入 -S 标志, 运行此命令 让 Git 每次都对你的提交进行签名:

    git config --global commit.gpgsign true

签名和验证标签

将你的 X.509 证书与 Git 关联 后, 你可以开始对你的标签进行签名:

  1. 创建 Git 标签时,添加 -s 标志:

    git tag -s v1.1.1 -m "My signed tag"
  2. 推送到 GitLab 并使用以下命令验证你的标签已签名:

    git tag --verify v1.1.1
  3. 如果你不想每次标签时都输入 -s 标志, 运行此命令 让 Git 每次都对你的标签进行签名:

    git config --global tag.gpgsign true

相关主题

故障排除

对于没有管理员访问权限的提交者,查看 已签名的提交的验证问题 列表以寻找可能的修复方法。本页面的其他故障排除建议需要管理员访问权限。

重新验证提交

GitLab 将已检查提交的状态存储在数据库中。 你可以在以下情况后重新验证提交:

  • 续期根 CA 或中间证书。
  • 对证书存储进行更改。

要重新验证提交:

  1. 确保根 CA 和任何中间证书都在 GitLab 证书存储中。
  2. 运行 update_signatures Rake 任务 来检查和更新先前已验证提交的状态。

主要验证检查

代码执行 这些关键检查, 所有检查都必须返回 verified

  • x509_certificate.nil? 应为 false。
  • x509_certificate.revoked? 应为 false。
  • verified_signature 应为 true。
  • user.nil? 应为 false。
  • user.verified_emails.include?(@email) 应为 true。
  • certificate_email == @email 应为 true。

要调查为什么提交显示为 未验证

  1. 启动 Rails 控制台

    sudo gitlab-rails console
  2. 识别你要调查的项目(通过路径或 ID)和完整的提交 SHA。 使用此信息创建 signature 以运行其他检查:

    project = Project.find_by_full_path('group/subgroup/project')
    project = Project.find_by_id('121')
    commit = project.repository.commit_by(oid: '87fdbd0f9382781442053b0b76da729344e37653')
    signedcommit=Gitlab::X509::Commit.new(commit)
    signature=Gitlab::X509::Signature.new(signedcommit.signature_text, signedcommit.signed_text, commit.committer_email, commit.created_at)

    如果你在运行检查时发现的问题进行了更改,请重启 Rails 控制台并从头开始再次运行检查。

  3. 检查提交上的证书:

    signature.x509_certificate.nil?
    signature.x509_certificate.revoked?

    两个检查都应返回 false

    > signature.x509_certificate.nil?
    => false
    > signature.x509_certificate.revoked?
    => false

    一个 已知问题 导致 这些检查因 Validation failed: Subject key identifier is invalid 而失败。

  4. 对签名进行加密检查。代码必须返回 true

    signature.verified_signature

    如果它返回 false,则 进一步调查此检查

  5. 确认提交和签名上的电子邮件地址匹配:

    • Rails 控制台显示正在比较的电子邮件地址。
    • 最后一个命令必须返回 true
    sigemail=signature.__send__:certificate_email
    commitemail=commit.committer_email
    sigemail == commitemail

    在 GitLab 16.2 及更早版本中,只有第一个电子邮件Subject Alternative Name 列表中被比较。要显示 Subject Alternative Name 列表,运行:

    signature.__send__ :get_certificate_extension,'subjectAltName'

    如果开发者的电子邮件地址不是列表中的第一个,此检查 失败,提交被标记为 unverified

  6. 提交上的电子邮件地址必须与 GitLab 中的账户关联。 此检查应返回 false

    signature.user.nil?
  7. 检查电子邮件地址是否与 GitLab 中的用户关联。此检查应 返回一个用户,例如 #<User id:1234 @user_handle>

    User.find_by_any_email(commit.committer_email)

    如果它返回 nil,则电子邮件地址未与用户关联,检查失败。

  8. 确认开发者的电子邮件地址已验证。此检查必须返回 true:

    signature.user.verified_emails.include?(commit.committer_email)

    如果前一个检查返回 nil,此命令将显示错误:

    NoMethodError (undefined method `verified_emails' for nil:NilClass)
  9. 验证状态存储在数据库中。要显示数据库记录:

    pp CommitSignatures::X509CommitSignature.by_commit_sha(commit.sha);nil

    如果所有前面的检查都返回了正确的值:

    • verification_status: "unverified" 表示数据库记录需要 更新。使用 Rake 任务

    • [] 表示数据库还没有记录。在 GitLab 中查找提交 以检查签名并存储结果。

加密验证检查

如果 GitLab 确定 verified_signaturefalse,在 Rails 控制台中调查原因。 这些检查需要 signature 存在。参考前面 主要验证检查signature 步骤。

  1. 检查签名,不检查颁发者,应返回 true

    signature.__send__ :valid_signature?
  2. 检查签名时间和日期。此检查必须返回 true

    signature.__send__ :valid_signing_time?
    • 代码允许代码签名证书过期。

    • 提交必须在证书的有效期内签名, 并且在或晚于提交的时间戳。显示提交时间和 证书详细信息,包括 not_beforenot_after

      commit.created_at
      pp signature.__send__ :cert; nil
  3. 检查签名,包括可以建立 TLS 信任。此检查必须返回 true

    signature.__send__(:p7).verify([], signature.__send__(:cert_store), signature.__send__(:signed_text))
    1. 如果此失败,添加建立信任所需的缺失证书 到 GitLab 证书存储

    2. 添加更多证书后,(如果这些故障排除步骤通过) 运行 Rake 任务 重新验证提交

    3. 你可以在 Rails 控制台中动态添加额外证书来检查 是否解决了问题。

      1. 使用可修改的信任存储 cert_store 重新测试签名。 它应该仍然失败,返回 false

        cert_store = signature.__send__ :cert_store
        signature.__send__(:p7).verify([], cert_store, signature.__send__(:signed_text))
      2. 添加额外证书,并重新测试:

        cert_store.add_file("/etc/ssl/certs/my_new_root_ca.pem")
        signature.__send__(:p7).verify([], cert_store, signature.__send__(:signed_text))
    4. 显示签名中包含的证书:

      pp signature.__send__(:p7).certificates ; nil
    5. 可以使用 OpenSSL 在命令行上进行进一步调查

确保任何额外的中间证书和根证书都已添加 到证书存储中。为了与 Web 服务器上构建证书链的方式保持一致:

  • 签名提交的 Git 客户端应在签名中包含证书 和所有中间证书。
  • GitLab 证书存储应仅包含根证书。

如果你从 GitLab 信任存储中删除根证书,例如当它过期时,链接回该根的提交签名将显示为 unverified

使用 OpenSSL 进行 S/MIME 验证

如果签名有问题,或者 TLS 信任失败,可以使用 OpenSSL 在命令行上进行进一步调试。

Rails 控制台 导出签名和已签名文本:

  1. 主要验证检查 的前两个步骤是必需的,这样 signature 就已设置。

  2. OpenSSL 要求 PKCS7 PEM 格式数据以 BEGIN PKCS7END PKCS7 为界,所以这通常需要修复:

    pkcs7_text = signature.signature_text.sub('-----BEGIN SIGNED MESSAGE-----', '-----BEGIN PKCS7-----')
    pkcs7_text = pkcs7_text.sub('-----END SIGNED MESSAGE-----', '-----END PKCS7-----')
  3. 写出签名和已签名文本:

    f1=File.new('/tmp/signature_text.pk7.pem','w')
    f1 << pkcs7_text
    f1.close
    
    f2=File.new('/tmp/signed_text.txt','w')
    f2 << signature.signed_text
    f2.close

现在可以使用 OpenSSL 在 Linux 命令行上调查这些数据:

  1. 可以查询包含签名的 PKCS #7 文件:

    /opt/gitlab/embedded/bin/openssl pkcs7 -inform pem -print_certs \
        -in /tmp/signature_text.pk7.pem -print -noout

    它应该在输出中包含至少一个 cert 部分;签名者的证书。

    输出中有大量低级别的详细信息。以下是应该存在的一些结构和标题的示例:

    PKCS7:
      d.sign:
        cert:
            cert_info:
              issuer:
              validity:
                notBefore:
                notAfter:
              subject:

    如果开发者的代码签名证书由中间证书颁发机构颁发, 应该有额外的证书详细信息:

    PKCS7:
      d.sign:
        cert:
            cert_info:
        cert:
            cert_info:
  2. 从签名中提取证书:

    /opt/gitlab/embedded/bin/openssl pkcs7 -inform pem -print_certs \
        -in /tmp/signature_text.pk7.pem -out /tmp/signature_cert.pem

    如果此步骤失败,签名可能缺少签名者的证书。

    • 在 Git 客户端上修复此问题。
    • 下一步将失败,但如果你将签名者的证书复制到 GitLab 服务器,你可以使用 -nointern -certfile signerscertificate.pem 进行一些测试。
  3. 使用提取的证书部分验证提交:

    /opt/gitlab/embedded/bin/openssl smime -verify -binary -inform pem \
        -in /tmp/signature_text.pk7.pem -content /tmp/signed_text.txt \
        -noverify -certfile /tmp/signature_cert.pem -nointern

    输出通常包括:

    • 父提交
    • 提交中的名称、电子邮件和时间戳
    • 提交文本
    • Verification successful(或类似内容)

    此检查与 GitLab 执行的检查不同,因为:

    • 它不验证签名者的证书 (-noverify)
    • 验证是使用提供的 -certfile 而不是消息中的证书 (-nointern) 进行的
  4. 使用消息中的证书部分验证提交:

    /opt/gitlab/embedded/bin/openssl smime -verify -binary -inform pem \
        -in /tmp/signature_text.pk7.pem -content /tmp/signed_text.txt \
        -noverify

    这应该与上一步使用提取的证书获得相同的结果。

    如果消息中缺少证书,错误包括 signer certificate not found

  5. 完全验证提交:

    /opt/gitlab/embedded/bin/openssl smime -verify -binary -inform pem \
        -in /tmp/signature_text.pk7.pem -content /tmp/signed_text.txt

    如果此步骤失败,GitLab 中的验证也会失败。

    解决任何错误,例如:

    • certificate verify error .. unable to get local issuer certificate
      • 无法建立信任链。
      • 此 OpenSSL 二进制文件使用 GitLab 信任存储。要么信任存储中缺少根证书, 要么签名缺少中间证书,无法构建到受信任根的链。
        • 如果不可能将中间证书包含在签名中,可以将它们放入信任存储。
        • 将证书添加到信任存储 的过程适用于打包的 GitLab - 使用 /etc/gitlab/trusted-certs
      • 使用以下命令使用 OpenSSL 测试额外的受信任证书:-CAfile /path/to/rootcertificate.pem
    • unsupported certificate purpose
      • 证书必须在签名者证书的 X509v3 Key Usage 部分指定 Digital Signature

      • 如果指定了 X509v3 Extended Key Usage (EKU) 部分,它必须包含 emailProtection。 有关更多详细信息,请参阅 RFC 5280

        如果没有与 (Key Usage) 扩展一致的目的,则该证书不得用于任何目的。

        如果向 EKU 列表的此添加不能解决问题, 请在 issue 440189 中提供反馈。

    • signer certificate not found,要么:
      • 你添加了 -nointern 参数,但没有提供 -certfile
      • 签名缺少签名者的证书。