kms/release: wire cc/attest chain verifier into Release path

Red Final N1 fix. Release() previously called only Attestation.Verify(nonce)
and ReleasePolicy.Permits() before sealing the session key. Neither step
performs the actual SEV-SNP / TDX / NRAS chain verification — AMD KDS
lookup, ARK→ASK→VCEK chain, NRAS JWT — leaving the #222 go-sev-guest
verifier as dead code on the release path. A worker that fabricates a
CompositeAttestation envelope whose Verify(nonce) returns true and whose
RIM/HW/issuer membership matches policy could harvest a session key
without ever presenting AMD-signed evidence.

Wire-in:
  - kms.CompositeAttestation gains VerifyEvidence(ctx, opts...) returning
    []*attest.VerifiedReport. *attestation.CompositeAttestation already
    implements this method (composite.go:320 → attest.Dispatch per blob).
  - ReleaseRequest gains optional Ctx + AttestOptions for context plumbing
    + per-call policy (e.g. attest.WithExpectedReportData(nonce[:])).
  - Release() calls VerifyEvidence between the cheap nonce check and the
    policy permit step. Any chain failure → ErrAttestationChain joined
    with ErrPolicyRefused via errors.Join. No silent acceptance.
  - New sentinel ErrAttestationChain.

Tests (go test -v -short -race -count=1 -timeout 60s ./pkg/kms/...):
  - all 14 existing TestRelease_/TestNonce_/TestAAD_ remain green
  - NEW TestRelease_RefusesOnChainFailure: Verify(nonce)=true but
    VerifyEvidence returns ErrChainInvalid → MUST surface
    ErrAttestationChain wrapped under ErrPolicyRefused
  - NEW TestRelease_AcceptsOnChainPass: paired happy-path so the
    chain-step is the only difference, not a wiring regression

