强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

06 - 验证机制

第 6 章:验证机制

本章详细介绍 Rekor 的各种验证机制,包括签名验证、日志状态验证、包含证明验证、一致性审计以及批量验证等核心能力。


6.1 验证层次概览

Rekor 的验证分为多个层次,每层提供不同级别的保证:

┌─────────────────────────────────────────────────────────────┐
│                    验证层次金字塔                            │
│                                                             │
│                    ┌───────────────┐                        │
│                    │  一致性证明   │  ← 证明日志在不同       │
│                    │  (Consistency)│     时间点之间一致      │
│                    └───────┬───────┘                        │
│                   ┌────────┴────────┐                       │
│                   │   包含证明      │  ← 证明条目确实       │
│                   │  (Inclusion)    │     存在于日志中       │
│                   └────────┬────────┘                       │
│                ┌───────────┴───────────┐                    │
│                │   日志状态验证         │  ← 验证树头        │
│                │  (Tree Head)          │     签名            │
│                └───────────┬───────────┘                    │
│             ┌──────────────┴──────────────┐                 │
│             │   签名验证                   │  ← 验证数字     │
│             │  (Signature)                │     签名         │
│             └─────────────────────────────┘                 │
└─────────────────────────────────────────────────────────────┘
层次验证内容保证
签名验证数字签名是否有效签名者身份正确,数据未被篡改
日志状态验证树头签名是否有效日志状态由可信方签名
包含证明条目是否在 Merkle Tree 中条目确实被记录在日志中
一致性证明两个树状态是否兼容日志未被回滚或篡改

6.2 签名验证

6.2.1 验证 cosign 签名

# 验证容器镜像签名(密钥模式)
cosign verify \
  --key cosign.pub \
  ghcr.io/myorg/myimage:v1.0.0

# 预期输出:
# Verification for ghcr.io/myorg/myimage:v1.0.0 --
# The following checks were performed:
#   - The cosign claims were validated
#   - The signatures were verified against the specified public key
#   - Any certificates were verified against the Fulcio roots.
#
# [{"critical":{"identity":{"docker-reference":"ghcr.io/myorg/myimage"},...}}]

6.2.2 验证无密钥签名

# 验证无密钥签名(需要指定身份信息)
cosign verify \
  --certificate-identity=[email protected] \
  --certificate-oidc-issuer=https://accounts.google.com \
  ghcr.io/myorg/myimage:v1.0.0

# 验证 GitHub Actions 签名
cosign verify \
  --certificate-identity=https://github.com/myorg/myrepo/.github/workflows/build.yml@refs/heads/main \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  ghcr.io/myorg/myimage:v1.0.0

# 验证带通配符的证书身份
cosign verify \
  --certificate-identity-regexp=".*@mycompany.com$" \
  --certificate-oidc-issuer-regexp="https://accounts.google.com" \
  ghcr.io/myorg/myimage:v1.0.0

6.2.3 验证 Blob 文件签名

# 验证文件签名
cosign verify-blob \
  --key cosign.pub \
  --signature myartifact.sig \
  --certificate myartifact.cert \
  myartifact.tar.gz

# 使用无密钥签名验证 Blob
cosign verify-blob \
  --certificate-identity=[email protected] \
  --certificate-oidc-issuer=https://accounts.google.com \
  --signature myartifact.sig \
  --certificate myartifact.cert \
  myartifact.tar.gz

6.2.4 签名验证检查项

检查项说明失败原因
签名数学验证签名是否由对应私钥生成签名被篡改或使用错误的公钥
证书有效期证书是否在有效期内签名时间超出证书有效期
证书链验证证书是否由可信 CA 颁发CA 不可信或证书链不完整
身份匹配证书中的身份是否匹配预期身份与实际身份不符
OIDC 颁发者匹配证书中的 OIDC 颁发者是否匹配颁发者不匹配
Rekor 日志记录签名是否已记录在 Rekor 中签名未上传到透明日志

6.3 日志状态验证

6.3.1 获取日志状态

# 获取公共 Rekor 的日志信息
rekor-cli loginfo

# 输出示例:
# [rekor root hash]: 4b8e2b5a45e3abc...
# [tree size]: 534,218,392
# [tree ID]: 3747744012

