security: add release trust authority bootstrap
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
|
||||
本工具只处理 RC-06 的一个精确发布批次:60 个合同资产、14 个 Prompt 模块和 31 项处置结论。唯一人工审核人为陈钰麒(总经理)。离线根密钥和 apply 服务密钥由系统按用途隔离管理,它们不是人工审核人。
|
||||
|
||||
当前没有生成真实审核私钥、正式签名、候选 tag、发布收据或生产授权。内部试点、UAT、生产及公众开放继续 `NO_GO`。一次 RC-06 签名不等于资产已经发布,也不等于授权公众生产切流。
|
||||
陈钰麒已在受控本机完成审核密钥初始化;该事实只表示审核私钥已加密保存并完成公钥注册准备。RC-06/recovery/apply 技术密钥、源码 pin、正式签名、候选 tag、发布收据和生产授权仍未形成。内部试点、UAT、生产及公众开放继续 `NO_GO`。一次 RC-06 签名不等于资产已经发布,也不等于授权公众生产切流。
|
||||
|
||||
## 2. 审核人只需要执行的两个入口
|
||||
|
||||
@@ -54,6 +54,30 @@
|
||||
- 后续通过业务 API 应用 74 项内容,并由服务密钥签署 74 项机器结果和在线 hash;
|
||||
- 继续验证 1 个起草+8 个审查运行时 Prompt 快照以及 RC-06 UAT。
|
||||
|
||||
系统侧使用 `yuqei-infra/scripts/Launch-ReleaseAuthority.ps1` 调用
|
||||
`rc06-release-trust-bootstrap.py`。该入口与审核人入口分离,默认非交互:
|
||||
|
||||
- `initialize` 一次性生成互不相同的 RC-06 approval root、recovery root 和
|
||||
apply-service 三把 Ed25519 技术密钥;私钥只以 Windows CurrentUser DPAPI
|
||||
密文保存在源码树外,并继承仅当前账户与 SYSTEM 可访问的 DACL;
|
||||
- `build-recovery-keyrings` 使用 recovery root 分别签发 first-release 与
|
||||
rollback 两种 schema/key-id 的 keyring;两者可共享 recovery root,但不能
|
||||
混用 schema、root key id 或签名 payload;
|
||||
- `pin-source` 默认只预览,显式 `--apply` 才会以严格匹配和事务回退方式固定
|
||||
三类公钥 SHA、两个 recovery keyring payload SHA 和精确 epoch;不写入私钥;
|
||||
- `sign-rc06-keyring` 只在冻结 manifest 形成后签发绑定精确 manifest 原始 SHA
|
||||
的候选专属 keyring;
|
||||
- `sign-apply-payloads` 只签署已经包含 74 项在线 hash、且可由正式 RC-06
|
||||
verifier 完整验证的 canonical apply payload;所有 receipt 先在内存生成并
|
||||
逐项自验,再作为禁止覆盖的完整 bundle 原子写出,不调用业务 API;
|
||||
- `provision-apply-private-key` 在安全 secret-store 交付适配器完成前保持
|
||||
fail-closed,不会导出明文或 DPAPI 私钥。
|
||||
|
||||
DPAPI 与 DACL 不能抵御已经控制当前 Windows 账户的进程。正式初始化仍只能在
|
||||
发布负责人确认的干净受控主机上执行;临时 UAT 主机、源码仓库、聊天、邮件、
|
||||
共享盘和构建 artifact 均不得成为技术私钥的保存位置。当前新增工具代码本身不
|
||||
表示真实技术密钥、pin、keyring、apply receipt 或发布授权已经形成。
|
||||
|
||||
## 4. 不得执行的操作
|
||||
|
||||
- 不得手工编辑 manifest、request、keyring、approval、SHA 或签名 JSON;
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true, Position = 0)]
|
||||
[ValidateSet(
|
||||
"runtime-check",
|
||||
"initialize",
|
||||
"status",
|
||||
"build-recovery-keyrings",
|
||||
"pin-source",
|
||||
"sign-rc06-keyring",
|
||||
"sign-apply-payloads",
|
||||
"provision-apply-private-key"
|
||||
)]
|
||||
[string]$Command,
|
||||
|
||||
[Parameter(ValueFromRemainingArguments = $true)]
|
||||
[string[]]$CommandArguments
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$env:PSModulePath = @(
|
||||
(Join-Path $env:SystemRoot "System32\WindowsPowerShell\v1.0\Modules")
|
||||
(Join-Path $env:ProgramFiles "WindowsPowerShell\Modules")
|
||||
) -join ";"
|
||||
|
||||
function Get-TrustedPythonCandidates {
|
||||
$candidatePaths = [System.Collections.Generic.List[string]]::new()
|
||||
foreach ($pattern in @(
|
||||
(Join-Path $env:LOCALAPPDATA "Python\pythoncore-*\python.exe")
|
||||
(Join-Path $env:LOCALAPPDATA "Programs\Python\Python*\python.exe")
|
||||
(Join-Path $env:ProgramFiles "Python*\python.exe")
|
||||
)) {
|
||||
Get-ChildItem -Path $pattern -File -ErrorAction SilentlyContinue |
|
||||
ForEach-Object { $candidatePaths.Add($_.FullName) }
|
||||
}
|
||||
foreach ($registryRoot in @(
|
||||
"Registry::HKEY_CURRENT_USER\Software\Python\PythonCore"
|
||||
"Registry::HKEY_LOCAL_MACHINE\Software\Python\PythonCore"
|
||||
)) {
|
||||
Get-ChildItem -LiteralPath $registryRoot -ErrorAction SilentlyContinue |
|
||||
ForEach-Object {
|
||||
$properties = Get-ItemProperty -LiteralPath (Join-Path $_.PSPath "InstallPath") -ErrorAction SilentlyContinue
|
||||
if ($properties.ExecutablePath) {
|
||||
$candidatePaths.Add([string]$properties.ExecutablePath)
|
||||
} elseif ($properties.'(default)') {
|
||||
$candidatePaths.Add((Join-Path ([string]$properties.'(default)') "python.exe"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$trusted = foreach ($candidatePath in $candidatePaths | Sort-Object -Unique) {
|
||||
if (-not (Test-Path -LiteralPath $candidatePath -PathType Leaf)) { continue }
|
||||
$item = Get-Item -LiteralPath $candidatePath -Force
|
||||
if (($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { continue }
|
||||
if ($item.FullName -like "*\Microsoft\WindowsApps\*") { continue }
|
||||
$version = [version]::new(
|
||||
[Math]::Max(0, $item.VersionInfo.FileMajorPart),
|
||||
[Math]::Max(0, $item.VersionInfo.FileMinorPart),
|
||||
[Math]::Max(0, $item.VersionInfo.FileBuildPart),
|
||||
[Math]::Max(0, $item.VersionInfo.FilePrivatePart)
|
||||
)
|
||||
if ($version -lt [version]"3.11") { continue }
|
||||
$signature = Get-AuthenticodeSignature -LiteralPath $item.FullName
|
||||
if (
|
||||
$signature.Status -ne [System.Management.Automation.SignatureStatus]::Valid -or
|
||||
$null -eq $signature.SignerCertificate -or
|
||||
$signature.SignerCertificate.Subject -notlike "*CN=Python Software Foundation*"
|
||||
) { continue }
|
||||
[pscustomobject]@{ Path = $item.FullName; Version = $version }
|
||||
}
|
||||
return $trusted | Sort-Object Version -Descending
|
||||
}
|
||||
|
||||
try {
|
||||
$authorityScript = Join-Path $PSScriptRoot "rc06-release-trust-bootstrap.py"
|
||||
if (-not (Test-Path -LiteralPath $authorityScript -PathType Leaf)) {
|
||||
throw "The release authority program is missing"
|
||||
}
|
||||
Remove-Item Env:PYTHONPATH -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:PYTHONHOME -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:PYTHONSTARTUP -ErrorAction SilentlyContinue
|
||||
Remove-Item Env:PYTHONUSERBASE -ErrorAction SilentlyContinue
|
||||
$runtime = $null
|
||||
$ErrorActionPreference = "Continue"
|
||||
foreach ($candidate in @(Get-TrustedPythonCandidates)) {
|
||||
$preflightOutput = @(& $candidate.Path -I -S -B $authorityScript runtime-check 2>&1)
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
$runtime = $candidate
|
||||
break
|
||||
}
|
||||
}
|
||||
$ErrorActionPreference = "Stop"
|
||||
if ($null -eq $runtime) {
|
||||
throw "No trusted offline Python runtime has DPAPI and the required cryptography dependency"
|
||||
}
|
||||
& $runtime.Path -I -S -B $authorityScript $Command @CommandArguments
|
||||
exit $LASTEXITCODE
|
||||
} catch {
|
||||
Write-Host ("Authority startup failed: " + $_.Exception.Message) -ForegroundColor Red
|
||||
Write-Host "No authority key, source pin, keyring, signature, or private-key export was created."
|
||||
exit 2
|
||||
}
|
||||
@@ -134,6 +134,7 @@ GOLDEN_INVENTORY_PATH = (
|
||||
FROZEN_IMPLEMENTATION_PATHS = (
|
||||
"yuqei-infra/scripts/build-rc06-publication-batch.py",
|
||||
"yuqei-infra/scripts/rc06-single-reviewer-signing.py",
|
||||
"yuqei-infra/scripts/rc06-release-trust-bootstrap.py",
|
||||
"yuqei-contract-platform/services/contract-api/src/yuqei_contract_api/contract_templates.py",
|
||||
"yuqei-contract-platform/services/contract-api/src/yuqei_contract_api/contract_scene_assets.py",
|
||||
"yuqei-contract-platform/services/contract-api/src/yuqei_contract_api/template_inputs.py",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,713 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
SCRIPT = REPO_ROOT / "yuqei-infra" / "scripts" / "rc06-release-trust-bootstrap.py"
|
||||
EXISTING_BATCH_TEST = REPO_ROOT / "yuqei-infra" / "tests" / "test_rc06_publication_batch.py"
|
||||
AUTHORITY_LAUNCHER = REPO_ROOT / "yuqei-infra" / "scripts" / "Launch-ReleaseAuthority.ps1"
|
||||
|
||||
|
||||
def _load_script():
|
||||
spec = importlib.util.spec_from_file_location("rc06_release_trust_bootstrap", SCRIPT)
|
||||
assert spec is not None and spec.loader is not None
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
authority = _load_script()
|
||||
|
||||
|
||||
def _load_existing_batch_test_module():
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"rc06_publication_batch_fixture_helpers",
|
||||
EXISTING_BATCH_TEST,
|
||||
)
|
||||
assert spec is not None and spec.loader is not None
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def _protect(raw: bytes, role: str) -> bytes:
|
||||
return b"fixture-protected\0" + role.encode("ascii") + b"\0" + raw[::-1]
|
||||
|
||||
|
||||
def _unprotect(raw: bytes, role: str) -> bytes:
|
||||
prefix = b"fixture-protected\0" + role.encode("ascii") + b"\0"
|
||||
if not raw.startswith(prefix):
|
||||
raise ValueError("wrong fixture protection domain")
|
||||
return raw[len(prefix) :][::-1]
|
||||
|
||||
|
||||
def _reviewer_registration(path: Path, *, private_key: Ed25519PrivateKey | None = None) -> dict:
|
||||
private_key = private_key or Ed25519PrivateKey.generate()
|
||||
public_key = private_key.public_key()
|
||||
public_raw = public_key.public_bytes(
|
||||
serialization.Encoding.Raw,
|
||||
serialization.PublicFormat.Raw,
|
||||
)
|
||||
public_pem = public_key.public_bytes(
|
||||
serialization.Encoding.PEM,
|
||||
serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
)
|
||||
fingerprint = authority.batch._sha256_bytes(public_raw)
|
||||
registration = {
|
||||
"schema_version": authority.REVIEWER_REGISTRATION_SCHEMA,
|
||||
"key_id": f"chen-yuqi-rc06-{fingerprint[:16]}",
|
||||
"algorithm": "Ed25519",
|
||||
"reviewer": dict(authority.batch.SOLE_RELEASE_REVIEWER),
|
||||
"created_at": "2026-09-27T00:00:00Z",
|
||||
"public_key_pem": public_pem.decode("ascii"),
|
||||
"public_key_raw_sha256": fingerprint,
|
||||
"public_key_pem_sha256": authority.batch._sha256_bytes(public_pem),
|
||||
}
|
||||
path.write_bytes(authority._canonical(registration))
|
||||
return registration
|
||||
|
||||
|
||||
def _initialize(tmp_path: Path):
|
||||
state = tmp_path / "authority-state"
|
||||
result = authority.initialize_authority(
|
||||
state,
|
||||
protect=_protect,
|
||||
unprotect=_unprotect,
|
||||
created_at=datetime(2026, 9, 27, 1, 0, tzinfo=UTC),
|
||||
)
|
||||
return state, result, authority.load_authority(state, unprotect=_unprotect)
|
||||
|
||||
|
||||
def _copy_pin_sources(tmp_path: Path) -> Path:
|
||||
repo = tmp_path / "candidate"
|
||||
paths = (
|
||||
"yuqei-infra/scripts/build-rc06-publication-batch.py",
|
||||
"yuqei-infra/deploy/plan-first-release-snapshot-restore.py",
|
||||
"yuqei-infra/deploy/plan-rollback-rehearsal.py",
|
||||
)
|
||||
for relative in paths:
|
||||
source = REPO_ROOT / relative
|
||||
target = repo / relative
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_bytes(source.read_bytes())
|
||||
return repo
|
||||
|
||||
|
||||
def _formal_apply_materials(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> dict:
|
||||
state, _result, keys = _initialize(tmp_path)
|
||||
reviewer_private = Ed25519PrivateKey.generate()
|
||||
reviewer_file = tmp_path / "reviewer-registration.json"
|
||||
registration = _reviewer_registration(reviewer_file, private_key=reviewer_private)
|
||||
helpers = _load_existing_batch_test_module()
|
||||
inventory = helpers.batch.load_runtime_inventory(REPO_ROOT)
|
||||
manifest = helpers._manifest(inventory, resolved=True)
|
||||
manifest_raw = authority._canonical(manifest)
|
||||
manifest_file = tmp_path / "rc06-publication-manifest.json"
|
||||
manifest_file.write_bytes(manifest_raw)
|
||||
manifest_sha = authority.batch._sha256_bytes(manifest_raw)
|
||||
monkeypatch.setattr(
|
||||
authority.batch,
|
||||
"TRUSTED_APPROVAL_ROOT_PUBLIC_KEY_SHA256",
|
||||
keys[authority.ROLE_RC06_ROOT].public_sha256,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
authority.batch,
|
||||
"TRUSTED_APPLY_SERVICE_PUBLIC_KEY_SHA256",
|
||||
keys[authority.ROLE_APPLY_SERVICE].public_sha256,
|
||||
)
|
||||
monkeypatch.setattr(authority.batch, "FROZEN_APPROVAL_KEYRING_EPOCH", 1)
|
||||
keyring_result = authority.build_rc06_candidate_keyring(
|
||||
state,
|
||||
reviewer_file,
|
||||
manifest_file,
|
||||
manifest_sha,
|
||||
unprotect=_unprotect,
|
||||
now=datetime(2026, 9, 27, 3, 0, tzinfo=UTC),
|
||||
)
|
||||
keyring_directory = Path(keyring_result["directory"])
|
||||
keyring_file = keyring_directory / "rc06-approval-keyring.json"
|
||||
keyring_raw = keyring_file.read_bytes()
|
||||
keyring = json.loads(keyring_raw)
|
||||
approved_at = datetime(2026, 9, 27, 3, 10, tzinfo=UTC)
|
||||
expires_at = datetime(2026, 9, 27, 5, 0, tzinfo=UTC)
|
||||
approval_payload = authority.batch.build_approval_payload(
|
||||
manifest,
|
||||
manifest_raw_sha256=manifest_sha,
|
||||
key_id=registration["key_id"],
|
||||
approval_keyring_epoch=1,
|
||||
approval_keyring_payload_sha256=keyring["keyring_payload_sha256"],
|
||||
approved_at=approved_at,
|
||||
expires_at=expires_at,
|
||||
)
|
||||
approval_signature = reviewer_private.sign(
|
||||
authority.batch.APPROVAL_SIGNATURE_DOMAIN + authority._canonical(approval_payload)
|
||||
)
|
||||
approval = {
|
||||
"schema_version": authority.batch.APPROVAL_SCHEMA_VERSION,
|
||||
"approval_id": (
|
||||
"rc06-batch-approval-"
|
||||
+ authority.batch._sha256_bytes(
|
||||
authority._canonical(approval_payload) + approval_signature
|
||||
)[:24]
|
||||
),
|
||||
"payload": approval_payload,
|
||||
"signature": base64.b64encode(approval_signature).decode("ascii"),
|
||||
}
|
||||
approval_file = tmp_path / "rc06-batch-approval.json"
|
||||
approval_raw = authority._canonical(approval)
|
||||
approval_file.write_bytes(approval_raw)
|
||||
item_results = [
|
||||
{
|
||||
"kind": "asset",
|
||||
"item_id": item["template_id"],
|
||||
"expected_content_sha256": item["content_sha256"],
|
||||
"online_content_sha256": item["content_sha256"],
|
||||
"status": "applied_and_verified",
|
||||
}
|
||||
for item in manifest["asset_batch"]["items"]
|
||||
] + [
|
||||
{
|
||||
"kind": "prompt_module",
|
||||
"item_id": item["key"],
|
||||
"expected_content_sha256": item["content_sha256"],
|
||||
"online_content_sha256": item["content_sha256"],
|
||||
"status": "applied_and_verified",
|
||||
}
|
||||
for item in manifest["prompt_module_batch"]["items"]
|
||||
]
|
||||
item_results.sort(key=lambda item: (item["kind"], item["item_id"]))
|
||||
apply_payload = {
|
||||
"schema_version": authority.batch.APPLY_PAYLOAD_SCHEMA_VERSION,
|
||||
"batch_manifest_payload_sha256": manifest["manifest_payload_sha256"],
|
||||
"approval_receipt_sha256": authority.batch._sha256_bytes(approval_raw),
|
||||
"environment_id": "isolated-uat-fixture",
|
||||
"applied_at": "2026-09-27T04:00:00Z",
|
||||
"generated_by": {"type": "system", "component": "rc06-publication-service"},
|
||||
"item_results": item_results,
|
||||
"write_count": 74,
|
||||
"status": "applied_and_verified",
|
||||
}
|
||||
payloads_file = tmp_path / "apply-payloads.json"
|
||||
payloads_raw = authority._canonical(apply_payload)
|
||||
payloads_file.write_bytes(payloads_raw)
|
||||
return {
|
||||
"state": state,
|
||||
"keys": keys,
|
||||
"reviewer_private": reviewer_private,
|
||||
"registration": registration,
|
||||
"manifest": manifest,
|
||||
"manifest_file": manifest_file,
|
||||
"manifest_sha": manifest_sha,
|
||||
"keyring": keyring,
|
||||
"keyring_file": keyring_file,
|
||||
"keyring_sha": authority.batch._sha256_bytes(keyring_raw),
|
||||
"root_file": keyring_directory / "rc06-approval-root-public-key.raw",
|
||||
"approval": approval,
|
||||
"approval_file": approval_file,
|
||||
"approval_sha": authority.batch._sha256_bytes(approval_raw),
|
||||
"apply_payload": apply_payload,
|
||||
"payloads_file": payloads_file,
|
||||
"payloads_sha": authority.batch._sha256_bytes(payloads_raw),
|
||||
}
|
||||
|
||||
|
||||
def test_authority_initialization_creates_three_distinct_protected_keys_and_is_idempotent(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
state, result, keys = _initialize(tmp_path)
|
||||
|
||||
assert result["status"] == "initialized_not_pinned_not_signed"
|
||||
assert result["private_key_export_supported"] is False
|
||||
assert set(keys) == set(authority.AUTHORITY_ROLES)
|
||||
assert len({item.public_raw for item in keys.values()}) == 3
|
||||
key_dir = state / authority.KEYS_DIRECTORY_NAME
|
||||
assert set(child.name for child in key_dir.iterdir()) == (
|
||||
authority._expected_authority_names() | {authority.signer.BUNDLE_COMPLETE_FILENAME}
|
||||
)
|
||||
for role, item in keys.items():
|
||||
protected_raw = (key_dir / authority._private_filename(role)).read_bytes()
|
||||
private_der = authority._private_der(item.private_key)
|
||||
assert private_der not in protected_raw
|
||||
assert b"BEGIN PRIVATE KEY" not in protected_raw
|
||||
envelope = json.loads(protected_raw)
|
||||
assert envelope["protection"] == authority.PROTECTION_NAME
|
||||
assert base64.b64decode(envelope["ciphertext_base64"], validate=True) != private_der
|
||||
|
||||
repeated = authority.initialize_authority(
|
||||
state,
|
||||
protect=lambda _raw, _role: pytest.fail("idempotent initialization generated a new key"),
|
||||
unprotect=_unprotect,
|
||||
)
|
||||
assert repeated["status"] == "already_initialized_and_verified"
|
||||
assert repeated["roles"] == result["roles"]
|
||||
|
||||
|
||||
def test_authority_rejects_partial_or_tampered_bundle(tmp_path: Path) -> None:
|
||||
state, _result, _keys = _initialize(tmp_path)
|
||||
key_dir = state / authority.KEYS_DIRECTORY_NAME
|
||||
(key_dir / authority._public_filename(authority.ROLE_APPLY_SERVICE)).unlink()
|
||||
|
||||
with pytest.raises(authority.AuthorityError, match="incomplete"):
|
||||
authority.initialize_authority(state, protect=_protect, unprotect=_unprotect)
|
||||
|
||||
|
||||
def test_recovery_keyrings_share_only_the_recovery_root_and_verify_both_schemas(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
state, _result, keys = _initialize(tmp_path)
|
||||
reviewer_file = tmp_path / "reviewer-registration.json"
|
||||
reviewer = _reviewer_registration(reviewer_file)
|
||||
now = datetime(2026, 9, 27, 2, 0, tzinfo=UTC)
|
||||
|
||||
result = authority.build_recovery_keyrings(
|
||||
state,
|
||||
reviewer_file,
|
||||
epoch=1,
|
||||
unprotect=_unprotect,
|
||||
now=now,
|
||||
validity=timedelta(days=30),
|
||||
)
|
||||
|
||||
assert result["status"] == "recovery_keyrings_created_not_pinned"
|
||||
assert result["recovery_root_public_key_sha256"] == keys[
|
||||
authority.ROLE_RECOVERY_ROOT
|
||||
].public_sha256
|
||||
assert result["first_release_keyring_payload_sha256"] != result[
|
||||
"rollback_keyring_payload_sha256"
|
||||
]
|
||||
bundle = Path(result["directory"])
|
||||
first_release = json.loads((bundle / "first-release-recovery-keyring.json").read_bytes())
|
||||
rollback = json.loads((bundle / "rollback-rehearsal-keyring.json").read_bytes())
|
||||
assert first_release["root_key_id"] == authority.FIRST_RELEASE_ROOT_KEY_ID
|
||||
assert rollback["root_key_id"] == authority.ROLLBACK_ROOT_KEY_ID
|
||||
assert "algorithm" not in first_release["keys"][0]
|
||||
assert rollback["keys"][0]["algorithm"] == "ed25519"
|
||||
assert first_release["keys"][0]["public_key_base64"] == rollback["keys"][0][
|
||||
"public_key_base64"
|
||||
]
|
||||
assert reviewer["public_key_raw_sha256"] not in {
|
||||
item.public_sha256 for item in keys.values()
|
||||
}
|
||||
|
||||
repeated = authority.build_recovery_keyrings(
|
||||
state,
|
||||
reviewer_file,
|
||||
epoch=1,
|
||||
unprotect=_unprotect,
|
||||
now=now + timedelta(hours=1),
|
||||
)
|
||||
assert repeated["status"] == "recovery_keyrings_already_created_and_verified_not_pinned"
|
||||
assert repeated["first_release_keyring_payload_sha256"] == result[
|
||||
"first_release_keyring_payload_sha256"
|
||||
]
|
||||
|
||||
|
||||
def test_recovery_keyrings_reject_reviewer_reuse_of_any_technical_key(tmp_path: Path) -> None:
|
||||
state, _result, keys = _initialize(tmp_path)
|
||||
technical_private = keys[authority.ROLE_APPLY_SERVICE].private_key
|
||||
reviewer_file = tmp_path / "reviewer-registration.json"
|
||||
_reviewer_registration(reviewer_file, private_key=technical_private)
|
||||
|
||||
with pytest.raises(authority.AuthorityError, match="must differ"):
|
||||
authority.build_recovery_keyrings(
|
||||
state,
|
||||
reviewer_file,
|
||||
epoch=1,
|
||||
unprotect=_unprotect,
|
||||
now=datetime(2026, 9, 27, 2, 0, tzinfo=UTC),
|
||||
)
|
||||
|
||||
|
||||
def test_pin_preview_and_apply_update_only_public_hashes_and_epoch_in_temp_sources(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
state, _result, keys = _initialize(tmp_path)
|
||||
reviewer_file = tmp_path / "reviewer-registration.json"
|
||||
_reviewer_registration(reviewer_file)
|
||||
recovery = authority.build_recovery_keyrings(
|
||||
state,
|
||||
reviewer_file,
|
||||
epoch=1,
|
||||
unprotect=_unprotect,
|
||||
now=datetime(2026, 9, 27, 2, 0, tzinfo=UTC),
|
||||
)
|
||||
repo = _copy_pin_sources(tmp_path)
|
||||
before = {
|
||||
path.relative_to(repo): path.read_bytes()
|
||||
for path in repo.rglob("*.py")
|
||||
}
|
||||
|
||||
plan = authority.plan_pin_updates(
|
||||
repo,
|
||||
state,
|
||||
Path(recovery["directory"]),
|
||||
epoch=1,
|
||||
unprotect=_unprotect,
|
||||
)
|
||||
preview = authority.pin_plan_summary(plan)
|
||||
assert preview["status"] == "preview_only_not_applied"
|
||||
assert all(path.read_bytes() == before[path.relative_to(repo)] for path in repo.rglob("*.py"))
|
||||
assert all(len(str(value)) in {1, 64} for value in preview["values"].values())
|
||||
|
||||
applied = authority.apply_pin_plan(plan)
|
||||
assert applied["status"] == "pins_applied_not_frozen_not_signed"
|
||||
assert applied["changed_file_count"] == 3
|
||||
rc06 = (repo / "yuqei-infra/scripts/build-rc06-publication-batch.py").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
first = (repo / "yuqei-infra/deploy/plan-first-release-snapshot-restore.py").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
rollback = (repo / "yuqei-infra/deploy/plan-rollback-rehearsal.py").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert f'TRUSTED_APPROVAL_ROOT_PUBLIC_KEY_SHA256 = "{keys[authority.ROLE_RC06_ROOT].public_sha256}"' in rc06
|
||||
assert f'TRUSTED_APPLY_SERVICE_PUBLIC_KEY_SHA256 = "{keys[authority.ROLE_APPLY_SERVICE].public_sha256}"' in rc06
|
||||
assert "FROZEN_APPROVAL_KEYRING_EPOCH = 1" in rc06
|
||||
for text in (first, rollback):
|
||||
assert f'TRUSTED_ROOT_PUBLIC_KEY_SHA256 = "{keys[authority.ROLE_RECOVERY_ROOT].public_sha256}"' in text
|
||||
assert re.search(r"(?m)^MIN_KEYRING_EPOCH: int \| None = 1$", text)
|
||||
assert "PRIVATE KEY" not in text
|
||||
|
||||
repeated = authority.plan_pin_updates(
|
||||
repo,
|
||||
state,
|
||||
Path(recovery["directory"]),
|
||||
epoch=1,
|
||||
unprotect=_unprotect,
|
||||
)
|
||||
assert all(change.original == change.proposed for change in repeated.changes)
|
||||
assert authority.apply_pin_plan(repeated)["changed_file_count"] == 0
|
||||
|
||||
|
||||
def test_pin_planner_refuses_unknown_existing_pin_without_overwriting(tmp_path: Path) -> None:
|
||||
state, _result, _keys = _initialize(tmp_path)
|
||||
reviewer_file = tmp_path / "reviewer-registration.json"
|
||||
_reviewer_registration(reviewer_file)
|
||||
recovery = authority.build_recovery_keyrings(
|
||||
state,
|
||||
reviewer_file,
|
||||
epoch=1,
|
||||
unprotect=_unprotect,
|
||||
now=datetime(2026, 9, 27, 2, 0, tzinfo=UTC),
|
||||
)
|
||||
repo = _copy_pin_sources(tmp_path)
|
||||
rc06 = repo / "yuqei-infra/scripts/build-rc06-publication-batch.py"
|
||||
original = rc06.read_text(encoding="utf-8")
|
||||
rc06.write_text(
|
||||
original.replace(
|
||||
'TRUSTED_APPROVAL_ROOT_PUBLIC_KEY_SHA256 = ""',
|
||||
'TRUSTED_APPROVAL_ROOT_PUBLIC_KEY_SHA256 = "' + "f" * 64 + '"',
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# A different already-provisioned pin cannot be replaced by a first-time
|
||||
# provisioning run.
|
||||
with pytest.raises(authority.AuthorityError, match="different epoch|unexpected existing value"):
|
||||
authority.plan_pin_updates(
|
||||
repo,
|
||||
state,
|
||||
Path(recovery["directory"]),
|
||||
epoch=1,
|
||||
unprotect=_unprotect,
|
||||
)
|
||||
|
||||
|
||||
def test_pin_apply_rolls_back_completed_files_when_later_replace_fails(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
state, _result, _keys = _initialize(tmp_path)
|
||||
reviewer_file = tmp_path / "reviewer-registration.json"
|
||||
_reviewer_registration(reviewer_file)
|
||||
recovery = authority.build_recovery_keyrings(
|
||||
state,
|
||||
reviewer_file,
|
||||
epoch=1,
|
||||
unprotect=_unprotect,
|
||||
now=datetime(2026, 9, 27, 2, 0, tzinfo=UTC),
|
||||
)
|
||||
repo = _copy_pin_sources(tmp_path)
|
||||
plan = authority.plan_pin_updates(
|
||||
repo,
|
||||
state,
|
||||
Path(recovery["directory"]),
|
||||
epoch=1,
|
||||
unprotect=_unprotect,
|
||||
)
|
||||
real_replace = authority._write_atomic_replacement
|
||||
calls = 0
|
||||
|
||||
def fail_second(path: Path, raw: bytes) -> None:
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
if calls == 2:
|
||||
raise OSError("fixture replacement failure")
|
||||
real_replace(path, raw)
|
||||
|
||||
monkeypatch.setattr(authority, "_write_atomic_replacement", fail_second)
|
||||
with pytest.raises(authority.AuthorityError, match="transaction failed"):
|
||||
authority.apply_pin_plan(plan)
|
||||
assert all(change.path.read_bytes() == change.original for change in plan.changes)
|
||||
|
||||
|
||||
def test_rc06_candidate_keyring_requires_frozen_pin_and_binds_exact_manifest(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
state, _result, keys = _initialize(tmp_path)
|
||||
reviewer_file = tmp_path / "reviewer-registration.json"
|
||||
registration = _reviewer_registration(reviewer_file)
|
||||
manifest_file = tmp_path / "manifest.json"
|
||||
manifest_raw = authority._canonical({"fixture": "validated elsewhere in this unit test"})
|
||||
manifest_file.write_bytes(manifest_raw)
|
||||
manifest_sha = authority.batch._sha256_bytes(manifest_raw)
|
||||
monkeypatch.setattr(authority.batch, "validate_manifest", lambda value: value)
|
||||
|
||||
with pytest.raises(authority.AuthorityError, match="frozen RC-06 root pin"):
|
||||
authority.build_rc06_candidate_keyring(
|
||||
state,
|
||||
reviewer_file,
|
||||
manifest_file,
|
||||
manifest_sha,
|
||||
unprotect=_unprotect,
|
||||
now=datetime(2026, 9, 27, 3, 0, tzinfo=UTC),
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
authority.batch,
|
||||
"TRUSTED_APPROVAL_ROOT_PUBLIC_KEY_SHA256",
|
||||
keys[authority.ROLE_RC06_ROOT].public_sha256,
|
||||
)
|
||||
monkeypatch.setattr(authority.batch, "FROZEN_APPROVAL_KEYRING_EPOCH", 1)
|
||||
result = authority.build_rc06_candidate_keyring(
|
||||
state,
|
||||
reviewer_file,
|
||||
manifest_file,
|
||||
manifest_sha,
|
||||
unprotect=_unprotect,
|
||||
now=datetime(2026, 9, 27, 3, 0, tzinfo=UTC),
|
||||
)
|
||||
assert result["status"] == "rc06_candidate_keyring_created_not_approved"
|
||||
bundle = Path(result["directory"])
|
||||
keyring = json.loads((bundle / "rc06-approval-keyring.json").read_bytes())
|
||||
assert keyring["authorized_batch_manifest_raw_sha256"] == manifest_sha
|
||||
assert keyring["epoch"] == 1
|
||||
assert keyring["keys"][0]["key_id"] == registration["key_id"]
|
||||
assert keyring["root_key_id"] == authority.batch.TRUSTED_APPROVAL_ROOT_KEY_ID
|
||||
|
||||
repeated = authority.build_rc06_candidate_keyring(
|
||||
state,
|
||||
reviewer_file,
|
||||
manifest_file,
|
||||
manifest_sha,
|
||||
unprotect=_unprotect,
|
||||
now=datetime(2026, 9, 27, 4, 0, tzinfo=UTC),
|
||||
)
|
||||
assert repeated["status"] == "rc06_candidate_keyring_already_created_and_verified_not_approved"
|
||||
with pytest.raises(authority.AuthorityError, match="SHA-256 mismatch"):
|
||||
authority.build_rc06_candidate_keyring(
|
||||
state,
|
||||
reviewer_file,
|
||||
manifest_file,
|
||||
"f" * 64,
|
||||
unprotect=_unprotect,
|
||||
now=datetime(2026, 9, 27, 4, 0, tzinfo=UTC),
|
||||
)
|
||||
|
||||
|
||||
def test_apply_private_key_provision_command_fails_closed_without_export(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
monkeypatch.setattr(authority, "_assert_runtime", lambda: None)
|
||||
result = authority.main(
|
||||
[
|
||||
"provision-apply-private-key",
|
||||
"--state-directory",
|
||||
os.fspath(tmp_path / "authority-state"),
|
||||
]
|
||||
)
|
||||
captured = capsys.readouterr()
|
||||
assert result == 2
|
||||
assert "not implemented" in captured.err
|
||||
assert '"private_material_exported":false' in captured.err
|
||||
assert list(tmp_path.iterdir()) == []
|
||||
|
||||
|
||||
def test_apply_payload_signing_is_bound_verified_atomic_and_idempotent(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
materials = _formal_apply_materials(tmp_path, monkeypatch)
|
||||
output = tmp_path / "signed-apply-receipts"
|
||||
result = authority.sign_apply_payloads(
|
||||
materials["state"],
|
||||
manifest_file=materials["manifest_file"],
|
||||
manifest_sha256=materials["manifest_sha"],
|
||||
approval_file=materials["approval_file"],
|
||||
approval_sha256=materials["approval_sha"],
|
||||
approval_keyring_file=materials["keyring_file"],
|
||||
approval_keyring_sha256=materials["keyring_sha"],
|
||||
approval_root_public_key_file=materials["root_file"],
|
||||
payloads_file=materials["payloads_file"],
|
||||
payloads_sha256=materials["payloads_sha"],
|
||||
output_directory=output,
|
||||
unprotect=_unprotect,
|
||||
)
|
||||
assert result["status"] == "apply_receipts_signed_and_verified_not_published"
|
||||
assert result["receipt_count"] == 1
|
||||
assert result["business_api_calls"] == 0
|
||||
assert result["private_keys_in_bundle"] is False
|
||||
receipt = json.loads((output / "apply-receipt-001.json").read_bytes())
|
||||
assert authority.batch.validate_apply_receipt(
|
||||
materials["manifest"],
|
||||
materials["manifest_sha"],
|
||||
materials["approval"],
|
||||
materials["keyring"],
|
||||
materials["keys"][authority.ROLE_RC06_ROOT].public_key,
|
||||
materials["keys"][authority.ROLE_APPLY_SERVICE].public_key,
|
||||
materials["approval_sha"],
|
||||
receipt,
|
||||
) == receipt
|
||||
repeated = authority.sign_apply_payloads(
|
||||
materials["state"],
|
||||
manifest_file=materials["manifest_file"],
|
||||
manifest_sha256=materials["manifest_sha"],
|
||||
approval_file=materials["approval_file"],
|
||||
approval_sha256=materials["approval_sha"],
|
||||
approval_keyring_file=materials["keyring_file"],
|
||||
approval_keyring_sha256=materials["keyring_sha"],
|
||||
approval_root_public_key_file=materials["root_file"],
|
||||
payloads_file=materials["payloads_file"],
|
||||
payloads_sha256=materials["payloads_sha"],
|
||||
output_directory=output,
|
||||
unprotect=_unprotect,
|
||||
)
|
||||
assert repeated["status"] == "apply_receipts_already_signed_and_verified_not_published"
|
||||
assert repeated["receipts"] == result["receipts"]
|
||||
|
||||
|
||||
def test_apply_payload_signing_rejects_tamper_cross_approval_and_partial_bundle(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
materials = _formal_apply_materials(tmp_path, monkeypatch)
|
||||
|
||||
tampered = dict(materials["apply_payload"])
|
||||
tampered["batch_manifest_payload_sha256"] = "f" * 64
|
||||
tampered_file = tmp_path / "tampered-payload.json"
|
||||
tampered_raw = authority._canonical(tampered)
|
||||
tampered_file.write_bytes(tampered_raw)
|
||||
tampered_output = tmp_path / "tampered-output"
|
||||
with pytest.raises(authority.AuthorityError, match="not bound to this batch"):
|
||||
authority.sign_apply_payloads(
|
||||
materials["state"],
|
||||
manifest_file=materials["manifest_file"],
|
||||
manifest_sha256=materials["manifest_sha"],
|
||||
approval_file=materials["approval_file"],
|
||||
approval_sha256=materials["approval_sha"],
|
||||
approval_keyring_file=materials["keyring_file"],
|
||||
approval_keyring_sha256=materials["keyring_sha"],
|
||||
approval_root_public_key_file=materials["root_file"],
|
||||
payloads_file=tampered_file,
|
||||
payloads_sha256=authority.batch._sha256_bytes(tampered_raw),
|
||||
output_directory=tampered_output,
|
||||
unprotect=_unprotect,
|
||||
)
|
||||
assert not tampered_output.exists()
|
||||
|
||||
second_payload = dict(materials["approval"]["payload"])
|
||||
second_payload["approved_at"] = "2026-09-27T03:15:00Z"
|
||||
second_signature = materials["reviewer_private"].sign(
|
||||
authority.batch.APPROVAL_SIGNATURE_DOMAIN + authority._canonical(second_payload)
|
||||
)
|
||||
second_approval = {
|
||||
"schema_version": authority.batch.APPROVAL_SCHEMA_VERSION,
|
||||
"approval_id": (
|
||||
"rc06-batch-approval-"
|
||||
+ authority.batch._sha256_bytes(
|
||||
authority._canonical(second_payload) + second_signature
|
||||
)[:24]
|
||||
),
|
||||
"payload": second_payload,
|
||||
"signature": base64.b64encode(second_signature).decode("ascii"),
|
||||
}
|
||||
second_approval_file = tmp_path / "second-approval.json"
|
||||
second_approval_raw = authority._canonical(second_approval)
|
||||
second_approval_file.write_bytes(second_approval_raw)
|
||||
with pytest.raises(authority.AuthorityError, match="not bound to the approval receipt"):
|
||||
authority.sign_apply_payloads(
|
||||
materials["state"],
|
||||
manifest_file=materials["manifest_file"],
|
||||
manifest_sha256=materials["manifest_sha"],
|
||||
approval_file=second_approval_file,
|
||||
approval_sha256=authority.batch._sha256_bytes(second_approval_raw),
|
||||
approval_keyring_file=materials["keyring_file"],
|
||||
approval_keyring_sha256=materials["keyring_sha"],
|
||||
approval_root_public_key_file=materials["root_file"],
|
||||
payloads_file=materials["payloads_file"],
|
||||
payloads_sha256=materials["payloads_sha"],
|
||||
output_directory=tmp_path / "cross-approval-output",
|
||||
unprotect=_unprotect,
|
||||
)
|
||||
|
||||
partial = dict(materials["apply_payload"])
|
||||
partial["item_results"] = partial["item_results"][:-1]
|
||||
payload_list = [materials["apply_payload"], partial]
|
||||
payload_list_file = tmp_path / "payload-list.json"
|
||||
payload_list_raw = authority._canonical(payload_list)
|
||||
payload_list_file.write_bytes(payload_list_raw)
|
||||
partial_output = tmp_path / "partial-output"
|
||||
with pytest.raises(authority.AuthorityError, match="74-item online evidence drift"):
|
||||
authority.sign_apply_payloads(
|
||||
materials["state"],
|
||||
manifest_file=materials["manifest_file"],
|
||||
manifest_sha256=materials["manifest_sha"],
|
||||
approval_file=materials["approval_file"],
|
||||
approval_sha256=materials["approval_sha"],
|
||||
approval_keyring_file=materials["keyring_file"],
|
||||
approval_keyring_sha256=materials["keyring_sha"],
|
||||
approval_root_public_key_file=materials["root_file"],
|
||||
payloads_file=payload_list_file,
|
||||
payloads_sha256=authority.batch._sha256_bytes(payload_list_raw),
|
||||
output_directory=partial_output,
|
||||
unprotect=_unprotect,
|
||||
)
|
||||
assert not partial_output.exists()
|
||||
|
||||
|
||||
def test_authority_launcher_uses_authority_script_and_forwards_non_secret_arguments() -> None:
|
||||
text = AUTHORITY_LAUNCHER.read_text(encoding="utf-8-sig")
|
||||
assert '"rc06-release-trust-bootstrap.py"' in text
|
||||
assert "ValueFromRemainingArguments = $true" in text
|
||||
assert "$authorityScript $Command @CommandArguments" in text
|
||||
assert "rc06-single-reviewer-signing.py" not in text
|
||||
assert "password" not in text.casefold()
|
||||
assert "passphrase" not in text.casefold()
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "nt", reason="Windows CurrentUser DPAPI is platform-specific")
|
||||
def test_windows_current_user_dpapi_round_trip_has_role_entropy() -> None:
|
||||
raw = b"not-a-real-private-key-fixture"
|
||||
protected = authority._protect_current_user(raw, authority.ROLE_RC06_ROOT)
|
||||
assert protected != raw
|
||||
assert authority._unprotect_current_user(protected, authority.ROLE_RC06_ROOT) == raw
|
||||
with pytest.raises(authority.AuthorityError):
|
||||
authority._unprotect_current_user(protected, authority.ROLE_APPLY_SERVICE)
|
||||
Reference in New Issue
Block a user