Also pulled in cherry-pick of canonical go-sev-guest sums (c4f1e22) so
cc/attest builds reproducibly outside the dirty local cache.
This commit is contained in:
2026-04-28 08:47:09 -07:00
parent 095b9c6460
commit be7fa85a9b
5 changed files with 184 additions and 95 deletions
+1
View File
@@ -111,6 +111,7 @@ require (
github.com/golang/mock v1.6.0 // indirect
github.com/google/flatbuffers v25.12.19+incompatible // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-sev-guest v0.14.1 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
github.com/googleapis/gax-go/v2 v2.21.0 // indirect
+2
View File
@@ -55,6 +55,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/ansel1/merry v1.8.1/go.mod h1:wJVu1mHEtEUWq5zTTX9RiWjcE+xL8y7BGYl2VTYdP7M=
github.com/ansel1/merry/v2 v2.2.2/go.mod h1:sludwzkWfhZHOF4jSOViv+t2nnu2HNmtsMKzAfwmVB8=
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
+3 -62
View File
@@ -159,11 +159,8 @@ github.com/chelnak/ysmrr v0.6.0/go.mod h1:56JSrmQgb7/7xoMvuD87h3PE/qW6K1+BQcrgWt
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
@@ -313,9 +310,6 @@ github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
@@ -335,11 +329,11 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ=
github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -472,14 +466,10 @@ github.com/luxfi/address v1.0.1/go.mod h1:5j3Eh66v9zvv1GbNdZwt+23krV8JlSDaRzmWZU
github.com/luxfi/ai v0.0.0-20251225021023-3f15131f2bd1/go.mod h1:4+FVvnCo1QqM3NaVUte0da4zgH2IblVLmUxKQi0BwE0=
github.com/luxfi/api v1.0.1/go.mod h1:N2Ymyqo3SoB6/DmPXRV6W377k2qNqZujSP/B8fbIB9U=
github.com/luxfi/atomic v0.0.1/go.mod h1:UzFk1K7VXjJhjm2c4Yiu5HydKbWi36kFtrlDKbm8MG8=
github.com/luxfi/atomic v1.0.0 h1:xUV60MuzRvXngaQ1sM0yVC2v4TRoLlUGkkH7M9PS4yw=
github.com/luxfi/atomic v1.0.0/go.mod h1:0G2mTlQ6TXWHICUHrUUPu1/qAiIyR4gSZ2tva9ci/bI=
github.com/luxfi/bft v0.1.0/go.mod h1:IUT167Erk2h8z36197Yrmu/3CBIsZku5WlEG8O2NMp4=
github.com/luxfi/cache v1.1.0/go.mod h1:9GvlEEE9rFPaaWxvVpSPwW8ZMo2+8VMNNcuPa4AwzPg=
github.com/luxfi/cache v1.2.0/go.mod h1:Esn48WMB3JL/+UOdCEV2Chyjr1MG3SSckXWLhB/P080=
github.com/luxfi/codec v1.1.2/go.mod h1:T+DV0Jau5bi+8wMcaoj51zS1rgt80VovQKzaN2CV5Mg=
github.com/luxfi/codec v1.1.4 h1:Yl8ZalMNkqo7cD6R9AjczAajkLOmsjyZ9+DASVYHrvg=
github.com/luxfi/codec v1.1.4/go.mod h1:oGQ3j6E8c2P0pL0irYtWkrB1hmDUFIE0puXHK4gV5KI=
github.com/luxfi/compress v0.0.2/go.mod h1:cfa7L3STdZdsMwJc9faZML1modLNh1n2u72n/DOfuNU=
github.com/luxfi/compress v0.0.4/go.mod h1:/eN0eca98igoE6DDsLv/4w7n1gsQKwa+HUTxmWz/iWw=
github.com/luxfi/concurrent v0.0.2/go.mod h1:SwEDrQXVAXgBiObXVg0pfmLzU1xsa9ZeBRptkBdyNUU=
@@ -492,8 +482,6 @@ github.com/luxfi/consensus v1.22.62/go.mod h1:sEAGM6n/tGXx7Sd5d7F22862rqT8n2y+Z1
github.com/luxfi/consensus v1.22.67/go.mod h1:mUffRVhwE3zz8bHwAIXT0xSroqOceszepHl8Tp6GwrI=
github.com/luxfi/const v1.4.1/go.mod h1:qWfecjdImPY3kXsy3UAe4ZVpMcsp2/iUlHwUMPVREoI=
github.com/luxfi/constants v1.4.2/go.mod h1:xk+0eA7UY1GkHrmI7yqR+9LDe4TUe3p1VfRvs6cJbNM=
github.com/luxfi/constants v1.4.7 h1:e/Qs+DQP3pugle3Zncq6fZCxKgqqtbyD/z7Gm4ZjsYg=
github.com/luxfi/constants v1.4.7/go.mod h1:hOszZ2NDQ8gMZKncfcZ67PXkb5OIbnwAzXC3oFbQwW0=
github.com/luxfi/container v0.0.2/go.mod h1:nnK+TxHmGry0mGnfun4jx0tLqWJgV/GbZXTETMuFJn4=
github.com/luxfi/coreth v0.15.71/go.mod h1:Z/OTRrw+q+uSNrCh1Fg0hkMf1fRquzbTQelx7wR0HOM=
github.com/luxfi/crypto v1.17.28/go.mod h1:v6eaW5ejuzW2gecChWKQqx4CtVhoM1b+e40ro/6WVPg=
@@ -501,28 +489,20 @@ github.com/luxfi/crypto v1.17.36/go.mod h1:UHzltsXPDF/tGcmTiL/DbkClAflr3YmvOaXlw
github.com/luxfi/crypto v1.17.37/go.mod h1:vu+pCHdyPQZULzrA+K+lTFYrg8vOtNdu+/5Vko8RzZ0=
github.com/luxfi/crypto v1.17.38/go.mod h1:G2t1GQvPsrwnzwyVEj0LQDuX2AWZVI5kEAPyVeicc5o=
github.com/luxfi/crypto v1.17.39/go.mod h1:mChLWmW4CLR1wAN6CeJTveCzUv0DTzGQnYgq01x3W0U=
github.com/luxfi/crypto v1.17.45 h1:uGK0y4+aLipE/M0YIQ5hcsWv0ZG0E4cPv03a94K/eLE=
github.com/luxfi/crypto v1.17.45/go.mod h1:GnAkhQ7HNs3X0Tzx5nOONS3kl0yRmWHbDcRO5ffILsg=
github.com/luxfi/czmq/v4 v4.2.2/go.mod h1:WFY9ldUbeU900P8bcIRr7Jim23veOhJL5A5zSpTogdU=
github.com/luxfi/database v1.17.35/go.mod h1:dYxaF30KGJQzuxA3VH7+WGU5/dhwRyzLgzSk9F3wje0=
github.com/luxfi/database v1.17.38/go.mod h1:q6BJNN+47VtkmkGrswQmC0ikERYAdlmZCYhGTXkF4mM=
github.com/luxfi/database v1.17.39/go.mod h1:iHE5evTMxltmYGe3QkewrZv1xgGrDBbqcv0y4NkDJcI=
github.com/luxfi/database v1.17.40/go.mod h1:du1q0PemBc0gZcdtHhsARs6mIsZM3a5R/C040DVYLCM=
github.com/luxfi/database v1.17.42/go.mod h1:0nArLBm0r42J103oHR2xkAe8+mftuz5jtFgNylU1hgg=
github.com/luxfi/database v1.17.44 h1:hfiTls7sqbweW+o4iaZqB8P997paC+vpgWmhN6v5MJ0=
github.com/luxfi/database v1.17.44/go.mod h1:6Ey5y3I0WNLHbxIlIdFqUuKfBg+b0fAgTA8FgRgQ8zg=
github.com/luxfi/evm v0.8.30/go.mod h1:S0fChMeuWV4XqY4OB6RozqvyHcm+imJCKyqsMY713hY=
github.com/luxfi/fhe v1.7.6-0.20260106060801-28e308e4c2f8/go.mod h1:SVO5KV3GKMW5U0XeQSV4x7fryNWHsCjyhhhAoIPCpG8=
github.com/luxfi/fhe v1.7.7 h1:RP26i3CxvNy2y/3LKiJPgd9uR5MrlG/MvQnv5rkQFhk=
github.com/luxfi/fhe v1.7.7/go.mod h1:FHfGNgKL5ZLcnKVKFVNLDJOBbQyQm5DGpVrMnY8ASJo=
github.com/luxfi/filesystem v0.0.1/go.mod h1:OQVSU6XNwqrr1AI+MqkID2taHUclx7NYmmr3svgttec=
github.com/luxfi/formatting v1.0.0/go.mod h1:lswh6dPQ4E9Q+nZLwispV+Jdthr5R1PkYTBA/LhuSgE=
github.com/luxfi/formatting v1.0.1/go.mod h1:mYzNf5DJOiqSSKUPzNj5dKy4tstFbN3pZlkI5716eKc=
github.com/luxfi/genesis v1.5.21/go.mod h1:XI1T2oMh/VS/f+LWMWPDQIEhEneCNVHodhn2Qr3rFMU=
github.com/luxfi/genesis v1.5.22/go.mod h1:ernOqYxKPEZnwu/QvgJ9y4Lb+IUpfoOtZ3/DubzExv0=
github.com/luxfi/geth v1.16.73/go.mod h1:CPzps7Zp3I7Og3KT3m+Qs41WW8z+YPGx1TOJQnHdBHY=
github.com/luxfi/geth v1.16.79 h1:MtP8ZUuSVZDjmZDa1kTJCl0PpG8+wGbQft652GO6a3A=
github.com/luxfi/geth v1.16.79/go.mod h1:6xNi4sHoh0v1kBf0TOjmFhBg5+4umbSqrwiFZsDanZ0=
github.com/luxfi/go-bip32 v1.0.2/go.mod h1:bc7/LXDKAJQZ/F0Xjf5yXaTZxY9/ssLb4FC+Hxn/cDk=
github.com/luxfi/go-bip39 v1.1.2/go.mod h1:96de9VkR2kY/ASAnhMtvt3TSh+PZkAFAngNj0GjRGDo=
github.com/luxfi/gpu v0.30.0/go.mod h1:7pFsHqra1Vrmy2aGXVEPKeKsPR+Bn+QmBXuByK3fdPA=
@@ -541,17 +521,11 @@ github.com/luxfi/log v1.2.1/go.mod h1:0vJzb4Hl9fvgwWDMKcbCD/SYH3bO0iSmgZMcYdfdl1
github.com/luxfi/log v1.4.0/go.mod h1:64IE3xRMJcpkQwnPUfJw3pDj7wU0kRS7BZ9wM7R72jk=
github.com/luxfi/lpm v1.0.5/go.mod h1:dS/NS6muB9zlHWbO/VeCMYCe53YwIjXDHg/jif7TejI=
github.com/luxfi/math v1.2.2/go.mod h1:C8STnF2H+D6rqBPt248CiWY2TGuJgdtv/+4UqrT15iM=
github.com/luxfi/math v1.2.4 h1:iVz7s0gToCNUU/2cLT8gUa8MLzc0YRp4jBmeXBeKdXk=
github.com/luxfi/math v1.2.4/go.mod h1:i+Am9YvFsqDE75ZW8MPE62XCXGukXNiN/7mD9SKxxZY=
github.com/luxfi/math/safe v0.0.1/go.mod h1:EejrmOJHh03YAD8+Zww8cPcMR1K3Q2I7w1dX4sMloeo=
github.com/luxfi/mdns v0.1.0 h1:VB3mQcETc9j5SY1S6lAgFtuGr/rjWuDgPYnxS+OKWMQ=
github.com/luxfi/mdns v0.1.0/go.mod h1:/3dheKVjUk2yiS/ocH1IDzeLXOIe+kpVsErIGDOZdiQ=
github.com/luxfi/metric v1.4.9/go.mod h1:JlAAc4Jm5apoU2DHEgt57UHD7+8AjMdpP2rQJ/zaACY=
github.com/luxfi/metric v1.4.10/go.mod h1:3OIQ+e6mciesm3cik3Q4CSOAvTlBy4JA0ZoKzCB9APo=
github.com/luxfi/metric v1.4.11/go.mod h1:EAFmkzd5EX654G48HXKIsyJufOUa4sxmOJKvwSRbjng=
github.com/luxfi/metric v1.4.12/go.mod h1:EAFmkzd5EX654G48HXKIsyJufOUa4sxmOJKvwSRbjng=
github.com/luxfi/metric v1.5.1 h1:6tVarXMnNR3Xzud8FYUHjtXabTll3HI/OEqG0tgLAcI=
github.com/luxfi/metric v1.5.1/go.mod h1:PkD4D4JoGuyKtfUkqPNYkrg9xKrJeNVZFdW/5XvAe/A=
github.com/luxfi/mock v0.1.0/go.mod h1:izF+9K0gGzFC9zERn6Po37v46eLdPB+EIsDjL3GLk+U=
github.com/luxfi/net v0.0.1/go.mod h1:JTPsJHaUcpF0HruczW14FlUTjPk78xF73XGSMQa86NA=
github.com/luxfi/netrunner v1.14.35/go.mod h1:vfeCr7L49R9yoSjZCpZe2+xtoNdPt4fstEtbKt7Jtls=
@@ -585,8 +559,6 @@ github.com/luxfi/staking v1.0.0/go.mod h1:E+fm3upfQTOu0fLCr3Uo4xYgAm7r/vEzgcUpNA
github.com/luxfi/staking v1.1.0/go.mod h1:xwYacmreoKoy31ELT6QPno6b5M8q7bg4zJrmmt524X4=
github.com/luxfi/sys v0.0.0-20260110090042-50187ec5ffd8/go.mod h1:Y0UwjnEJYSbGM7IvJbKYLf8BWNVLfT8Oz00bEiDITHw=
github.com/luxfi/threshold v1.5.1/go.mod h1:OsWmH2foqB2K4vVpHTjSHrVM4oveSzqOZb/k+fqNsYY=
github.com/luxfi/threshold v1.6.0 h1:pq/n1iYowws+CXe/+slJodjPlJ152hFF94luHp2E82c=
github.com/luxfi/threshold v1.6.0/go.mod h1:XWSfWM3n/tVggluh/LH7is4vYL85zmyMHCqfz2qrYVU=
github.com/luxfi/timer v1.0.1/go.mod h1:PQ15H3AEUfvWVf0kv5WhIGPWmhr9BxBv/VKLyyekpk4=
github.com/luxfi/tls v1.0.0/go.mod h1:QZXWuYFZPfGA9AyRIBXgIuXPrmvWWHtQkJtplZnTlWc=
github.com/luxfi/tls v1.0.2/go.mod h1:t6Fr+KYKDZEz1KxciGHzRrj/KsQIx8V3iG/8YiSqpvU=
@@ -596,8 +568,6 @@ github.com/luxfi/units v1.0.0/go.mod h1:tma28v4ed1tupdS0kpSeyO+u1wWK/g1NqODPbN1Y
github.com/luxfi/upgrade v1.0.0/go.mod h1:DZF7dO4erhUEKf907B2e65k4/vGkCPkUngpvIEUS3eI=
github.com/luxfi/utils v1.1.0/go.mod h1:ABqhBdGNig0CaDcnNYldv1byS0BTNi5IKPbJSPF1p98=
github.com/luxfi/utils v1.1.1/go.mod h1:DL7634uXEcCu2tCBll0bmX0SVnMJeABbmhm5NkLMu78=
github.com/luxfi/utils v1.1.4 h1:8OY0jXCkpp6OotN1Y++6DATJkvtEwzq2k0p56imJlAk=
github.com/luxfi/utils v1.1.4/go.mod h1:c3yz1RjzrB+cs5GZm+q1T3/2cCKElO9vxm9yRRtgSEM=
github.com/luxfi/utxo v0.0.1/go.mod h1:cwkUv/J6zNohRI7+xrj03wi39bm6CYkek+6XRhQrPTU=
github.com/luxfi/utxo v0.2.3/go.mod h1:I23ZJND7kU6NUklD2nY24mU93jQXBPGu1c9Y1hjX9Vk=
github.com/luxfi/validators v1.0.0/go.mod h1:QGppjtI/gqprbplWc10J+4OIK8UYWpv+53mfH2aKhy4=
@@ -615,13 +585,9 @@ github.com/luxfi/warp v1.18.2/go.mod h1:qu3TZv98repNhIDf04I6pzXePSgHjzP+JoSfWmF4
github.com/luxfi/warp v1.18.4/go.mod h1:5kXaZGYBBzOFyzPoDukVynvTTPskElrA8EQ5TVgovqc=
github.com/luxfi/warp v1.18.5/go.mod h1:SFyC529HDvbP/TWRAdYQSyJUliMa5JKFRtBrTLEElp4=
github.com/luxfi/zap v0.0.0-20260126200933-22a2a4c72dfb/go.mod h1:uvmQO9kVvOfBcqPhY52wx5Z1O6L/4Mv2tBrJDc3gVgM=
github.com/luxfi/zap v0.2.0 h1:RzvOkp3EoN5UCkpnqfObHLM1sEHy7YcxXKuermhE/VA=
github.com/luxfi/zap v0.2.0/go.mod h1:2hydPSa/XMCMtfW6/DC9M5Bt03N5h75QwCV5Vypsqr4=
github.com/luxfi/zapdb v1.0.0 h1:u8TF4g/bWew5mQwiR/Mcu+QIFYTT6BP5Ty8oBaDgalA=
github.com/luxfi/zapdb v1.0.0/go.mod h1:tT2WF2iSabQHrAYe9l7LHscaIBfN2YtCbdgOrCyCI2A=
github.com/luxfi/zapdb/v4 v4.9.1/go.mod h1:gYJjLcFCxmUATZ6Zg7iZHwAbgBMxGVP1g8UOWomK70Y=
github.com/luxfi/zapdb/v4 v4.9.3 h1:KV1DrbZqtZsZvrWyPw5D8WHR0DxzqW2jHjqx6obxgoM=
github.com/luxfi/zapdb/v4 v4.9.3/go.mod h1:BrpLaDeYy+smON4ASDPCFKWrQ58CXhy3WNEPGna7vuU=
github.com/luxfi/zmq/v4 v4.2.2/go.mod h1:omy8ymZAhx60lx/nPsWT6uOV11uNFVnjtls+Hi5KqaU=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
@@ -796,8 +762,6 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
@@ -839,7 +803,6 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJu
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
@@ -880,6 +843,7 @@ go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
@@ -944,8 +908,6 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -967,8 +929,6 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -1026,9 +986,6 @@ golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwE
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/perf v0.0.0-20230113213139-801c7ef9e5c5/go.mod h1:UBKtEnL8aqnd+0JHqZ+2qoMDwtuy6cYhhKNoHLBiTQc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
@@ -1057,9 +1014,6 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1080,13 +1034,10 @@ golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1124,7 +1075,6 @@ golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -1187,15 +1137,7 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
@@ -1335,7 +1277,6 @@ gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3M
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/vmihailenco/msgpack.v2 v2.9.2/go.mod h1:/3Dn1Npt9+MYyLpYYXjInO/5jvMLamn+AEGwNEOatn8=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+91 -10
View File
@@ -18,6 +18,9 @@
// (challenge-response binding — a stolen nonce cannot be replayed
// under a different jobID, and a forged nonce cannot trick the gate
// into accepting a worker-chosen challenge).
// 6. Every CPU TEE / GPU evidence blob in the envelope cryptographically
// chains to its pinned vendor root via cc/attest.Dispatch (AMD KDS +
// VCEK for SEV-SNP, Intel quote chain for TDX, NRAS JWT for NVIDIA).
//
// On success the gate returns the session key sealed (HPKE-style: ECDH
// over X25519, ChaCha20-Poly1305 AEAD) to the worker's GPU TEE public
@@ -35,6 +38,7 @@
package kms
import (
"context"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
@@ -46,22 +50,44 @@ import (
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/curve25519"
"github.com/luxfi/mpc/cc/attest"
)
// CompositeAttestation is the contract surface the release gate consumes
// from pkg/attestation (LP-127, v0.62). We declare the interface here so
// pkg/kms does not depend on pkg/attestation; the v0.62 concrete type
// satisfies via method-set match.
// pkg/kms does not depend on pkg/attestation directly; the v0.62
// concrete type satisfies via method-set match.
//
// The attestation MUST bind a freshly-generated nonce supplied by the
// gate (challenge-response) and the worker's TEE public key. Either
// failure mode collapses release to "refused".
//
// VerifyEvidence is the wire-in for the canonical cc/attest chain
// verifier. The gate calls VerifyEvidence after the cheap nonce check
// and BEFORE policy / seal so that a forged report whose Verify(nonce)
// returns true but whose chain does not validate cannot harvest a
// session key.
type CompositeAttestation interface {
// Verify checks NVTrust + TPM + nonce. Returns false on any
// cryptographic mismatch. Errors only on unrecoverable failure
// (parsing, root-of-trust load) — a forged attestation is (false, nil).
Verify(expectedNonce [32]byte) (bool, error)
// VerifyEvidence cryptographically validates every CPU TEE / GPU
// evidence blob in the envelope using cc/attest:
//
// - SEV-SNP → AMD KDS lookup, ARK→ASK→VCEK chain, report-signature
// - TDX → Intel quote chain
// - NRAS → NVIDIA NRAS JWT verify against pinned issuer key
//
// Returns the per-evidence verified reports on success; any
// chain-verify failure → (nil, err) and the gate MUST refuse
// release. Wire-in for #222 go-sev-guest: without this step a
// worker can craft an envelope whose Verify(nonce) returns true
// while the actual blobs are unsigned or signed by a non-AMD key.
VerifyEvidence(ctx context.Context, opts ...attest.Option) ([]*attest.VerifiedReport, error)
// RIMDigest returns the Reference Integrity Manifest digest the
// worker measured. Compared to ReleasePolicy.RequiredRIM.
RIMDigest() [32]byte
@@ -157,11 +183,24 @@ func (s SealedSessionKey) AAD(teePub [32]byte) []byte {
// ReleaseRequest carries everything the gate needs in one shot. JobID
// is opaque to the gate (used only for AAD binding).
//
// Ctx, when non-nil, is threaded into the cc/attest chain verifier so
// AMD KDS / NRAS network calls cannot stall the gate. When nil the
// gate uses context.Background().
//
// AttestOptions are forwarded to VerifyEvidence; production callers
// MUST include attest.WithExpectedReportData(rec.Nonce[:]) (or an
// equivalent measurement pin) so the chain verifier refuses any quote
// whose REPORT_DATA does not equal the gate-issued nonce. The gate
// does not auto-bind here because the REPORT_DATA framing is
// kind-specific (64-byte field on SEV-SNP/TDX, JWT claim on NRAS).
type ReleaseRequest struct {
JobID [32]byte
Epoch uint64
Nonce [32]byte // gate-issued, attested-over by the worker
Attestation CompositeAttestation
JobID [32]byte
Epoch uint64
Nonce [32]byte // gate-issued, attested-over by the worker
Attestation CompositeAttestation
Ctx context.Context
AttestOptions []attest.Option
}
// ReleaseGate verifies attestation + releases a session key sealed to
@@ -180,6 +219,7 @@ type ReleaseGate interface {
// - hardware not in allowlist
// - epoch mismatch (replay)
// - jobID re-use within the current epoch (replay)
// - cc/attest chain verify failure on any evidence blob
Release(req ReleaseRequest) (SealedSessionKey, error)
// Rotate advances the epoch — invalidates all in-flight nonces.
@@ -191,6 +231,16 @@ type ReleaseGate interface {
// Callers MUST treat this as a hard refusal — never as "approved by default".
var ErrPolicyRefused = errors.New("kms/release: refused")
// ErrAttestationChain is returned when the cc/attest chain verifier
// (AMD KDS + VCEK validation, NVIDIA NRAS JWT, Intel TDX quote)
// refuses any evidence blob in the envelope. Wrapped by
// ErrPolicyRefused via errors.Join so callers can switch on either
// sentinel; the gate releases NO key when this fires. Red Final N1
// wire-in: a forged envelope whose Verify(nonce) returns true but
// whose evidence does not chain to the pinned vendor root must
// produce ErrAttestationChain, not silent acceptance.
var ErrAttestationChain = errors.New("kms/release: attestation chain verify failed")
// DefaultReplayWindow is how long a consumed jobID stays in the replay
// set after Release(). 1 hour absorbs clock skew + worker retries
// without growing the consumed-set unbounded.
@@ -318,10 +368,20 @@ func (g *LocalReleaseGate) Issue(jobID [32]byte) ([32]byte, uint64, error) {
// 1. epoch match (cheap)
// 2. lookup pending record (storage hit)
// 3. constant-time nonce equality (no timing leak on jobID enumeration)
// 4. attestation verify (expensive crypto)
// 5. policy permits (membership check)
// 6. seal session key (key derivation + AEAD)
// 7. consume (atomic — only after all checks pass)
// 4. attestation nonce verify (cheap, in-memory)
// 5. cc/attest chain verify — VCEK / KDS / SEV-SNP / TDX / NRAS
// (network + signature, the actual root-of-trust step). Without
// this step the audit-bypass exists: a worker crafts an envelope
// whose Verify(nonce) returns true but whose evidence blobs do not
// chain to the pinned vendor root.
// 6. policy permits (RIM + hardware membership)
// 7. seal session key (ECDH + AEAD)
// 8. consume (atomic — only after all checks pass)
//
// Any failure at step 4 → ErrPolicyRefused. Any failure at step 5 →
// ErrPolicyRefused joined with ErrAttestationChain. No silent
// acceptance; a cryptographically valid nonce binding is NOT
// sufficient on its own.
func (g *LocalReleaseGate) Release(req ReleaseRequest) (SealedSessionKey, error) {
g.mu.Lock()
defer g.mu.Unlock()
@@ -368,6 +428,27 @@ func (g *LocalReleaseGate) Release(req ReleaseRequest) (SealedSessionKey, error)
if !ok {
return SealedSessionKey{}, fmt.Errorf("%w: attestation invalid", ErrPolicyRefused)
}
// Step 5 — the canonical chain verifier. Dispatches every
// supported CPU TEE / GPU evidence blob to cc/attest:
//
// - SEV-SNP → AMD KDS lookup, ARK→ASK→VCEK chain, signature
// - TDX → Intel quote chain
// - NRAS → NVIDIA NRAS JWT verify against pinned issuer key
//
// Red Final N1: without this step the #222 go-sev-guest wins are
// dead code — Verify(nonce) is trivially satisfied by a worker
// that fabricates the envelope. VerifyEvidence forces the actual
// cryptographic chain check; any failure → ErrAttestationChain
// wrapped under ErrPolicyRefused.
ctx := req.Ctx
if ctx == nil {
ctx = context.Background()
}
if _, err := req.Attestation.VerifyEvidence(ctx, req.AttestOptions...); err != nil {
return SealedSessionKey{}, errors.Join(ErrPolicyRefused, fmt.Errorf("%w: %v", ErrAttestationChain, err))
}
if !g.policy.Permits(req.Attestation) {
return SealedSessionKey{}, fmt.Errorf("%w: policy denied (RIM or hardware)", ErrPolicyRefused)
}
+87 -23
View File
@@ -1,12 +1,15 @@
package kms
import (
"context"
"crypto/rand"
"errors"
"testing"
"time"
"golang.org/x/crypto/curve25519"
"github.com/luxfi/mpc/cc/attest"
)
// x25519Basepoint multiplies priv by the curve25519 basepoint.
@@ -17,12 +20,17 @@ func x25519Basepoint(priv []byte) ([]byte, error) {
// fakeAttestation is a deterministic stub. Verify() succeeds iff
// expectedNonce equals the nonce captured at construction. RIM and
// hardware are caller-supplied so the test exercises Permits().
//
// VerifyEvidence simulates the cc/attest chain dispatch. Default is
// success. Set failChain=true to force a chain-verify failure — used
// by TestRelease_RefusesOnChainFailure.
type fakeAttestation struct {
expectNonce [32]byte
rim [32]byte
hw [32]byte
teePub [32]byte
failVerify bool
failChain bool
}
func (f *fakeAttestation) Verify(expectedNonce [32]byte) (bool, error) {
@@ -31,6 +39,13 @@ func (f *fakeAttestation) Verify(expectedNonce [32]byte) (bool, error) {
}
return f.expectNonce == expectedNonce, nil
}
func (f *fakeAttestation) VerifyEvidence(ctx context.Context, opts ...attest.Option) ([]*attest.VerifiedReport, error) {
if f.failChain {
return nil, attest.ErrChainInvalid
}
return []*attest.VerifiedReport{{Kind: attest.KindSEVSNP, Vendor: "amd.sev.snp"}}, nil
}
func (f *fakeAttestation) RIMDigest() [32]byte { return f.rim }
func (f *fakeAttestation) HardwareFingerprint() [32]byte { return f.hw }
func (f *fakeAttestation) TEEPublicKey() [32]byte { return f.teePub }
@@ -59,7 +74,6 @@ func newTestGate(t *testing.T) (*LocalReleaseGate, *MemoryNonceStore, [32]byte,
t.Fatalf("NewLocalReleaseGate: %v", err)
}
var teePub [32]byte
// Use a real curve25519 pubkey so ECDH succeeds in seal.
priv := mustRand(t, 32)
priv[0] &= 248
priv[31] &= 127
@@ -72,9 +86,6 @@ func newTestGate(t *testing.T) (*LocalReleaseGate, *MemoryNonceStore, [32]byte,
return gate, store, rim, hw, teePub
}
// curve25519X25519Basepoint wraps the basepoint multiplication so the
// test file does not import curve25519 directly (avoid duplicate import
// surfaces; release.go already pulls it).
func curve25519X25519Basepoint(priv []byte) ([]byte, error) {
return x25519Basepoint(priv)
}
@@ -119,8 +130,6 @@ func TestRelease_WrongNonce(t *testing.T) {
t.Fatalf("Issue: %v", err)
}
// Worker presents a forged nonce (e.g. the worker chose its own
// random challenge instead of using the gate-issued one).
var forged [32]byte
copy(forged[:], mustRand(t, 32))
if forged == nonce {
@@ -141,8 +150,7 @@ func TestRelease_WrongNonce(t *testing.T) {
}
}
// Test 3: Issue → KMS restart (re-construct gate from store) → Release
// still works (no replay-window reset).
// Test 3: Issue → KMS restart → Release still works.
func TestRelease_SurvivesRestart(t *testing.T) {
policy := NewReleasePolicy(
[][32]byte{[32]byte(mustRand(t, 32))},
@@ -151,12 +159,10 @@ func TestRelease_SurvivesRestart(t *testing.T) {
for k := range policy.RequiredRIM {
_ = k
}
// Reuse the same store across two gate constructions.
var rootKey [32]byte
copy(rootKey[:], mustRand(t, 32))
store := NewMemoryNonceStore()
// First gate: Issue.
g1, err := NewLocalReleaseGate(policy, store, rootKey)
if err != nil {
t.Fatalf("NewLocalReleaseGate: %v", err)
@@ -168,14 +174,11 @@ func TestRelease_SurvivesRestart(t *testing.T) {
t.Fatalf("Issue: %v", err)
}
// "Restart": construct a second gate over the same store. The
// pending nonce + epoch must round-trip.
g2, err := NewLocalReleaseGate(policy, store, rootKey)
if err != nil {
t.Fatalf("NewLocalReleaseGate (restart): %v", err)
}
// Pull the policy's RIM/HW so the attestation matches.
var rim, hw [32]byte
for k := range policy.RequiredRIM {
rim = k
@@ -222,7 +225,6 @@ func TestRelease_AlreadyConsumed(t *testing.T) {
t.Fatalf("Release #1: %v", err)
}
// Replay: same jobID + nonce. Must refuse.
_, err = gate.Release(ReleaseRequest{
JobID: jobID, Epoch: epoch, Nonce: nonce, Attestation: att,
})
@@ -249,7 +251,6 @@ func TestRelease_Expired(t *testing.T) {
t.Fatalf("Issue: %v", err)
}
// Wait past the TTL.
time.Sleep(50 * time.Millisecond)
att := &fakeAttestation{expectNonce: nonce, rim: rim, hw: hw, teePub: teePub}
@@ -266,12 +267,9 @@ func TestRelease_Expired(t *testing.T) {
t.Fatalf("Release: expected ErrExpired wrapped, got %v", err)
}
// GC should reclaim the pending entry.
if err := gate.GC(); err != nil {
t.Fatalf("GC: %v", err)
}
// After GC the jobID is "unknown" rather than "expired"; either is
// a valid hard refusal.
_, err = gate.Release(ReleaseRequest{
JobID: jobID, Epoch: epoch, Nonce: nonce, Attestation: att,
})
@@ -280,9 +278,6 @@ func TestRelease_Expired(t *testing.T) {
}
}
// Bonus: nonce binding — nonce derived for jobID A must NOT match
// jobID B even with identical fresh entropy. The HMAC binding is what
// the audit calls out; this asserts it directly.
func TestNonce_BoundToJobID(t *testing.T) {
gate, _, _, _, _ := newTestGate(t)
@@ -303,8 +298,6 @@ func TestNonce_BoundToJobID(t *testing.T) {
}
}
// Bonus: AAD includes the issued nonce. Re-deriving AAD with a
// different IssuedNonce must produce different bytes.
func TestAAD_BindsIssuedNonce(t *testing.T) {
var jobID, n1, n2, teePub [32]byte
copy(jobID[:], mustRand(t, 32))
@@ -320,3 +313,74 @@ func TestAAD_BindsIssuedNonce(t *testing.T) {
t.Fatalf("AAD did not bind IssuedNonce (collision)")
}
}
// Test (Red Final N1): an attestation that passes the cheap nonce
// Verify() but FAILS the cc/attest chain verifier (AMD KDS / VCEK,
// NRAS, TDX) MUST be refused. Without the chain step a worker can
// craft a forged envelope whose Verify(nonce) returns true but whose
// evidence blobs do not chain to the pinned vendor root, harvesting a
// session key.
func TestRelease_RefusesOnChainFailure(t *testing.T) {
gate, _, rim, hw, teePub := newTestGate(t)
var jobID [32]byte
copy(jobID[:], mustRand(t, 32))
nonce, epoch, err := gate.Issue(jobID)
if err != nil {
t.Fatalf("Issue: %v", err)
}
// nonce binding passes (failVerify=false) but the chain check
// fails — the forged-evidence path the audit flagged.
att := &fakeAttestation{
expectNonce: nonce,
rim: rim,
hw: hw,
teePub: teePub,
failVerify: false,
failChain: true,
}
_, err = gate.Release(ReleaseRequest{
JobID: jobID,
Epoch: epoch,
Nonce: nonce,
Attestation: att,
})
if err == nil {
t.Fatalf("Release: expected chain-verify refusal, got nil")
}
if !errors.Is(err, ErrPolicyRefused) {
t.Fatalf("Release: expected ErrPolicyRefused wrapped, got %v", err)
}
if !errors.Is(err, ErrAttestationChain) {
t.Fatalf("Release: expected ErrAttestationChain wrapped, got %v", err)
}
}
// Test (Red Final N1, paired): the SAME attestation with chain check
// passing must succeed.
func TestRelease_AcceptsOnChainPass(t *testing.T) {
gate, _, rim, hw, teePub := newTestGate(t)
var jobID [32]byte
copy(jobID[:], mustRand(t, 32))
nonce, epoch, err := gate.Issue(jobID)
if err != nil {
t.Fatalf("Issue: %v", err)
}
att := &fakeAttestation{
expectNonce: nonce,
rim: rim,
hw: hw,
teePub: teePub,
failChain: false,
}
if _, err := gate.Release(ReleaseRequest{
JobID: jobID,
Epoch: epoch,
Nonce: nonce,
Attestation: att,
}); err != nil {
t.Fatalf("Release: expected success, got %v", err)
}
}