# 获取 JSON 格式
rekor-cli loginfo --format=json

# 使用 API
curl -s https://rekor.sigstore.dev/api/v1/log | jq '{
  rootHash: .rootHash,
  treeSize: .treeSize,
  signedTreeHead: .signedTreeHead
}'

6.3.2 验证树头签名

树头(Tree Head)由 Trillian Log Signer 签名,确保日志状态未被篡改:

# 使用 API 获取签名的树头
curl -s https://rekor.sigstore.dev/api/v1/log | jq '.signedTreeHead'

# 验证树头签名(使用 Rekor 公钥)
# Rekor 的公钥可以通过 TUF 获取
cosign initialize
# 公钥缓存在 ~/.sigstore/root/ 目录下

6.3.3 树头验证内容

{
  "treeSize": 534218392,
  "rootHash": "4b8e2b5a45e3abc...",
  "timestampNanos": 1704067200000000000,
  "signature": {
    "keyId": "rekor-signer-key",
    "algorithm": "ecdsa-sha2-nistp256",
    "value": "MEUCIQDx..."
  }
}
字段说明
treeSize树中叶子节点总数
rootHash当前根哈希
timestampNanos树头生成时间(纳秒精度)
signature对树头数据的数字签名

6.4 包含证明验证

6.4.1 获取包含证明

# 通过 rekor-cli 验证条目包含
rekor-cli verify --log-index=12345678

# 通过 API 获取包含证明
curl -s "https://rekor.sigstore.dev/api/v1/log/entries/<ENTRY_UUID>/proof" | jq '.'

# 获取条目详情(包含包含证明)
rekor-cli get --log-index=12345678 --format=json | jq '.verification.inclusionProof'

6.4.2 包含证明的结构

{
  "hashes": [
    "a1b2c3d4e5f6...",
    "b2c3d4e5f6a1...",
    "c3d4e5f6a1b2...",
    "d4e5f6a1b2c3...",
    "e5f6a1b2c3d4..."
  ],
  "logIndex": 12345678,
  "rootHash": "4b8e2b5a45e3abc...",
  "treeSize": 534218392,
  "checkpoint": {
    "envelope": "rekor.sigstore.dev ...\ntree size: 534218392\n..."
  }
}

6.4.3 手动验证包含证明

使用 Python 脚本手动验证包含证明的正确性:

#!/usr/bin/env python3
"""
手动验证 Rekor 包含证明
"""

import hashlib
import json
import sys
import requests

def sha256(data: bytes) -> bytes:
    return hashlib.sha256(data).digest()

def verify_inclusion_proof(
    leaf_hash: bytes,
    proof_hashes: list[bytes],
    log_index: int,
    root_hash: bytes,
) -> bool:
    """验证包含证明"""
    current_hash = leaf_hash
    index = log_index
    
    for sibling_hash in proof_hashes:
        if index % 2 == 0:
            current_hash = sha256(current_hash + sibling_hash)
        else:
            current_hash = sha256(sibling_hash + current_hash)
        index //= 2
    
    return current_hash == root_hash

def main():
    # 获取条目
    entry_uuid = sys.argv[1] if len(sys.argv) > 1 else None
    if not entry_uuid:
        print("Usage: python verify_inclusion.py <entry_uuid>")
        sys.exit(1)
    
    # 从 Rekor 获取条目
    resp = requests.get(
        f"https://rekor.sigstore.dev/api/v1/log/entries/{entry_uuid}"
    )
    entry = resp.json()
    
    # 提取包含证明
    proof = entry["verification"]["inclusionProof"]
    leaf_hash = bytes.fromhex(entry["body"])  # 简化处理
    
    # 验证
    proof_hashes = [bytes.fromhex(h) for h in proof["hashes"]]
    root_hash = bytes.fromhex(proof["rootHash"])
    
    result = verify_inclusion_proof(
        leaf_hash=sha256(leaf_hash.encode()),
        proof_hashes=proof_hashes,
        log_index=proof["logIndex"],
        root_hash=root_hash,
    )
    
    if result:
        print("✅ 包含证明验证成功")
    else:
        print("❌ 包含证明验证失败")
        sys.exit(1)

if __name__ == "__main__":
    main()

6.4.4 包含证明验证流程图

输入: entry_hash, proof_hashes[], log_index, expected_root_hash

        entry_hash
            │
            ▼
    ┌───────────────────┐
    │ log_index = 12    │ (二进制: 1100)
    │ current = entry   │
    └───────┬───────────┘
            │
    ┌───────▼───────────┐
    │ Step 1: index=12  │
    │ 12 % 2 == 0       │
    │ 12 / 2 = 6        │
    │ current = H(current + hash[0])
    └───────┬───────────┘
            │
    ┌───────▼───────────┐
    │ Step 2: index=6   │
    │ 6 % 2 == 0        │
    │ 6 / 2 = 3         │
    │ current = H(current + hash[1])
    └───────┬───────────┘
            │
    ┌───────▼───────────┐
    │ Step 3: index=3   │
    │ 3 % 2 == 1        │
    │ 3 / 2 = 1         │
    │ current = H(hash[2] + current)
    └───────┬───────────┘
            │
    ┌───────▼───────────┐
    │ Step 4: index=1   │
    │ 1 % 2 == 1        │
    │ 1 / 2 = 0         │
    │ current = H(hash[3] + current)
    └───────┬───────────┘
            │
    ┌───────▼───────────┐
    │ current == root?  │
    │ ✅ 或 ❌          │
    └───────────────────┘

6.5 一致性证明

6.5.1 什么是一致性证明?

一致性证明用于验证两个不同时间点的 Merkle Tree 状态是否兼容,即新树是旧树的超集(只追加操作)。

6.5.2 获取一致性证明

# 获取一致性证明(从树大小 firstSize 到 lastSize)
curl -s "https://rekor.sigstore.dev/api/v1/log/proof?firstSize=1000&lastSize=2000" | jq '.'

# 预期输出:
# {
#   "hashes": ["...", "...", ...],
#   "firstSize": 1000,
#   "lastSize": 2000
# }

6.5.3 一致性证明验证

def verify_consistency_proof(
    first_hash: bytes,      # 旧树根哈希
    second_hash: bytes,     # 新树根哈希
    first_size: int,        # 旧树大小
    second_size: int,       # 新树大小
    proof_hashes: list[bytes],  # 一致性证明哈希
) -> bool:
    """验证一致性证明"""
    # 使用 RFC 6962 中的验证算法
    # 简化实现,实际验证逻辑更复杂
    if first_size == second_size:
        return first_hash == second_hash
    
    if first_size > second_size:
        return False
    
    # ... 完整验证逻辑参见 RFC 6962
    # https://datatracker.ietf.org/doc/html/rfc6962#section-8.1
    pass

6.5.4 一致性证明的使用场景

场景说明
监控器定期获取树头,验证与上次状态的一致性
审计证明日志在审计期间未被篡改
故障恢复验证系统恢复后日志状态的正确性
多实例同步验证不同 Rekor 实例的日志一致性

6.6 审计流程

6.6.1 完整审计流程

#!/bin/bash
# Rekor 日志审计脚本

set -e

REKOR_SERVER="https://rekor.sigstore.dev"
AUDIT_DIR="./rekor-audit-$(date +%Y%m%d)"
mkdir -p "$AUDIT_DIR"

echo "=== Rekor 日志审计 ==="
echo "审计目录: $AUDIT_DIR"

# 步骤 1: 记录当前日志状态
echo ">>> 步骤 1: 记录当前日志状态"
curl -s "$REKOR_SERVER/api/v1/log" | jq '.' > "$AUDIT_DIR/log-status.json"
cat "$AUDIT_DIR/log-status.json" | jq '{treeSize, rootHash}'

# 步骤 2: 获取日志信息
echo ">>> 步骤 2: 获取日志信息"
rekor-cli loginfo > "$AUDIT_DIR/loginfo.txt"
cat "$AUDIT_DIR/loginfo.txt"

# 步骤 3: 验证特定条目
echo ">>> 步骤 3: 验证条目"
ENTRY_INDEX=12345678
rekor-cli get --log-index=$ENTRY_INDEX --format=json > "$AUDIT_DIR/entry-$ENTRY_INDEX.json"
rekor-cli verify --log-index=$ENTRY_INDEX && echo "✅ 条目 $ENTRY_INDEX 包含证明验证通过" || echo "❌ 验证失败"

# 步骤 4: 记录审计时间戳
echo ">>> 步骤 4: 记录审计时间"
echo "审计时间: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" > "$AUDIT_DIR/audit-timestamp.txt"
echo "审计员: $(whoami)" >> "$AUDIT_DIR/audit-timestamp.txt"

# 步骤 5: 生成审计报告
echo ">>> 步骤 5: 生成审计报告"
cat > "$AUDIT_DIR/audit-report.md" << EOF
# Rekor 审计报告

## 基本信息
- 审计时间: $(date -u '+%Y-%m-%d %H:%M:%S UTC')
- Rekor 服务器: $REKOR_SERVER
- 树大小: $(jq -r '.treeSize' "$AUDIT_DIR/log-status.json")
- 根哈希: $(jq -r '.rootHash' "$AUDIT_DIR/log-status.json")

## 验证结果
- 条目 $ENTRY_INDEX: $(rekor-cli verify --log-index=$ENTRY_INDEX 2>/dev/null && echo "✅ 通过" || echo "❌ 失败")
EOF

echo "=== 审计完成 ==="
echo "审计文件保存在: $AUDIT_DIR/"

6.6.2 审计检查清单

检查项命令/方法预期结果
日志可访问rekor-cli loginfo返回树大小和根哈希
树头签名有效API 获取 + 公钥验证签名验证通过
条目包含证明rekor-cli verify --log-index=N证明验证通过
条目签名有效cosign verify签名验证通过
根哈希一致对比不同时间点的根哈希只增不减
树大小增长对比不同时间点的树大小只增不减

6.7 批量验证

6.7.1 批量验证多个镜像

#!/bin/bash
# 批量验证容器镜像签名

IMAGES=(
  "ghcr.io/myorg/app:v1.0.0"
  "ghcr.io/myorg/app:v1.1.0"
  "ghcr.io/myorg/service:latest"
  "ghcr.io/myorg/api:v2.0.0"
)

CERTIFICATE_IDENTITY="https://github.com/myorg/myrepo/.github/workflows/build.yml@refs/heads/main"
CERTIFICATE_OIDC_ISSUER="https://token.actions.githubusercontent.com"

PASS_COUNT=0
FAIL_COUNT=0

for image in "${IMAGES[@]}"; do
  echo -n "验证 $image ... "
  if cosign verify \
    --certificate-identity="$CERTIFICATE_IDENTITY" \
    --certificate-oidc-issuer="$CERTIFICATE_OIDC_ISSUER" \
    "$image" > /dev/null 2>&1; then
    echo "✅ 通过"
    ((PASS_COUNT++))
  else
    echo "❌ 失败"
    ((FAIL_COUNT++))
  fi
done

echo ""
echo "=== 验证结果 ==="
echo "通过: $PASS_COUNT"
echo "失败: $FAIL_COUNT"
echo "总计: ${#IMAGES[@]}"

if [ $FAIL_COUNT -gt 0 ]; then
  exit 1
fi

6.7.2 批量验证 Blob 文件

#!/bin/bash
# 批量验证文件签名

KEY_FILE="cosign.pub"
ARTIFACT_DIR="./artifacts"
SIG_DIR="./signatures"

PASS_COUNT=0
FAIL_COUNT=0

for artifact in "$ARTIFACT_DIR"/*.tar.gz; do
  basename=$(basename "$artifact")
  sig_file="$SIG_DIR/${basename}.sig"
  cert_file="$SIG_DIR/${basename}.cert"
  
  if [ ! -f "$sig_file" ]; then
    echo "⚠️  签名文件不存在: $sig_file"
    continue
  fi
  
  echo -n "验证 $basename ... "
  if cosign verify-blob \
    --key "$KEY_FILE" \
    --signature "$sig_file" \
    --certificate "$cert_file" \
    "$artifact" > /dev/null 2>&1; then
    echo "✅"
    ((PASS_COUNT++))
  else
    echo "❌"
    ((FAIL_COUNT++))
  fi
done

echo ""
echo "=== 结果: $PASS_COUNT 通过, $FAIL_COUNT 失败 ==="

6.7.3 批量验证 Rekor 条目

#!/bin/bash
# 批量验证 Rekor 条目包含证明

INDICES=(12345678 12345679 12345680 12345681 12345682)

for index in "${INDICES[@]}"; do
  echo -n "验证条目 $index ... "
  if rekor-cli verify --log-index=$index > /dev/null 2>&1; then
    echo "✅"
  else
    echo "❌"
  fi
done

6.8 验证错误处理

6.8.1 常见验证错误

错误信息原因解决方案
no matching signatures签名不匹配检查公钥是否正确
certificate identity mismatch证书身份不匹配检查 --certificate-identity 参数
certificate OIDC issuer mismatch颁发者不匹配检查 --certificate-oidc-issuer 参数
entry not found in the transparency log条目不在日志中检查条目 UUID 或索引
inclusion proof verification failed包含证明无效条目可能已被篡改或日志不一致
certificate has expired证书已过期对于无密钥签名,检查 integratedTime

6.8.2 调试验证问题

# 启用详细日志
COSIGN_EXPERIMENTAL=1 cosign verify \
  --certificate-identity=[email protected] \
  --certificate-oidc-issuer=https://accounts.google.com \
  --output=text \
  ghcr.io/myorg/myimage:v1.0.0 2>&1 | head -50

# 查看证书详情
cosign verify \
  --certificate-identity=[email protected] \
  --certificate-oidc-issuer=https://accounts.google.com \
  --output-certificate=/tmp/cert.pem \
  ghcr.io/myorg/myimage:v1.0.0

# 解析证书
openssl x509 -in /tmp/cert.pem -text -noout

6.8.3 验证失败的安全影响

失败类型安全影响建议操作
签名验证失败镜像可能被篡改阻止部署,调查来源
包含证明失败日志可能不一致切换到其他 Rekor 实例
证书过期验证时间晚于证书有效期检查集成时间戳
身份不匹配镜像来源不可信确认构建流程

6.9 验证策略

6.9.1 分层验证策略

# Kubernetes 验证策略示例
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
  name: image-verification-policy
spec:
  images:
    - glob: "ghcr.io/myorg/**"
  authorities:
    # 策略 1: 无密钥签名验证
    - keyless:
        url: https://fulcio.sigstore.dev
        identities:
          - issuer: https://token.actions.githubusercontent.com
            subjectRegExp: ".*myorg/myrepo.*"
      ctlog:
        url: https://rekor.sigstore.dev
      tuf:
        url: https://tuf-repo-cdn.sigstore.dev
    # 策略 2: 密钥签名验证(备选)
    - key:
        data: |
          -----BEGIN PUBLIC KEY-----
          MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
          -----END PUBLIC KEY-----

6.9.2 验证策略对比

策略安全性灵活性适用场景
仅验证签名⭐⭐⭐⭐⭐⭐⭐⭐开发环境
签名 + Rekor⭐⭐⭐⭐⭐⭐⭐⭐测试环境
签名 + 身份 + Rekor⭐⭐⭐⭐⭐⭐⭐⭐生产环境
多重签名⭐⭐⭐⭐⭐⭐⭐关键系统

6.10 注意事项

时钟同步:包含证明和证书验证依赖准确的时间。确保验证系统与 NTP 服务器同步。

公共实例依赖:验证公共 Rekor 的条目需要能够访问 rekor.sigstore.dev。内网环境应部署私有实例或缓存。

证书有效期理解:无密钥签名的 Fulcio 证书只有 10 分钟有效期,但签名记录在 Rekor 中后可以通过 integratedTime 验证签名时证书有效。

批量验证的速率限制:公共实例有速率限制,大量验证请考虑使用本地缓存或私有实例。


6.11 本章小结

验证类型工具/命令用途
签名验证cosign verify验证数字签名的有效性
日志状态验证rekor-cli loginfo验证树头签名
包含证明rekor-cli verify证明条目在日志中
一致性证明API /log/proof验证日志未被回滚
批量验证自定义脚本批量验证多个构件

扩展阅读


下一章07 - CI/CD 集成 — 将 Rekor 集成到 GitHub Actions、GitLab CI 等 CI/CD 流水线中。