feat(connect): add device identity store and registration proof

A RustFS cluster device needs a durable identity before it can exchange a
one-time registration token for a certificate. This adds the device-side
half of that exchange, which rustfs/connect already verifies.

`connect::identity` builds the canonical registration transcript frozen by
protocol/agent/v1/registration-proof.md, signs it as low-S ES256, and emits
the PKCS#10 certificate request Connect consumes for its SubjectPublicKeyInfo.
`connect::identity_store` seals the P-256 key at mode 0600 and publishes it
through a no-clobber link, so a retry or a concurrent start returns the
original identity rather than minting a second one, and a corrupt or widened
key is refused rather than silently replaced.

The protocol fixture set is copied here byte-identically because
fixture-sets.json names this repository as the consumer copy; the tests
verify it against its own manifests and cross-verify Connect-produced ECDSA
proofs against transcripts rebuilt locally.

Nothing starts a task or touches the S3 data path: an unenrolled deployment
generates no key and holds no identity.
This commit is contained in:
overtrue
2026-08-19 12:54:50 +08:00
parent 09fe561443
commit 3b5164032a
48 changed files with 7356 additions and 0 deletions
Generated
+1
View File
@@ -9142,6 +9142,7 @@ dependencies = [
"mime_guess",
"opentelemetry",
"opentelemetry_sdk",
"p256 0.13.2",
"parking_lot",
"percent-encoding",
"pin-project-lite",
@@ -0,0 +1,5 @@
3d602080f7ca4c32ba9e37ad1a32665c78560726b30aeee08fd9e95eb2f36194 accept-vectors.json
d3c19946288717088145592e0e8d6f2fa684443ba2f73d4c7bc49c415d6dd051 certificate-profile.json
060485263c51003274c056a0e04bec1b7d76157cf599ba79eebe040bc7cee71b error-codes.json
43fe297ffb512b1b9f4af62f1832f3aa3905157893bfdc3dcc6d56f5a98aaef6 reject-vectors.json
b946175b094f4a8d75091b652fbe3d4327c9c795f28e02c96e1ab90a429e418d surface-separation.json
@@ -0,0 +1,84 @@
{
"protocolVersion": "v1",
"fixtureSet": "auth",
"fixture": "accept-vectors",
"description": "Presented certificates that authenticate. Time offsets are seconds relative to the moment the request is evaluated.",
"vectors": [
{
"name": "current credential on an active cluster",
"clusterState": "ACTIVE",
"credential": {
"validFromOffsetSeconds": -3600,
"validUntilOffsetSeconds": 82800,
"transition": null
},
"presented": {
"credential": "current",
"serial": "matching",
"certificateFingerprint": "matching"
},
"expected": {
"credentialResolved": true,
"authenticationEffective": true,
"reason": null
}
},
{
"name": "first device authenticates while its cluster is still pending",
"clusterState": "PENDING",
"credential": {
"validFromOffsetSeconds": -60,
"validUntilOffsetSeconds": 86340,
"transition": null
},
"presented": {
"credential": "current",
"serial": "matching",
"certificateFingerprint": "matching"
},
"expected": {
"credentialResolved": true,
"authenticationEffective": true,
"reason": null
}
},
{
"name": "outgoing credential inside the bounded rotation overlap",
"clusterState": "ACTIVE",
"credential": {
"validFromOffsetSeconds": -3600,
"validUntilOffsetSeconds": 82800,
"transition": "rotate"
},
"presented": {
"credential": "outgoing",
"serial": "matching",
"certificateFingerprint": "matching"
},
"expected": {
"credentialResolved": true,
"authenticationEffective": true,
"reason": null
}
},
{
"name": "incoming credential immediately after rotation",
"clusterState": "ACTIVE",
"credential": {
"validFromOffsetSeconds": -3600,
"validUntilOffsetSeconds": 82800,
"transition": "rotate"
},
"presented": {
"credential": "current",
"serial": "matching",
"certificateFingerprint": "matching"
},
"expected": {
"credentialResolved": true,
"authenticationEffective": true,
"reason": null
}
}
]
}
@@ -0,0 +1,82 @@
{
"protocolVersion": "v1",
"fixtureSet": "auth",
"fixture": "certificate-profile",
"description": "Frozen shape of the client certificate an online device presents and of the RFC 9440 header that conveys it.",
"certificate": {
"subject": {
"rdnCount": 1,
"commonName": "{clusterDeviceUid}",
"forbiddenAttributes": ["O", "OU", "C", "ST", "L", "emailAddress"]
},
"subjectAlternativeName": {
"entryCount": 1,
"type": "uniformResourceIdentifier",
"value": "urn:rustfs:connect:device:{clusterDeviceUid}",
"forbiddenTypes": ["dNSName", "iPAddress", "rfc822Name", "directoryName"],
"wildcardsAccepted": false
},
"clusterDeviceUid": {
"source": "cluster_devices.uid",
"format": "lowercase canonical UUIDv7",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
},
"keyAlgorithm": "EC",
"keyCurve": "P-256",
"signatureAlgorithm": "ES256",
"certificateSigningRequest": {
"format": "PKCS#10",
"signatureAlgorithm": "ES256",
"proofOfPossession": "self-signed with the device private key"
},
"lifetimeSeconds": 86400,
"maxRotationOverlapSeconds": 86400,
"recommendedRotationLeadSeconds": 28800,
"maxPresentableCredentialsPerDevice": 2,
"serial": {
"encoding": "lowercase-hex",
"length": 32,
"pattern": "^[0-9a-f]{32}$",
"entropyBits": 128
},
"certificateFingerprint": {
"algorithm": "SHA-256",
"over": "DER certificate",
"encoding": "lowercase-hex",
"pattern": "^[0-9a-f]{64}$"
},
"publicKeyFingerprint": {
"algorithm": "SHA-256",
"over": "DER SubjectPublicKeyInfo",
"encoding": "lowercase-hex",
"pattern": "^[0-9a-f]{64}$"
},
"keyId": {
"pattern": "^[a-z0-9][a-z0-9._-]{7,127}$"
},
"carriesOrganizationIdentifier": false,
"carriesClusterIdentifier": false,
"tenantBinding": {
"source": "device_credentials matched by certificate serial and certificate fingerprint",
"resolver": "ClusterDeviceIdentityPort::resolveOnlineCertificate"
}
},
"header": {
"name": "Client-Cert",
"specification": "RFC 9440",
"encoding": "sf-binary",
"valueTemplate": ":{base64(DER certificate)}:",
"example": ":MIIBkDCCATagAwIBAgIQZXhhbXBsZQ==:",
"chainHeader": {
"name": "Client-Cert-Chain",
"accepted": false,
"reason": "Chain validation belongs to the trusted ingress, which verifies against the Connect device CA before forwarding."
},
"setByTrustedIngressOnly": true,
"inboundHeaderStripped": true,
"appendAccepted": false,
"acceptedOnSurfaces": ["/agent"],
"ignoredOnSurfaces": ["/api"],
"backendPubliclyReachable": false
}
}
@@ -0,0 +1,86 @@
{
"protocolVersion": "v1",
"fixtureSet": "auth",
"fixture": "error-codes",
"description": "Frozen ErrorInfo reasons for agent authentication and negotiation. Clients branch on status and reason, never on message.",
"domain": "rustfs.connect",
"detailType": "type.googleapis.com/google.rpc.ErrorInfo",
"disclosureRules": [
"A rejection never reveals whether an unknown certificate belongs to another tenant.",
"A rejection never contains certificate bytes, key material, or a fingerprint."
],
"reasons": [
{
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"meaning": "The requested protocol major version is missing, malformed, or not supported."
},
{
"reason": "CLIENT_CERTIFICATE_MISSING",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "The request reached an authenticated agent operation without a Client-Cert header from trusted ingress."
},
{
"reason": "CLIENT_CERTIFICATE_MALFORMED",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "The Client-Cert header is not a valid RFC 9440 byte sequence, or the certificate violates the frozen profile."
},
{
"reason": "CLIENT_CERTIFICATE_UNKNOWN",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "No device credential matches the presented certificate serial and fingerprint together."
},
{
"reason": "CREDENTIAL_NOT_YET_VALID",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "The matched credential's validity window has not opened yet."
},
{
"reason": "CREDENTIAL_EXPIRED",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "The matched credential's validity window has closed, ending any rotation overlap."
},
{
"reason": "CREDENTIAL_REVOKED",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "The matched credential is REVOKED. Revocation takes effect immediately."
},
{
"reason": "CREDENTIAL_COMPROMISED",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "The matched credential is COMPROMISED, for example after a clone was observed."
},
{
"reason": "CLUSTER_DISABLED",
"httpStatus": 403,
"status": "PERMISSION_DENIED",
"meaning": "The credential is intact but its cluster is DISABLED, so no agent activity is accepted."
},
{
"reason": "CLUSTER_DELETED",
"httpStatus": 403,
"status": "PERMISSION_DENIED",
"meaning": "The credential is intact but its cluster is DELETED."
},
{
"reason": "TENANT_MISMATCH",
"httpStatus": 403,
"status": "PERMISSION_DENIED",
"meaning": "The authenticated device belongs to a different organization than the resource named by the request."
},
{
"reason": "SESSION_CREDENTIAL_NOT_ACCEPTED",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "A browser session cookie was presented to an authenticated agent operation. The agent surface never accepts it."
}
]
}
@@ -0,0 +1,195 @@
{
"protocolVersion": "v1",
"fixtureSet": "auth",
"fixture": "reject-vectors",
"description": "Presented certificates that must not authenticate. A credential that is known but unusable still resolves, so the rejection can be audited against a device instead of being reported as an unknown certificate.",
"vectors": [
{
"name": "revoked credential",
"clusterState": "ACTIVE",
"credential": {
"validFromOffsetSeconds": -3600,
"validUntilOffsetSeconds": 82800,
"transition": "revoke"
},
"presented": {
"credential": "current",
"serial": "matching",
"certificateFingerprint": "matching"
},
"expected": {
"credentialResolved": true,
"authenticationEffective": false,
"reason": "CREDENTIAL_REVOKED"
}
},
{
"name": "compromised credential",
"clusterState": "ACTIVE",
"credential": {
"validFromOffsetSeconds": -3600,
"validUntilOffsetSeconds": 82800,
"transition": "markCompromised"
},
"presented": {
"credential": "current",
"serial": "matching",
"certificateFingerprint": "matching"
},
"expected": {
"credentialResolved": true,
"authenticationEffective": false,
"reason": "CREDENTIAL_COMPROMISED"
}
},
{
"name": "credential whose validity window has closed",
"clusterState": "ACTIVE",
"credential": {
"validFromOffsetSeconds": -86460,
"validUntilOffsetSeconds": -60,
"transition": null
},
"presented": {
"credential": "current",
"serial": "matching",
"certificateFingerprint": "matching"
},
"expected": {
"credentialResolved": true,
"authenticationEffective": false,
"reason": "CREDENTIAL_EXPIRED"
}
},
{
"name": "credential whose validity window has not opened",
"clusterState": "ACTIVE",
"credential": {
"validFromOffsetSeconds": 3600,
"validUntilOffsetSeconds": 90000,
"transition": null
},
"presented": {
"credential": "current",
"serial": "matching",
"certificateFingerprint": "matching"
},
"expected": {
"credentialResolved": true,
"authenticationEffective": false,
"reason": "CREDENTIAL_NOT_YET_VALID"
}
},
{
"name": "intact credential on a disabled cluster",
"clusterState": "DISABLED",
"credential": {
"validFromOffsetSeconds": -3600,
"validUntilOffsetSeconds": 82800,
"transition": null
},
"presented": {
"credential": "current",
"serial": "matching",
"certificateFingerprint": "matching"
},
"expected": {
"credentialResolved": true,
"authenticationEffective": false,
"reason": "CLUSTER_DISABLED"
}
},
{
"name": "intact credential on a deleted cluster",
"clusterState": "DELETED",
"credential": {
"validFromOffsetSeconds": -3600,
"validUntilOffsetSeconds": 82800,
"transition": null
},
"presented": {
"credential": "current",
"serial": "matching",
"certificateFingerprint": "matching"
},
"expected": {
"credentialResolved": true,
"authenticationEffective": false,
"reason": "CLUSTER_DELETED"
}
},
{
"name": "certificate Connect never issued",
"clusterState": "ACTIVE",
"credential": {
"validFromOffsetSeconds": -3600,
"validUntilOffsetSeconds": 82800,
"transition": null
},
"presented": {
"credential": "current",
"serial": "unrelated",
"certificateFingerprint": "unrelated"
},
"expected": {
"credentialResolved": false,
"authenticationEffective": false,
"reason": "CLIENT_CERTIFICATE_UNKNOWN"
}
},
{
"name": "issued serial presented with a substituted certificate",
"clusterState": "ACTIVE",
"credential": {
"validFromOffsetSeconds": -3600,
"validUntilOffsetSeconds": 82800,
"transition": null
},
"presented": {
"credential": "current",
"serial": "matching",
"certificateFingerprint": "unrelated"
},
"expected": {
"credentialResolved": false,
"authenticationEffective": false,
"reason": "CLIENT_CERTIFICATE_UNKNOWN"
}
},
{
"name": "issued certificate presented under a substituted serial",
"clusterState": "ACTIVE",
"credential": {
"validFromOffsetSeconds": -3600,
"validUntilOffsetSeconds": 82800,
"transition": null
},
"presented": {
"credential": "current",
"serial": "unrelated",
"certificateFingerprint": "matching"
},
"expected": {
"credentialResolved": false,
"authenticationEffective": false,
"reason": "CLIENT_CERTIFICATE_UNKNOWN"
}
}
],
"tenantVector": {
"name": "authenticated device reaching a resource owned by another organization",
"description": "The certificate is valid and its credential is effective. Only the stored organization decides what the device may reach, and the certificate carries no organization identifier to contradict it.",
"clusterState": "ACTIVE",
"credential": {
"validFromOffsetSeconds": -3600,
"validUntilOffsetSeconds": 82800,
"transition": null
},
"expected": {
"credentialResolved": true,
"authenticationEffective": true,
"resolvedIdentityBelongsToForeignOrganization": false,
"reason": "TENANT_MISMATCH"
}
}
}
@@ -0,0 +1,78 @@
{
"protocolVersion": "v1",
"fixtureSet": "auth",
"fixture": "surface-separation",
"description": "The control surface and the agent surface have disjoint credentials. Neither accepts the other's, and neither OpenAPI document declares the other's security scheme.",
"httpVectors": [
{
"name": "agent client certificate presented to the control surface",
"surface": "/api",
"request": {
"method": "GET",
"path": "/api/session",
"headers": {
"Client-Cert": ":MIIBkDCCATagAwIBAgIQZXhhbXBsZQ==:"
},
"browserSession": false
},
"expected": {
"authenticated": false,
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"reason": "UNAUTHENTICATED"
}
},
{
"name": "browser session presented to the agent surface",
"surface": "/agent",
"request": {
"method": "GET",
"path": "/agent/protocolStatus",
"headers": {},
"browserSession": true
},
"expected": {
"deviceIdentityEstablished": false,
"httpStatus": 200,
"body": {
"protocolVersion": "v1"
},
"identicalToUnauthenticatedRequest": true,
"note": "getProtocolStatus is the pre-registration operation and is public on purpose. An authenticated browser session neither changes its answer nor grants anything on the agent surface."
}
}
],
"documentVectors": [
{
"document": "openapi/agent.json",
"securityScheme": "agentMutualTls",
"schemeType": "mutualTLS",
"forbiddenSecuritySchemes": ["sessionCookie"],
"defaultSecurity": ["agentMutualTls"],
"publicOperations": ["getProtocolStatus"]
},
{
"document": "openapi/control.json",
"securityScheme": "sessionCookie",
"schemeType": "apiKey",
"forbiddenSecuritySchemes": ["agentMutualTls"],
"defaultSecurity": [],
"publicOperations": null
}
],
"routeGuards": {
"surfacePrefix": "agent/",
"description": "No agent route may be protected by a session authentication guard. A device is identified by its certificate or not at all.",
"forbiddenMiddlewarePrefixes": ["auth:", "auth.session"],
"forbiddenMiddleware": ["auth"],
"forbiddenMiddlewareClasses": [
"Illuminate\\Auth\\Middleware\\Authenticate",
"Illuminate\\Auth\\Middleware\\AuthenticateSession"
],
"knownGap": {
"middleware": "Laravel\\Sanctum\\Http\\Middleware\\EnsureFrontendRequestsAreStateful",
"description": "The agent routes still share the api middleware group with the control surface, so Sanctum's stateful frontend middleware runs on them. It establishes no device identity and no agent route uses an authentication guard, but the agent surface should get its own middleware group when the first authenticated agent operation lands.",
"owner": "the issue that adds the first authenticated agent operation"
}
}
}
@@ -0,0 +1,4 @@
4dacc8f8b7fd7f3820dbef4ea4611207dce4fb0bada287c09451499f951f64cd accept-vectors.json
afe10983a0de23cf2e1400399bb8a757de90cff2c6120d90cd83b0d28bc2cad2 error-codes.json
22133a5cbd5cd36588987d3540c9cadde063faa0ea529dc913f19ed2dc253dea manifest-signing.json
04aedbaacdac72fa2a0df22edf6a8c402645a972713778c6031d6c643976a14c reject-vectors.json
@@ -0,0 +1,96 @@
{
"protocolVersion": "v1",
"fixtureSet": "bundle",
"fixture": "accept-vectors",
"description": "Support bundle manifests that verify. The second vector is the load-bearing one: its bytes are deliberately non-canonical, and re-serialising them before verification breaks the signature. bytes is standard padded base64 of the exact raw manifest octets.",
"authorisedBundle": {
"bundleUid": "0198f3a1-8000-7e50-8f61-4a5b6c7d8e94",
"organizationName": "organizations/0198f3a1-4c00-7a10-8b21-0c1d2e3f4a50",
"clusterName": "organizations/0198f3a1-4c00-7a10-8b21-0c1d2e3f4a50/clusters/0198f3a1-5d00-7b20-9c31-1d2e3f4a5b61",
"deviceName": "organizations/0198f3a1-4c00-7a10-8b21-0c1d2e3f4a50/clusters/0198f3a1-5d00-7b20-9c31-1d2e3f4a5b61/clusterDevices/0198f3a1-6e00-7c30-ad41-2e3f4a5b6c72",
"effectiveDeviceKeyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"note": "What Connect authorised, read from the support_bundles row and the enrolled device key. Every name in a manifest is compared against this and never trusted on its own."
},
"vectors": [
{
"name": "manifest signed by the effective device key",
"signerKey": "device",
"evaluationTime": "2026-08-21T00:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Quc3VwcG9ydC5idW5kbGVNYW5pZmVzdC8xIiwKICAgICJwcm90b2NvbFZlcnNpb24iOiAidjEiLAogICAgImJ1bmRsZVVpZCI6ICIwMTk4ZjNhMS04MDAwLTdlNTAtOGY2MS00YTViNmM3ZDhlOTQiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiZGV2aWNlTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEvY2x1c3RlckRldmljZXMvMDE5OGYzYTEtNmUwMC03YzMwLWFkNDEtMmUzZjRhNWI2YzcyIiwKICAgICJkZXZpY2VLZXlJZCI6ICIzOWNhMjRjOGIwMmE1NTlmZDliZWIyYjFmNWQxOGNlZDIwYzRiYjI0NjU3N2I5MjkxNGFlNjgxNGMzZjcwYWNmIiwKICAgICJub25jZSI6ICI0dE1NQndDY1hqRVUxTEM1cDlNeGJaLTM3VGVZRFFSWWpSanNnbTR5dWUwIiwKICAgICJwcm9kdWNlZEF0IjogIjIwMjYtMDgtMjBUMDk6MzA6MDBaIiwKICAgICJyZWRhY3Rpb25WZXJzaW9uIjogInJ1c3Rmcy5jb25uZWN0LnJlZGFjdGlvbi52MSIsCiAgICAicnVsZXNldEhhc2giOiAiYjM3NDM2ZDhlNzI1MTUzOTRhMTIyZDYzMzg2NWIxZGMwMjhkNGVjZTM0OTM1MmEwYTNhMjNmNTJjYTQyODVmMyIsCiAgICAiY2xhc3NpZmljYXRpb25SZWdpc3RyeVZlcnNpb24iOiAxLAogICAgImVudHJpZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicGF0aCI6ICJkaWFnbm9zdGljcy9vZmZsaW5lLXN1bW1hcnkuanNvbiIsCiAgICAgICAgICAgICJ0eXBlIjogIm9mZmxpbmUtZGlhZ25vc3RpYyIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiAyMDQ4LAogICAgICAgICAgICAic2hhMjU2IjogIjFkOTY1N2E2N2RmYzEyYTBjNmY5NzQ1ODZjMDQxZDZkY2U2ZjQ4MzNmYjkwZGZhNjBhYWYwYTQ1NWRkYzE2NzkiLAogICAgICAgICAgICAiY2xhc3NpZmljYXRpb24iOiAiTDAiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJwYXRoIjogImRpYWdub3N0aWNzL2hvc3Qtc3VtbWFyeS5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAib2ZmbGluZS1kaWFnbm9zdGljIiwKICAgICAgICAgICAgInNpemVCeXRlcyI6IDQwOTYsCiAgICAgICAgICAgICJzaGEyNTYiOiAiNDViYjZhMjgxYzk4MzBkOGUxNmQ4NTg3MDA2ODQwMTA0OGY2NDQ0YmFkY2UyMGFhOThlYTNmMGNjMGVkYjIwNCIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMSIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgInBhdGgiOiAicmVkYWN0aW9uL3JlcG9ydC5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAicmVkYWN0aW9uLXJlcG9ydCIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiA1MTIsCiAgICAgICAgICAgICJzaGEyNTYiOiAiZGFmMWQ2MTUyZmQzNzc1MmNiOTU1ZjQ2ZjM2ZTQ2YTUxZThjMTQ2ZTFiMzcxODE5NDY5NjljMWU1NTE3MzczNiIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMCIKICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "st6Svkk8UgwDMq_8rEomsdIdANyPjqTYFTWPN4FISYor3su27YNRnoWWIyemileZusezfHd8BlxuiSvzJrCcnw"
}
},
"expected": {
"signatureVerifies": true,
"organizationMatches": true,
"clusterMatches": true,
"deviceMatches": true,
"withinWindow": true,
"accepted": true,
"reason": null
}
},
{
"name": "manifest whose bytes no re-serialiser would reproduce",
"signerKey": "device",
"evaluationTime": "2026-08-21T00:00:00Z",
"document": {
"bytes": "ewogICAicHJvdG9jb2xWZXJzaW9uIjogInYxIiwgIAoJImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Quc3VwcG9ydC5idW5kbGVNYW5pZmVzdFwvMSIsCiAgICJlbnRyaWVzIjogWwogICAgICB7ICJzaGEyNTYiOiAiMWQ5NjU3YTY3ZGZjMTJhMGM2Zjk3NDU4NmMwNDFkNmRjZTZmNDgzM2ZiOTBkZmE2MGFhZjBhNDU1ZGRjMTY3OSIsICJjbGFzc2lmaWNhdGlvbiI6ICJMMCIsICJwYXRoIjogImRpYWdub3N0aWNzXC9vZmZsaW5lLXN1bW1hcnkuanNvbiIsICJ0eXBlIjogIm9mZmxpbmUtZGlhZ25vc3RpYyIsICJzaXplQnl0ZXMiOiAyMDQ4IH0sCiAgICAgIHsicGF0aCI6ICJyZWRhY3Rpb25cL3JlcG9ydC5qc29uIiwJInR5cGUiOiAicmVkYWN0aW9uLXJlcG9ydCIsICJzaXplQnl0ZXMiOiA1MTIsICJzaGEyNTYiOiAiZGFmMWQ2MTUyZmQzNzc1MmNiOTU1ZjQ2ZjM2ZTQ2YTUxZThjMTQ2ZTFiMzcxODE5NDY5NjljMWU1NTE3MzczNiIsICJjbGFzc2lmaWNhdGlvbiI6ICJMMCJ9CiAgIF0sCiAgIm5vbmNlIjogIjhEUFJ4TGVVclVzTWdia09MWkJsTE5LMXRmQVFhTHNuVHVWRmxXX0d3ZkEiLCAgIAogICAgICAicHJvZHVjZWRBdCI6ICIyMDI2LTA4LTIwVDA5OjMxOjAwWiIsCiAgICJkZXZpY2VLZXlJZCI6ICIzOWNhMjRjOGIwMmE1NTlmZDliZWIyYjFmNWQxOGNlZDIwYzRiYjI0NjU3N2I5MjkxNGFlNjgxNGMzZjcwYWNmIiwKICAgImJ1bmRsZVVpZCI6ICIwMTk4ZjNhMS04MDAwLTdlNTAtOGY2MS00YTViNmM3ZDhlOTQiLAogICAib3JnYW5pemF0aW9uTmFtZSI6ICJvcmdhbml6YXRpb25zXC8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAiY2x1c3Rlck5hbWUiOiAib3JnYW5pemF0aW9uc1wvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwXC9jbHVzdGVyc1wvMDE5OGYzYTEtNWQwMC03YjIwLTljMzEtMWQyZTNmNGE1YjYxIiwKICAgImRldmljZU5hbWUiOiAib3JnYW5pemF0aW9uc1wvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwXC9jbHVzdGVyc1wvMDE5OGYzYTEtNWQwMC03YjIwLTljMzEtMWQyZTNmNGE1YjYxXC9jbHVzdGVyRGV2aWNlc1wvMDE5OGYzYTEtNmUwMC03YzMwLWFkNDEtMmUzZjRhNWI2YzcyIiwKICAgInJlZGFjdGlvblZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3QucmVkYWN0aW9uLnYxIiwKCSJydWxlc2V0SGFzaCI6ICJiMzc0MzZkOGU3MjUxNTM5NGExMjJkNjMzODY1YjFkYzAyOGQ0ZWNlMzQ5MzUyYTBhM2EyM2Y1MmNhNDI4NWYzIiwgIAogICAiY2xhc3NpZmljYXRpb25SZWdpc3RyeVZlcnNpb24iOiAxCn0=",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "byAAzj9jJJSXCv50pU2M5ugSES4kgN_DQzY_CFFDG64rXqn8EBApEGH_a2_TodAyArhpoM2HicOmHi_csL11cQ"
}
},
"nonCanonical": {
"keyOrderDiffersFromSchema": true,
"mixedIndentation": true,
"containsTabs": true,
"containsTrailingWhitespace": true,
"escapesSolidus": true,
"entriesOnOneLine": true,
"note": "Every one of these survives a signature over the raw octets and none survives a re-serialisation. reject-vectors.json carries the re-serialised copy of exactly this document under the same signature; it must fail."
},
"expected": {
"signatureVerifies": true,
"organizationMatches": true,
"clusterMatches": true,
"deviceMatches": true,
"withinWindow": true,
"accepted": true,
"reason": null
}
},
{
"name": "manifest carrying an unknown optional field is accepted and the field is discarded",
"signerKey": "device",
"evaluationTime": "2026-08-21T00:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Quc3VwcG9ydC5idW5kbGVNYW5pZmVzdC8xIiwKICAgICJwcm90b2NvbFZlcnNpb24iOiAidjEiLAogICAgImJ1bmRsZVVpZCI6ICIwMTk4ZjNhMS04MDAwLTdlNTAtOGY2MS00YTViNmM3ZDhlOTQiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiZGV2aWNlTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEvY2x1c3RlckRldmljZXMvMDE5OGYzYTEtNmUwMC03YzMwLWFkNDEtMmUzZjRhNWI2YzcyIiwKICAgICJkZXZpY2VLZXlJZCI6ICIzOWNhMjRjOGIwMmE1NTlmZDliZWIyYjFmNWQxOGNlZDIwYzRiYjI0NjU3N2I5MjkxNGFlNjgxNGMzZjcwYWNmIiwKICAgICJub25jZSI6ICJ6eTg4bmI2QXNQbXZYa2YxR3hwTU1VYnhqcEdGQ2llWkFGQ19XMDFJZUVjIiwKICAgICJwcm9kdWNlZEF0IjogIjIwMjYtMDgtMjBUMDk6MzA6MDBaIiwKICAgICJyZWRhY3Rpb25WZXJzaW9uIjogInJ1c3Rmcy5jb25uZWN0LnJlZGFjdGlvbi52MSIsCiAgICAicnVsZXNldEhhc2giOiAiYjM3NDM2ZDhlNzI1MTUzOTRhMTIyZDYzMzg2NWIxZGMwMjhkNGVjZTM0OTM1MmEwYTNhMjNmNTJjYTQyODVmMyIsCiAgICAiY2xhc3NpZmljYXRpb25SZWdpc3RyeVZlcnNpb24iOiAxLAogICAgImVudHJpZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicGF0aCI6ICJkaWFnbm9zdGljcy9vZmZsaW5lLXN1bW1hcnkuanNvbiIsCiAgICAgICAgICAgICJ0eXBlIjogIm9mZmxpbmUtZGlhZ25vc3RpYyIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiAyMDQ4LAogICAgICAgICAgICAic2hhMjU2IjogIjFkOTY1N2E2N2RmYzEyYTBjNmY5NzQ1ODZjMDQxZDZkY2U2ZjQ4MzNmYjkwZGZhNjBhYWYwYTQ1NWRkYzE2NzkiLAogICAgICAgICAgICAiY2xhc3NpZmljYXRpb24iOiAiTDAiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJwYXRoIjogImRpYWdub3N0aWNzL2hvc3Qtc3VtbWFyeS5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAib2ZmbGluZS1kaWFnbm9zdGljIiwKICAgICAgICAgICAgInNpemVCeXRlcyI6IDQwOTYsCiAgICAgICAgICAgICJzaGEyNTYiOiAiNDViYjZhMjgxYzk4MzBkOGUxNmQ4NTg3MDA2ODQwMTA0OGY2NDQ0YmFkY2UyMGFhOThlYTNmMGNjMGVkYjIwNCIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMSIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgInBhdGgiOiAicmVkYWN0aW9uL3JlcG9ydC5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAicmVkYWN0aW9uLXJlcG9ydCIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiA1MTIsCiAgICAgICAgICAgICJzaGEyNTYiOiAiZGFmMWQ2MTUyZmQzNzc1MmNiOTU1ZjQ2ZjM2ZTQ2YTUxZThjMTQ2ZTFiMzcxODE5NDY5NjljMWU1NTE3MzczNiIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMCIKICAgICAgICB9CiAgICBdLAogICAgImNvbGxlY3RvckhpbnQiOiAiaWdub3JlZCIKfQo=",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "YeAkr64tXPUwLUjNNKU2AGsYKzt47uh0l6H_3OwaL5cQ28fGmGoYOMIN8jXLkiEJlbTiAQ0jG7Uda6LK0CD7qw"
}
},
"expected": {
"signatureVerifies": true,
"organizationMatches": true,
"clusterMatches": true,
"deviceMatches": true,
"withinWindow": true,
"accepted": true,
"reason": null,
"discardedFields": [
"collectorHint"
],
"echoedBack": [],
"stored": []
}
}
]
}
@@ -0,0 +1,148 @@
{
"protocolVersion": "v1",
"fixtureSet": "bundle",
"fixture": "error-codes",
"description": "Frozen ErrorInfo reasons for support bundle manifest validation, and the closed persisted code each one collapses into.",
"domain": "rustfs.connect",
"detailType": "type.googleapis.com/google.rpc.ErrorInfo",
"disclosureRules": [
"A rejection never reveals whether a bundle, key, or device belongs to another tenant.",
"A rejection never contains manifest bytes, entry paths, key material, or digests.",
"Only supportBundleRejectionCode is persisted. The protocol reason stays in the verifier."
],
"reasons": [
{
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"supportBundleRejectionCode": "MANIFEST_INVALID",
"meaning": "The protocolVersion is missing, malformed, or names an unsupported major version."
},
{
"reason": "UNSUPPORTED_FORMAT",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"supportBundleRejectionCode": "MANIFEST_INVALID",
"meaning": "The formatVersion is not rustfs.connect.support.bundleManifest/1."
},
{
"reason": "SIGNATURE_MALFORMED",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"supportBundleRejectionCode": "MANIFEST_INVALID",
"meaning": "The signature is not 64 octets of fixed-width r||s in unpadded base64url, or r or s is out of range."
},
{
"reason": "SIGNATURE_NOT_CANONICAL",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"supportBundleRejectionCode": "MANIFEST_INVALID",
"meaning": "The signature verifies but its s exceeds half the group order."
},
{
"reason": "SIGNATURE_INVALID",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"supportBundleRejectionCode": "MANIFEST_INVALID",
"meaning": "ECDSA verification over the received manifest octets failed. Tampering and re-serialisation both land here."
},
{
"reason": "DEVICE_KEY_UNKNOWN",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"supportBundleRejectionCode": "MANIFEST_INVALID",
"meaning": "No enrolled device key matches deviceKeyId for this bundle."
},
{
"reason": "DEVICE_KEY_REVOKED",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"supportBundleRejectionCode": "MANIFEST_INVALID",
"meaning": "The device key is known but revoked. Revocation is not a validity window and is not retroactively forgiven."
},
{
"reason": "ORGANIZATION_MISMATCH",
"httpStatus": 403,
"status": "PERMISSION_DENIED",
"supportBundleRejectionCode": "MANIFEST_INVALID",
"meaning": "The manifest names a different organization than the bundle Connect authorised."
},
{
"reason": "CLUSTER_MISMATCH",
"httpStatus": 403,
"status": "PERMISSION_DENIED",
"supportBundleRejectionCode": "MANIFEST_INVALID",
"meaning": "The manifest names a different cluster than the bundle Connect authorised."
},
{
"reason": "DEVICE_MISMATCH",
"httpStatus": 403,
"status": "PERMISSION_DENIED",
"supportBundleRejectionCode": "MANIFEST_INVALID",
"meaning": "The manifest names a different device than the bundle Connect authorised."
},
{
"reason": "MANIFEST_NOT_YET_VALID",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"supportBundleRejectionCode": "MANIFEST_INVALID",
"meaning": "producedAt is further in the future than the skew tolerance."
},
{
"reason": "MANIFEST_EXPIRED",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"supportBundleRejectionCode": "MANIFEST_INVALID",
"meaning": "producedAt is older than the freshness window."
},
{
"reason": "CLASSIFICATION_NOT_PERMITTED",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"supportBundleRejectionCode": "MANIFEST_INVALID",
"meaning": "An entry declares a classification level that is not collected in this release."
},
{
"reason": "ENTRY_TYPE_UNKNOWN",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"supportBundleRejectionCode": "MANIFEST_INVALID",
"meaning": "An entry declares a type outside the closed set."
},
{
"reason": "REDACTION_VERSION_UNKNOWN",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"supportBundleRejectionCode": "MANIFEST_INVALID",
"meaning": "redactionVersion is not an identifier Connect implements. The identifier is opaque, so a newer looking one is never treated as a superset of an older one."
},
{
"reason": "REDACTION_RULESET_MISMATCH",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"supportBundleRejectionCode": "MANIFEST_INVALID",
"meaning": "redactionVersion is known but rulesetHash is not the hash of the rules that identifier names, so the identifier would stand for two different treatments."
},
{
"reason": "ENTRY_DIGEST_MISMATCH",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"supportBundleRejectionCode": "HASH_MISMATCH",
"meaning": "An archive member does not hash to the digest its manifest entry declares."
},
{
"reason": "ENTRY_SIZE_MISMATCH",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"supportBundleRejectionCode": "SIZE_MISMATCH",
"meaning": "An archive member is not the size its manifest entry declares."
},
{
"reason": "BUNDLE_REPLAYED",
"httpStatus": 409,
"status": "ABORTED",
"supportBundleRejectionCode": "MANIFEST_INVALID",
"meaning": "The manifest nonce was already accepted for this organization, cluster, and device."
}
]
}
@@ -0,0 +1,266 @@
{
"protocolVersion": "v1",
"fixtureSet": "bundle",
"fixture": "manifest-signing",
"description": "How a support bundle manifest is signed and what it must contain. The signature input is the single detail R07 and every verifier must get exactly right, so it is stated first and proved by the non-canonical vector in accept-vectors.json.",
"signatureInput": {
"definition": "rustfs-support-bundle-v1 || 0x00 || the exact raw octets of manifest.json as stored in the archive",
"domainSeparationTag": "rustfs-support-bundle-v1",
"separatorByte": "0x00",
"signedOver": "the received manifest octets, byte for byte",
"reserialisationPermitted": false,
"canonicalJsonPermitted": false,
"prohibited": [
"Serialising a parsed manifest back to JSON and signing or verifying that.",
"Sorting keys, changing indentation, changing solidus escaping, or trimming whitespace before hashing.",
"Hashing a manifest read through a JSON library that does not preserve the original octets.",
"Verifying a manifest against a copy re-encoded by an HTTP client, a database column, or a template."
],
"note": "A verifier must hold the received octets, prepend the tag and the separator, and verify. Only after that may it parse. The producer may format the manifest however it likes: correctness comes from the bytes travelling unchanged, not from agreeing on a canonical form."
},
"signatureEncoding": {
"signatureAlgorithm": "ES256",
"curve": "P-256",
"hash": "SHA-256",
"signatureEncoding": "fixed-width-r-s",
"signatureLengthBytes": 64,
"signatureTransferEncoding": "base64url-unpadded",
"signatureValuePattern": "^[A-Za-z0-9_-]{86}$",
"lowSRequired": true,
"groupOrder": "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551",
"maxS": "7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a8",
"publicKeyEncoding": "sec1-uncompressed",
"publicKeyLengthBytes": 65,
"publicKeyTransferEncoding": "base64url-unpadded",
"subjectPublicKeyInfoDerPrefix": "3059301306072a8648ce3d020106082a8648ce3d030107034200",
"keyIdAlgorithm": "SHA-256",
"keyIdOver": "DER SubjectPublicKeyInfo",
"keyIdEncoding": "lowercase-hex",
"keyIdPattern": "^[0-9a-f]{64}$",
"documentTransferEncoding": "base64-padded"
},
"archiveLayout": {
"manifestPath": "manifest.json",
"signaturePath": "manifest.sig",
"signatureDocument": {
"fields": [
"algorithm",
"keyId",
"value",
"signedFile",
"domainSeparationTag"
],
"signed": false,
"note": "The detached signature document carries no security claim of its own. keyId is a lookup hint; a verifier accepts the key only if it is the effective device key for the bundle, never because the document named it."
},
"entryPathPattern": "^[a-z0-9][a-z0-9._-]*(/[a-z0-9][a-z0-9._-]*)*$",
"entryPathMaxLength": 256,
"entryPathRules": [
"Relative to the archive root, never absolute.",
"No . or .. segment, no empty segment, no backslash, no drive letter, no symlink target.",
"Unique across the manifest; a duplicate path is a rejection.",
"manifest.json and manifest.sig are not manifest entries and must not appear in entries."
],
"entriesCoverArchive": "Every archive member other than manifest.json and manifest.sig must appear exactly once in entries, and every entry must exist in the archive."
},
"declaredVersusActual": {
"manifestCovers": "the per-entry path, type, size, digest, and classification of the archive members",
"manifestDoesNotCover": "the digest or size of the archive itself, which cannot be inside the archive it describes",
"archiveDigestDeclaredBy": "support_bundles.declared_sha256 and support_bundles.declared_size_bytes, committed before the upload starts",
"archiveDigestMeasuredInto": "support_bundles.actual_sha256 and support_bundles.actual_size_bytes",
"order": [
"Measure the quarantined object and record actual_sha256 and actual_size_bytes.",
"Reject unless the measured archive equals what the device declared.",
"Read manifest.json and manifest.sig without extracting anything else.",
"Verify the manifest signature over the raw manifest octets.",
"Parse the manifest only after the signature verified.",
"Check tenancy, freshness, replay, redaction version, classifications, and entry types.",
"Verify every entry digest and size against the archive members.",
"Only then promote the bundle to READY."
],
"note": "READY is already unreachable in PostgreSQL unless actual_sha256 = declared_sha256 and actual_size_bytes = declared_size_bytes, so the archive-level check is enforced by the database and this contract adds the manifest-level checks above it."
},
"fields": [
{
"name": "formatVersion",
"requiredness": "required",
"type": "string",
"default": null,
"note": "Exactly rustfs.connect.support.bundleManifest/1."
},
{
"name": "protocolVersion",
"requiredness": "required",
"type": "string",
"default": null,
"note": "Exactly v1 in this release; the rule is the one frozen in protocol/agent/v1/authentication.md."
},
{
"name": "bundleUid",
"requiredness": "required",
"type": "string",
"default": null,
"note": "Lowercase canonical UUIDv7, the uid of the support_bundles row."
},
{
"name": "organizationName",
"requiredness": "required",
"type": "string",
"default": null,
"note": "organizations/{organizationUid}. An untrusted locator; Connect compares it against the bundle it authorised and never derives a tenant from it."
},
{
"name": "clusterName",
"requiredness": "required",
"type": "string",
"default": null,
"note": "organizations/{organizationUid}/clusters/{clusterUid}."
},
{
"name": "deviceName",
"requiredness": "required",
"type": "string",
"default": null,
"note": "organizations/{organizationUid}/clusters/{clusterUid}/clusterDevices/{clusterDeviceUid}."
},
{
"name": "deviceKeyId",
"requiredness": "required",
"type": "string",
"default": null,
"note": "Lowercase SHA-256 hex of the DER SubjectPublicKeyInfo of the signing device key."
},
{
"name": "nonce",
"requiredness": "required",
"type": "string",
"default": null,
"note": "32 random octets as unpadded base64url. Unique per organization, cluster, and device for at least the manifest max age."
},
{
"name": "producedAt",
"requiredness": "required",
"type": "string",
"default": null,
"note": "RFC 3339 UTC with a Z offset and second precision. Advisory device clock, bounded by the frozen freshness window."
},
{
"name": "redactionVersion",
"requiredness": "required",
"type": "string",
"default": null,
"note": "The opaque identifier of the deterministic redaction ruleset applied before packaging, frozen by protocol/agent/v1/fixtures/redaction/ruleset.json. Compared for equality only, never ordered."
},
{
"name": "rulesetHash",
"requiredness": "required",
"type": "string",
"default": null,
"note": "Lowercase SHA-256 hex of the canonical form of the ruleset named by redactionVersion. Together the pair proves which rules produced the redacted documents in this archive."
},
{
"name": "classificationRegistryVersion",
"requiredness": "required",
"type": "integer",
"default": null,
"note": "The schemaVersion of protocol/data-collection-fields.json the producer collected against."
},
{
"name": "entries",
"requiredness": "required",
"type": "array",
"default": null,
"note": "One object per archive member other than manifest.json and manifest.sig."
}
],
"entryFields": [
{
"name": "path",
"requiredness": "required",
"type": "string",
"default": null
},
{
"name": "type",
"requiredness": "required",
"type": "string",
"default": null
},
{
"name": "sizeBytes",
"requiredness": "required",
"type": "integer",
"default": null
},
{
"name": "sha256",
"requiredness": "required",
"type": "string",
"default": null
},
{
"name": "classification",
"requiredness": "required",
"type": "string",
"default": null
}
],
"closedEnumerations": {
"entryTypes": [
"offline-diagnostic",
"redaction-report"
],
"classifications": [
"L0",
"L1"
],
"forbiddenClassifications": [
"L2",
"L3"
],
"note": "docs/data-classification.md defers L2 and L3 for the first release, so a manifest that declares one is rejected rather than quietly accepted and filtered. Adding an entry type or admitting a classification level is a protocol change with its own ADR and security review, not an additive field."
},
"redaction": {
"meaning": "redactionVersion identifies the deterministic redaction ruleset the producer applied. It is a claim about what was already removed; it is never a request for Connect to redact. rulesetHash pins the exact rules behind that identifier.",
"identifierIsOpaque": true,
"comparison": "equality",
"orderingPermitted": false,
"supportedVersions": [
"rustfs.connect.redaction.v1"
],
"versionFormat": "^rustfs\\.connect\\.redaction\\.v[1-9][0-9]*$",
"rulesetHashAlgorithm": "sha256",
"rulesetHashPattern": "^[0-9a-f]{64}$",
"knownRulesetHashes": {
"rustfs.connect.redaction.v1": "b37436d8e72515394a122d633865b1dc028d4ece349352a0a3a23f52ca4285f3"
},
"unknownVersionPolicy": "reject with REDACTION_VERSION_UNKNOWN",
"rulesetHashMismatchPolicy": "reject with REDACTION_RULESET_MISMATCH",
"definedBy": "protocol/agent/v1/fixtures/redaction/ruleset.json, implemented by api/app/Modules/Diagnostics/Domain/Redaction and frozen by its own issue",
"classificationRegistry": "protocol/data-collection-fields.json, described by docs/data-classification.md",
"classificationRegistrySchemaVersion": 1,
"note": "This fixture cites the redaction and classification registries; it does not define them. A bundle whose redactionVersion Connect does not implement is rejected, because Connect cannot otherwise know what the producer believed it had removed. A known identifier carrying a foreign rulesetHash is rejected for the same reason: the identifier alone would then be a name for two different treatments."
},
"freshness": {
"maxAgeSeconds": 2592000,
"maxFutureSkewSeconds": 300,
"evaluatedAgainst": "the Connect receive time",
"note": "ADR 0003 already makes device clocks advisory. An air-gapped bundle may be couriered for weeks, so the window is generous in the past and tight in the future."
},
"supportBundleRejectionCodeMapping": {
"note": "Every protocol reason below maps into the closed six code set that support_bundles.rejected_reason_code accepts. The detailed reason stays in the verifier and is never persisted, exactly as SupportBundleRejectionReason documents.",
"codes": [
"ARCHIVE_INVALID",
"HASH_MISMATCH",
"MANIFEST_INVALID",
"SECRET_DETECTED",
"SIZE_LIMIT_EXCEEDED",
"SIZE_MISMATCH"
],
"notCoveredByThisContract": {
"ARCHIVE_INVALID": "archive structure, entry paths, entry types, and compression ratio, validated before the manifest is read",
"SECRET_DETECTED": "redaction review, owned by the redaction issue",
"SIZE_LIMIT_EXCEEDED": "the archive size ceiling, owned by the upload authorization issue"
}
}
}
@@ -0,0 +1,331 @@
{
"protocolVersion": "v1",
"fixtureSet": "bundle",
"fixture": "reject-vectors",
"description": "Support bundle manifests that must never be accepted. supportBundleRejectionCode is the code that would be persisted on the support_bundles row; the protocol reason itself never leaves the verifier.",
"authorisedBundle": {
"bundleUid": "0198f3a1-8000-7e50-8f61-4a5b6c7d8e94",
"organizationName": "organizations/0198f3a1-4c00-7a10-8b21-0c1d2e3f4a50",
"clusterName": "organizations/0198f3a1-4c00-7a10-8b21-0c1d2e3f4a50/clusters/0198f3a1-5d00-7b20-9c31-1d2e3f4a5b61",
"deviceName": "organizations/0198f3a1-4c00-7a10-8b21-0c1d2e3f4a50/clusters/0198f3a1-5d00-7b20-9c31-1d2e3f4a5b61/clusterDevices/0198f3a1-6e00-7c30-ad41-2e3f4a5b6c72",
"effectiveDeviceKeyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"note": "What Connect authorised, read from the support_bundles row and the enrolled device key. Every name in a manifest is compared against this and never trusted on its own."
},
"vectors": [
{
"name": "re-serialised copy of the accepted non-canonical manifest under its own signature",
"signerKey": "device",
"evaluationTime": "2026-08-21T00:00:00Z",
"document": {
"bytes": "ewogICAgInByb3RvY29sVmVyc2lvbiI6ICJ2MSIsCiAgICAiZm9ybWF0VmVyc2lvbiI6ICJydXN0ZnMuY29ubmVjdC5zdXBwb3J0LmJ1bmRsZU1hbmlmZXN0LzEiLAogICAgImVudHJpZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAic2hhMjU2IjogIjFkOTY1N2E2N2RmYzEyYTBjNmY5NzQ1ODZjMDQxZDZkY2U2ZjQ4MzNmYjkwZGZhNjBhYWYwYTQ1NWRkYzE2NzkiLAogICAgICAgICAgICAiY2xhc3NpZmljYXRpb24iOiAiTDAiLAogICAgICAgICAgICAicGF0aCI6ICJkaWFnbm9zdGljcy9vZmZsaW5lLXN1bW1hcnkuanNvbiIsCiAgICAgICAgICAgICJ0eXBlIjogIm9mZmxpbmUtZGlhZ25vc3RpYyIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiAyMDQ4CiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJwYXRoIjogInJlZGFjdGlvbi9yZXBvcnQuanNvbiIsCiAgICAgICAgICAgICJ0eXBlIjogInJlZGFjdGlvbi1yZXBvcnQiLAogICAgICAgICAgICAic2l6ZUJ5dGVzIjogNTEyLAogICAgICAgICAgICAic2hhMjU2IjogImRhZjFkNjE1MmZkMzc3NTJjYjk1NWY0NmYzNmU0NmE1MWU4YzE0NmUxYjM3MTgxOTQ2OTY5YzFlNTUxNzM3MzYiLAogICAgICAgICAgICAiY2xhc3NpZmljYXRpb24iOiAiTDAiCiAgICAgICAgfQogICAgXSwKICAgICJub25jZSI6ICI4RFBSeExlVXJVc01nYmtPTFpCbExOSzF0ZkFRYUxzblR1VkZsV19Hd2ZBIiwKICAgICJwcm9kdWNlZEF0IjogIjIwMjYtMDgtMjBUMDk6MzE6MDBaIiwKICAgICJkZXZpY2VLZXlJZCI6ICIzOWNhMjRjOGIwMmE1NTlmZDliZWIyYjFmNWQxOGNlZDIwYzRiYjI0NjU3N2I5MjkxNGFlNjgxNGMzZjcwYWNmIiwKICAgICJidW5kbGVVaWQiOiAiMDE5OGYzYTEtODAwMC03ZTUwLThmNjEtNGE1YjZjN2Q4ZTk0IiwKICAgICJvcmdhbml6YXRpb25OYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwIiwKICAgICJjbHVzdGVyTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEiLAogICAgImRldmljZU5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAvY2x1c3RlcnMvMDE5OGYzYTEtNWQwMC03YjIwLTljMzEtMWQyZTNmNGE1YjYxL2NsdXN0ZXJEZXZpY2VzLzAxOThmM2ExLTZlMDAtN2MzMC1hZDQxLTJlM2Y0YTViNmM3MiIsCiAgICAicmVkYWN0aW9uVmVyc2lvbiI6ICJydXN0ZnMuY29ubmVjdC5yZWRhY3Rpb24udjEiLAogICAgInJ1bGVzZXRIYXNoIjogImIzNzQzNmQ4ZTcyNTE1Mzk0YTEyMmQ2MzM4NjViMWRjMDI4ZDRlY2UzNDkzNTJhMGEzYTIzZjUyY2E0Mjg1ZjMiLAogICAgImNsYXNzaWZpY2F0aW9uUmVnaXN0cnlWZXJzaW9uIjogMQp9Cg==",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "byAAzj9jJJSXCv50pU2M5ugSES4kgN_DQzY_CFFDG64rXqn8EBApEGH_a2_TodAyArhpoM2HicOmHi_csL11cQ"
}
},
"sameParsedDocumentAs": "manifest whose bytes no re-serialiser would reproduce",
"expected": {
"signatureVerifies": false,
"accepted": false,
"reason": "SIGNATURE_INVALID",
"supportBundleRejectionCode": "MANIFEST_INVALID"
}
},
{
"name": "tampered manifest bytes with the original signature",
"signerKey": "device",
"evaluationTime": "2026-08-21T00:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Quc3VwcG9ydC5idW5kbGVNYW5pZmVzdC8xIiwKICAgICJwcm90b2NvbFZlcnNpb24iOiAidjEiLAogICAgImJ1bmRsZVVpZCI6ICIwMTk4ZjNhMS04MDAwLTdlNTAtOGY2MS00YTViNmM3ZDhlOTQiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiZGV2aWNlTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEvY2x1c3RlckRldmljZXMvMDE5OGYzYTEtNmUwMC03YzMwLWFkNDEtMmUzZjRhNWI2YzcyIiwKICAgICJkZXZpY2VLZXlJZCI6ICIzOWNhMjRjOGIwMmE1NTlmZDliZWIyYjFmNWQxOGNlZDIwYzRiYjI0NjU3N2I5MjkxNGFlNjgxNGMzZjcwYWNmIiwKICAgICJub25jZSI6ICI0dE1NQndDY1hqRVUxTEM1cDlNeGJaLTM3VGVZRFFSWWpSanNnbTR5dWUwIiwKICAgICJwcm9kdWNlZEF0IjogIjIwMjYtMDgtMjBUMDk6MzA6MDBaIiwKICAgICJyZWRhY3Rpb25WZXJzaW9uIjogInJ1c3Rmcy5jb25uZWN0LnJlZGFjdGlvbi52MSIsCiAgICAicnVsZXNldEhhc2giOiAiYjM3NDM2ZDhlNzI1MTUzOTRhMTIyZDYzMzg2NWIxZGMwMjhkNGVjZTM0OTM1MmEwYTNhMjNmNTJjYTQyODVmMyIsCiAgICAiY2xhc3NpZmljYXRpb25SZWdpc3RyeVZlcnNpb24iOiAxLAogICAgImVudHJpZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicGF0aCI6ICJkaWFnbm9zdGljcy9vZmZsaW5lLXN1bW1hcnkuanNvbiIsCiAgICAgICAgICAgICJ0eXBlIjogIm9mZmxpbmUtZGlhZ25vc3RpYyIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiAyMDQ5LAogICAgICAgICAgICAic2hhMjU2IjogIjFkOTY1N2E2N2RmYzEyYTBjNmY5NzQ1ODZjMDQxZDZkY2U2ZjQ4MzNmYjkwZGZhNjBhYWYwYTQ1NWRkYzE2NzkiLAogICAgICAgICAgICAiY2xhc3NpZmljYXRpb24iOiAiTDAiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJwYXRoIjogImRpYWdub3N0aWNzL2hvc3Qtc3VtbWFyeS5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAib2ZmbGluZS1kaWFnbm9zdGljIiwKICAgICAgICAgICAgInNpemVCeXRlcyI6IDQwOTYsCiAgICAgICAgICAgICJzaGEyNTYiOiAiNDViYjZhMjgxYzk4MzBkOGUxNmQ4NTg3MDA2ODQwMTA0OGY2NDQ0YmFkY2UyMGFhOThlYTNmMGNjMGVkYjIwNCIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMSIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgInBhdGgiOiAicmVkYWN0aW9uL3JlcG9ydC5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAicmVkYWN0aW9uLXJlcG9ydCIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiA1MTIsCiAgICAgICAgICAgICJzaGEyNTYiOiAiZGFmMWQ2MTUyZmQzNzc1MmNiOTU1ZjQ2ZjM2ZTQ2YTUxZThjMTQ2ZTFiMzcxODE5NDY5NjljMWU1NTE3MzczNiIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMCIKICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "st6Svkk8UgwDMq_8rEomsdIdANyPjqTYFTWPN4FISYor3su27YNRnoWWIyemileZusezfHd8BlxuiSvzJrCcnw"
}
},
"expected": {
"signatureVerifies": false,
"accepted": false,
"reason": "SIGNATURE_INVALID",
"supportBundleRejectionCode": "MANIFEST_INVALID"
}
},
{
"name": "manifest naming another organization",
"signerKey": "device",
"evaluationTime": "2026-08-21T00:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Quc3VwcG9ydC5idW5kbGVNYW5pZmVzdC8xIiwKICAgICJwcm90b2NvbFZlcnNpb24iOiAidjEiLAogICAgImJ1bmRsZVVpZCI6ICIwMTk4ZjNhMS04MDAwLTdlNTAtOGY2MS00YTViNmM3ZDhlOTQiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRiNjAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YjYwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWM3MiIsCiAgICAiZGV2aWNlTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGI2MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTVjNzIvY2x1c3RlckRldmljZXMvMDE5OGYzYTEtNmUwMC03YzMwLWFkNDEtMmUzZjRhNWI2YzcyIiwKICAgICJkZXZpY2VLZXlJZCI6ICIzOWNhMjRjOGIwMmE1NTlmZDliZWIyYjFmNWQxOGNlZDIwYzRiYjI0NjU3N2I5MjkxNGFlNjgxNGMzZjcwYWNmIiwKICAgICJub25jZSI6ICIyNlhfTE1yajlBUU9uX1hobzR3WS1HaTBmRFJPMXdvakxPVkhmRU1CaDJVIiwKICAgICJwcm9kdWNlZEF0IjogIjIwMjYtMDgtMjBUMDk6MzA6MDBaIiwKICAgICJyZWRhY3Rpb25WZXJzaW9uIjogInJ1c3Rmcy5jb25uZWN0LnJlZGFjdGlvbi52MSIsCiAgICAicnVsZXNldEhhc2giOiAiYjM3NDM2ZDhlNzI1MTUzOTRhMTIyZDYzMzg2NWIxZGMwMjhkNGVjZTM0OTM1MmEwYTNhMjNmNTJjYTQyODVmMyIsCiAgICAiY2xhc3NpZmljYXRpb25SZWdpc3RyeVZlcnNpb24iOiAxLAogICAgImVudHJpZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicGF0aCI6ICJkaWFnbm9zdGljcy9vZmZsaW5lLXN1bW1hcnkuanNvbiIsCiAgICAgICAgICAgICJ0eXBlIjogIm9mZmxpbmUtZGlhZ25vc3RpYyIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiAyMDQ4LAogICAgICAgICAgICAic2hhMjU2IjogIjFkOTY1N2E2N2RmYzEyYTBjNmY5NzQ1ODZjMDQxZDZkY2U2ZjQ4MzNmYjkwZGZhNjBhYWYwYTQ1NWRkYzE2NzkiLAogICAgICAgICAgICAiY2xhc3NpZmljYXRpb24iOiAiTDAiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJwYXRoIjogImRpYWdub3N0aWNzL2hvc3Qtc3VtbWFyeS5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAib2ZmbGluZS1kaWFnbm9zdGljIiwKICAgICAgICAgICAgInNpemVCeXRlcyI6IDQwOTYsCiAgICAgICAgICAgICJzaGEyNTYiOiAiNDViYjZhMjgxYzk4MzBkOGUxNmQ4NTg3MDA2ODQwMTA0OGY2NDQ0YmFkY2UyMGFhOThlYTNmMGNjMGVkYjIwNCIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMSIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgInBhdGgiOiAicmVkYWN0aW9uL3JlcG9ydC5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAicmVkYWN0aW9uLXJlcG9ydCIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiA1MTIsCiAgICAgICAgICAgICJzaGEyNTYiOiAiZGFmMWQ2MTUyZmQzNzc1MmNiOTU1ZjQ2ZjM2ZTQ2YTUxZThjMTQ2ZTFiMzcxODE5NDY5NjljMWU1NTE3MzczNiIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMCIKICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "mLo2vgNTBuAHE185MWezEZX_sgU-j7i7NQNfQlKvKw8r6AXhBjEP3yIk2h2uD-lTzonfcHaVKKHImXIwN1U4jA"
}
},
"expected": {
"signatureVerifies": true,
"organizationMatches": false,
"accepted": false,
"reason": "ORGANIZATION_MISMATCH",
"supportBundleRejectionCode": "MANIFEST_INVALID"
}
},
{
"name": "manifest naming another cluster inside the same organization",
"signerKey": "device",
"evaluationTime": "2026-08-21T00:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Quc3VwcG9ydC5idW5kbGVNYW5pZmVzdC8xIiwKICAgICJwcm90b2NvbFZlcnNpb24iOiAidjEiLAogICAgImJ1bmRsZVVpZCI6ICIwMTk4ZjNhMS04MDAwLTdlNTAtOGY2MS00YTViNmM3ZDhlOTQiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWM3MiIsCiAgICAiZGV2aWNlTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEvY2x1c3RlckRldmljZXMvMDE5OGYzYTEtNmUwMC03YzMwLWFkNDEtMmUzZjRhNWI2YzcyIiwKICAgICJkZXZpY2VLZXlJZCI6ICIzOWNhMjRjOGIwMmE1NTlmZDliZWIyYjFmNWQxOGNlZDIwYzRiYjI0NjU3N2I5MjkxNGFlNjgxNGMzZjcwYWNmIiwKICAgICJub25jZSI6ICJsUTB6NWszNUoxUjNqdldpMG9RSUlaek1mMnFpckVMOW5iOVI2T2FFS1hzIiwKICAgICJwcm9kdWNlZEF0IjogIjIwMjYtMDgtMjBUMDk6MzA6MDBaIiwKICAgICJyZWRhY3Rpb25WZXJzaW9uIjogInJ1c3Rmcy5jb25uZWN0LnJlZGFjdGlvbi52MSIsCiAgICAicnVsZXNldEhhc2giOiAiYjM3NDM2ZDhlNzI1MTUzOTRhMTIyZDYzMzg2NWIxZGMwMjhkNGVjZTM0OTM1MmEwYTNhMjNmNTJjYTQyODVmMyIsCiAgICAiY2xhc3NpZmljYXRpb25SZWdpc3RyeVZlcnNpb24iOiAxLAogICAgImVudHJpZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicGF0aCI6ICJkaWFnbm9zdGljcy9vZmZsaW5lLXN1bW1hcnkuanNvbiIsCiAgICAgICAgICAgICJ0eXBlIjogIm9mZmxpbmUtZGlhZ25vc3RpYyIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiAyMDQ4LAogICAgICAgICAgICAic2hhMjU2IjogIjFkOTY1N2E2N2RmYzEyYTBjNmY5NzQ1ODZjMDQxZDZkY2U2ZjQ4MzNmYjkwZGZhNjBhYWYwYTQ1NWRkYzE2NzkiLAogICAgICAgICAgICAiY2xhc3NpZmljYXRpb24iOiAiTDAiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJwYXRoIjogImRpYWdub3N0aWNzL2hvc3Qtc3VtbWFyeS5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAib2ZmbGluZS1kaWFnbm9zdGljIiwKICAgICAgICAgICAgInNpemVCeXRlcyI6IDQwOTYsCiAgICAgICAgICAgICJzaGEyNTYiOiAiNDViYjZhMjgxYzk4MzBkOGUxNmQ4NTg3MDA2ODQwMTA0OGY2NDQ0YmFkY2UyMGFhOThlYTNmMGNjMGVkYjIwNCIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMSIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgInBhdGgiOiAicmVkYWN0aW9uL3JlcG9ydC5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAicmVkYWN0aW9uLXJlcG9ydCIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiA1MTIsCiAgICAgICAgICAgICJzaGEyNTYiOiAiZGFmMWQ2MTUyZmQzNzc1MmNiOTU1ZjQ2ZjM2ZTQ2YTUxZThjMTQ2ZTFiMzcxODE5NDY5NjljMWU1NTE3MzczNiIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMCIKICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "560Jc2BYkDzkQ4A2tfGsXfpx1LazwMazfWmOzMi79VUB3keMD8kl6uS3lD3ej_d6hQAR5Blj96-rVGCxhDM_Pw"
}
},
"expected": {
"signatureVerifies": true,
"clusterMatches": false,
"accepted": false,
"reason": "CLUSTER_MISMATCH",
"supportBundleRejectionCode": "MANIFEST_INVALID"
}
},
{
"name": "manifest naming another device inside the same cluster",
"signerKey": "device",
"evaluationTime": "2026-08-21T00:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Quc3VwcG9ydC5idW5kbGVNYW5pZmVzdC8xIiwKICAgICJwcm90b2NvbFZlcnNpb24iOiAidjEiLAogICAgImJ1bmRsZVVpZCI6ICIwMTk4ZjNhMS04MDAwLTdlNTAtOGY2MS00YTViNmM3ZDhlOTQiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiZGV2aWNlTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEvY2x1c3RlckRldmljZXMvMDE5OGYzYTEtNmUwMC03YzMwLWFkNDEtMmUzZjRhNWI2ZDgzIiwKICAgICJkZXZpY2VLZXlJZCI6ICIzOWNhMjRjOGIwMmE1NTlmZDliZWIyYjFmNWQxOGNlZDIwYzRiYjI0NjU3N2I5MjkxNGFlNjgxNGMzZjcwYWNmIiwKICAgICJub25jZSI6ICJBX1Bfc0FPdFZ2eG9HQXJYdVBuNzlpYU0yRXllcmJLSXBPbzl4VGZHWmlzIiwKICAgICJwcm9kdWNlZEF0IjogIjIwMjYtMDgtMjBUMDk6MzA6MDBaIiwKICAgICJyZWRhY3Rpb25WZXJzaW9uIjogInJ1c3Rmcy5jb25uZWN0LnJlZGFjdGlvbi52MSIsCiAgICAicnVsZXNldEhhc2giOiAiYjM3NDM2ZDhlNzI1MTUzOTRhMTIyZDYzMzg2NWIxZGMwMjhkNGVjZTM0OTM1MmEwYTNhMjNmNTJjYTQyODVmMyIsCiAgICAiY2xhc3NpZmljYXRpb25SZWdpc3RyeVZlcnNpb24iOiAxLAogICAgImVudHJpZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicGF0aCI6ICJkaWFnbm9zdGljcy9vZmZsaW5lLXN1bW1hcnkuanNvbiIsCiAgICAgICAgICAgICJ0eXBlIjogIm9mZmxpbmUtZGlhZ25vc3RpYyIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiAyMDQ4LAogICAgICAgICAgICAic2hhMjU2IjogIjFkOTY1N2E2N2RmYzEyYTBjNmY5NzQ1ODZjMDQxZDZkY2U2ZjQ4MzNmYjkwZGZhNjBhYWYwYTQ1NWRkYzE2NzkiLAogICAgICAgICAgICAiY2xhc3NpZmljYXRpb24iOiAiTDAiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJwYXRoIjogImRpYWdub3N0aWNzL2hvc3Qtc3VtbWFyeS5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAib2ZmbGluZS1kaWFnbm9zdGljIiwKICAgICAgICAgICAgInNpemVCeXRlcyI6IDQwOTYsCiAgICAgICAgICAgICJzaGEyNTYiOiAiNDViYjZhMjgxYzk4MzBkOGUxNmQ4NTg3MDA2ODQwMTA0OGY2NDQ0YmFkY2UyMGFhOThlYTNmMGNjMGVkYjIwNCIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMSIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgInBhdGgiOiAicmVkYWN0aW9uL3JlcG9ydC5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAicmVkYWN0aW9uLXJlcG9ydCIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiA1MTIsCiAgICAgICAgICAgICJzaGEyNTYiOiAiZGFmMWQ2MTUyZmQzNzc1MmNiOTU1ZjQ2ZjM2ZTQ2YTUxZThjMTQ2ZTFiMzcxODE5NDY5NjljMWU1NTE3MzczNiIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMCIKICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "-N1-Am_dNSVRKdb5YSXyKB9MYG6x67ZrlNSw98dpkbZHwUGa0rt0yZne8UScYfrqW6SV9r5F2SNUSxsM-gyxQg"
}
},
"expected": {
"signatureVerifies": true,
"deviceMatches": false,
"accepted": false,
"reason": "DEVICE_MISMATCH",
"supportBundleRejectionCode": "MANIFEST_INVALID"
}
},
{
"name": "manifest older than the freshness window",
"signerKey": "device",
"evaluationTime": "2026-08-21T00:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Quc3VwcG9ydC5idW5kbGVNYW5pZmVzdC8xIiwKICAgICJwcm90b2NvbFZlcnNpb24iOiAidjEiLAogICAgImJ1bmRsZVVpZCI6ICIwMTk4ZjNhMS04MDAwLTdlNTAtOGY2MS00YTViNmM3ZDhlOTQiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiZGV2aWNlTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEvY2x1c3RlckRldmljZXMvMDE5OGYzYTEtNmUwMC03YzMwLWFkNDEtMmUzZjRhNWI2YzcyIiwKICAgICJkZXZpY2VLZXlJZCI6ICIzOWNhMjRjOGIwMmE1NTlmZDliZWIyYjFmNWQxOGNlZDIwYzRiYjI0NjU3N2I5MjkxNGFlNjgxNGMzZjcwYWNmIiwKICAgICJub25jZSI6ICJzUWljMmNNbkt2Ulc0M3VXWm9BbjF5bzRDbHZzU0RuTF9BSFFsbk0tQjhvIiwKICAgICJwcm9kdWNlZEF0IjogIjIwMjYtMDYtMDFUMDA6MDA6MDBaIiwKICAgICJyZWRhY3Rpb25WZXJzaW9uIjogInJ1c3Rmcy5jb25uZWN0LnJlZGFjdGlvbi52MSIsCiAgICAicnVsZXNldEhhc2giOiAiYjM3NDM2ZDhlNzI1MTUzOTRhMTIyZDYzMzg2NWIxZGMwMjhkNGVjZTM0OTM1MmEwYTNhMjNmNTJjYTQyODVmMyIsCiAgICAiY2xhc3NpZmljYXRpb25SZWdpc3RyeVZlcnNpb24iOiAxLAogICAgImVudHJpZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicGF0aCI6ICJkaWFnbm9zdGljcy9vZmZsaW5lLXN1bW1hcnkuanNvbiIsCiAgICAgICAgICAgICJ0eXBlIjogIm9mZmxpbmUtZGlhZ25vc3RpYyIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiAyMDQ4LAogICAgICAgICAgICAic2hhMjU2IjogIjFkOTY1N2E2N2RmYzEyYTBjNmY5NzQ1ODZjMDQxZDZkY2U2ZjQ4MzNmYjkwZGZhNjBhYWYwYTQ1NWRkYzE2NzkiLAogICAgICAgICAgICAiY2xhc3NpZmljYXRpb24iOiAiTDAiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJwYXRoIjogImRpYWdub3N0aWNzL2hvc3Qtc3VtbWFyeS5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAib2ZmbGluZS1kaWFnbm9zdGljIiwKICAgICAgICAgICAgInNpemVCeXRlcyI6IDQwOTYsCiAgICAgICAgICAgICJzaGEyNTYiOiAiNDViYjZhMjgxYzk4MzBkOGUxNmQ4NTg3MDA2ODQwMTA0OGY2NDQ0YmFkY2UyMGFhOThlYTNmMGNjMGVkYjIwNCIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMSIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgInBhdGgiOiAicmVkYWN0aW9uL3JlcG9ydC5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAicmVkYWN0aW9uLXJlcG9ydCIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiA1MTIsCiAgICAgICAgICAgICJzaGEyNTYiOiAiZGFmMWQ2MTUyZmQzNzc1MmNiOTU1ZjQ2ZjM2ZTQ2YTUxZThjMTQ2ZTFiMzcxODE5NDY5NjljMWU1NTE3MzczNiIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMCIKICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "CU715flInzASTMilUaRKFDPxEkTm4V7O2dwJQQ4QpLR_25KIX7ztyQ0TyodFjcaqC_6mH8vFt6avK0VUH8qpLQ"
}
},
"expected": {
"signatureVerifies": true,
"withinWindow": false,
"accepted": false,
"reason": "MANIFEST_EXPIRED",
"supportBundleRejectionCode": "MANIFEST_INVALID"
}
},
{
"name": "manifest produced further in the future than the skew tolerance",
"signerKey": "device",
"evaluationTime": "2026-08-21T00:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Quc3VwcG9ydC5idW5kbGVNYW5pZmVzdC8xIiwKICAgICJwcm90b2NvbFZlcnNpb24iOiAidjEiLAogICAgImJ1bmRsZVVpZCI6ICIwMTk4ZjNhMS04MDAwLTdlNTAtOGY2MS00YTViNmM3ZDhlOTQiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiZGV2aWNlTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEvY2x1c3RlckRldmljZXMvMDE5OGYzYTEtNmUwMC03YzMwLWFkNDEtMmUzZjRhNWI2YzcyIiwKICAgICJkZXZpY2VLZXlJZCI6ICIzOWNhMjRjOGIwMmE1NTlmZDliZWIyYjFmNWQxOGNlZDIwYzRiYjI0NjU3N2I5MjkxNGFlNjgxNGMzZjcwYWNmIiwKICAgICJub25jZSI6ICJ1MnVRa2RSTmk1d19QcGNSZlkzS2JDVHBpM2JoUHFBYkI3MFBVXzZNaGUwIiwKICAgICJwcm9kdWNlZEF0IjogIjIwMjYtMDgtMjFUMDA6MDY6MDBaIiwKICAgICJyZWRhY3Rpb25WZXJzaW9uIjogInJ1c3Rmcy5jb25uZWN0LnJlZGFjdGlvbi52MSIsCiAgICAicnVsZXNldEhhc2giOiAiYjM3NDM2ZDhlNzI1MTUzOTRhMTIyZDYzMzg2NWIxZGMwMjhkNGVjZTM0OTM1MmEwYTNhMjNmNTJjYTQyODVmMyIsCiAgICAiY2xhc3NpZmljYXRpb25SZWdpc3RyeVZlcnNpb24iOiAxLAogICAgImVudHJpZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicGF0aCI6ICJkaWFnbm9zdGljcy9vZmZsaW5lLXN1bW1hcnkuanNvbiIsCiAgICAgICAgICAgICJ0eXBlIjogIm9mZmxpbmUtZGlhZ25vc3RpYyIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiAyMDQ4LAogICAgICAgICAgICAic2hhMjU2IjogIjFkOTY1N2E2N2RmYzEyYTBjNmY5NzQ1ODZjMDQxZDZkY2U2ZjQ4MzNmYjkwZGZhNjBhYWYwYTQ1NWRkYzE2NzkiLAogICAgICAgICAgICAiY2xhc3NpZmljYXRpb24iOiAiTDAiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJwYXRoIjogImRpYWdub3N0aWNzL2hvc3Qtc3VtbWFyeS5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAib2ZmbGluZS1kaWFnbm9zdGljIiwKICAgICAgICAgICAgInNpemVCeXRlcyI6IDQwOTYsCiAgICAgICAgICAgICJzaGEyNTYiOiAiNDViYjZhMjgxYzk4MzBkOGUxNmQ4NTg3MDA2ODQwMTA0OGY2NDQ0YmFkY2UyMGFhOThlYTNmMGNjMGVkYjIwNCIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMSIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgInBhdGgiOiAicmVkYWN0aW9uL3JlcG9ydC5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAicmVkYWN0aW9uLXJlcG9ydCIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiA1MTIsCiAgICAgICAgICAgICJzaGEyNTYiOiAiZGFmMWQ2MTUyZmQzNzc1MmNiOTU1ZjQ2ZjM2ZTQ2YTUxZThjMTQ2ZTFiMzcxODE5NDY5NjljMWU1NTE3MzczNiIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMCIKICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "d8ozgCiXo8msztj5u503DqG3SBrB8p4efkek8p4zI0pV2C4I0jpf9h7EZhMBQTogUflLr92QRLqvp8ubnbbEIw"
}
},
"expected": {
"signatureVerifies": true,
"withinWindow": false,
"accepted": false,
"reason": "MANIFEST_NOT_YET_VALID",
"supportBundleRejectionCode": "MANIFEST_INVALID"
}
},
{
"name": "manifest signed by a device key Connect never enrolled",
"signerKey": "foreignDevice",
"evaluationTime": "2026-08-21T00:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Quc3VwcG9ydC5idW5kbGVNYW5pZmVzdC8xIiwKICAgICJwcm90b2NvbFZlcnNpb24iOiAidjEiLAogICAgImJ1bmRsZVVpZCI6ICIwMTk4ZjNhMS04MDAwLTdlNTAtOGY2MS00YTViNmM3ZDhlOTQiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiZGV2aWNlTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEvY2x1c3RlckRldmljZXMvMDE5OGYzYTEtNmUwMC03YzMwLWFkNDEtMmUzZjRhNWI2YzcyIiwKICAgICJkZXZpY2VLZXlJZCI6ICI2M2Q4MTg0ZjJiMmVjNjg5NWJiNzFiOTk5YzkwMTkyZDc3MTg2OTRiYTA0MmJhZDg2MzY0YjI3NjI4ZjljYjUwIiwKICAgICJub25jZSI6ICI0S1JEQzc2SzM4MWY5cnN3OWpZT2tFbFF3cmJSeUFZN0FwWHJRYXgwelpRIiwKICAgICJwcm9kdWNlZEF0IjogIjIwMjYtMDgtMjBUMDk6MzA6MDBaIiwKICAgICJyZWRhY3Rpb25WZXJzaW9uIjogInJ1c3Rmcy5jb25uZWN0LnJlZGFjdGlvbi52MSIsCiAgICAicnVsZXNldEhhc2giOiAiYjM3NDM2ZDhlNzI1MTUzOTRhMTIyZDYzMzg2NWIxZGMwMjhkNGVjZTM0OTM1MmEwYTNhMjNmNTJjYTQyODVmMyIsCiAgICAiY2xhc3NpZmljYXRpb25SZWdpc3RyeVZlcnNpb24iOiAxLAogICAgImVudHJpZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicGF0aCI6ICJkaWFnbm9zdGljcy9vZmZsaW5lLXN1bW1hcnkuanNvbiIsCiAgICAgICAgICAgICJ0eXBlIjogIm9mZmxpbmUtZGlhZ25vc3RpYyIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiAyMDQ4LAogICAgICAgICAgICAic2hhMjU2IjogIjFkOTY1N2E2N2RmYzEyYTBjNmY5NzQ1ODZjMDQxZDZkY2U2ZjQ4MzNmYjkwZGZhNjBhYWYwYTQ1NWRkYzE2NzkiLAogICAgICAgICAgICAiY2xhc3NpZmljYXRpb24iOiAiTDAiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJwYXRoIjogImRpYWdub3N0aWNzL2hvc3Qtc3VtbWFyeS5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAib2ZmbGluZS1kaWFnbm9zdGljIiwKICAgICAgICAgICAgInNpemVCeXRlcyI6IDQwOTYsCiAgICAgICAgICAgICJzaGEyNTYiOiAiNDViYjZhMjgxYzk4MzBkOGUxNmQ4NTg3MDA2ODQwMTA0OGY2NDQ0YmFkY2UyMGFhOThlYTNmMGNjMGVkYjIwNCIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMSIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgInBhdGgiOiAicmVkYWN0aW9uL3JlcG9ydC5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAicmVkYWN0aW9uLXJlcG9ydCIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiA1MTIsCiAgICAgICAgICAgICJzaGEyNTYiOiAiZGFmMWQ2MTUyZmQzNzc1MmNiOTU1ZjQ2ZjM2ZTQ2YTUxZThjMTQ2ZTFiMzcxODE5NDY5NjljMWU1NTE3MzczNiIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMCIKICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "63d8184f2b2ec6895bb71b999c90192d7718694ba042bad86364b27628f9cb50",
"value": "CXTr6odGyXA5Ipa8yXkhqQObrO_-OywgW-Q9Cc-S5NxbxJoTT_k-RfC5SCDRBETvQq7NoiZiyrE58oANmeLBSg"
}
},
"expected": {
"signatureVerifies": true,
"deviceKeyEffective": false,
"accepted": false,
"reason": "DEVICE_KEY_UNKNOWN",
"supportBundleRejectionCode": "MANIFEST_INVALID"
}
},
{
"name": "manifest signed by a revoked device key",
"signerKey": "revokedDevice",
"evaluationTime": "2026-08-21T00:00:00Z",
"deviceKeyRevoked": true,
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Quc3VwcG9ydC5idW5kbGVNYW5pZmVzdC8xIiwKICAgICJwcm90b2NvbFZlcnNpb24iOiAidjEiLAogICAgImJ1bmRsZVVpZCI6ICIwMTk4ZjNhMS04MDAwLTdlNTAtOGY2MS00YTViNmM3ZDhlOTQiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiZGV2aWNlTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEvY2x1c3RlckRldmljZXMvMDE5OGYzYTEtNmUwMC03YzMwLWFkNDEtMmUzZjRhNWI2YzcyIiwKICAgICJkZXZpY2VLZXlJZCI6ICIxZjZhMGM2ODQ3MTY4NjJjMjUwN2I0OGU3MmRlMjVmYmNiN2RiNWM3YWE2MjlmZjBjMTdlMjBiOGEzODdkN2FkIiwKICAgICJub25jZSI6ICJNUklHRjRxUFVHenFsYW5nUlJNRDNldmlyTWZ4T0FIUlp0ZHlyVmVVRjhrIiwKICAgICJwcm9kdWNlZEF0IjogIjIwMjYtMDgtMjBUMDk6MzA6MDBaIiwKICAgICJyZWRhY3Rpb25WZXJzaW9uIjogInJ1c3Rmcy5jb25uZWN0LnJlZGFjdGlvbi52MSIsCiAgICAicnVsZXNldEhhc2giOiAiYjM3NDM2ZDhlNzI1MTUzOTRhMTIyZDYzMzg2NWIxZGMwMjhkNGVjZTM0OTM1MmEwYTNhMjNmNTJjYTQyODVmMyIsCiAgICAiY2xhc3NpZmljYXRpb25SZWdpc3RyeVZlcnNpb24iOiAxLAogICAgImVudHJpZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicGF0aCI6ICJkaWFnbm9zdGljcy9vZmZsaW5lLXN1bW1hcnkuanNvbiIsCiAgICAgICAgICAgICJ0eXBlIjogIm9mZmxpbmUtZGlhZ25vc3RpYyIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiAyMDQ4LAogICAgICAgICAgICAic2hhMjU2IjogIjFkOTY1N2E2N2RmYzEyYTBjNmY5NzQ1ODZjMDQxZDZkY2U2ZjQ4MzNmYjkwZGZhNjBhYWYwYTQ1NWRkYzE2NzkiLAogICAgICAgICAgICAiY2xhc3NpZmljYXRpb24iOiAiTDAiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJwYXRoIjogImRpYWdub3N0aWNzL2hvc3Qtc3VtbWFyeS5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAib2ZmbGluZS1kaWFnbm9zdGljIiwKICAgICAgICAgICAgInNpemVCeXRlcyI6IDQwOTYsCiAgICAgICAgICAgICJzaGEyNTYiOiAiNDViYjZhMjgxYzk4MzBkOGUxNmQ4NTg3MDA2ODQwMTA0OGY2NDQ0YmFkY2UyMGFhOThlYTNmMGNjMGVkYjIwNCIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMSIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgInBhdGgiOiAicmVkYWN0aW9uL3JlcG9ydC5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAicmVkYWN0aW9uLXJlcG9ydCIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiA1MTIsCiAgICAgICAgICAgICJzaGEyNTYiOiAiZGFmMWQ2MTUyZmQzNzc1MmNiOTU1ZjQ2ZjM2ZTQ2YTUxZThjMTQ2ZTFiMzcxODE5NDY5NjljMWU1NTE3MzczNiIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMCIKICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "1f6a0c684716862c2507b48e72de25fbcb7db5c7aa629ff0c17e20b8a387d7ad",
"value": "lNDuUkKjGK6KOWUnSLWNkG_5yQFE6b2N-N1KkSIUflc-C0FjjYCFuL1b6sex4k9BLNcHoByGlDUjtkgcY4r6WQ"
}
},
"expected": {
"signatureVerifies": true,
"deviceKeyEffective": false,
"accepted": false,
"reason": "DEVICE_KEY_REVOKED",
"supportBundleRejectionCode": "MANIFEST_INVALID"
}
},
{
"name": "manifest declaring an unknown formatVersion",
"signerKey": "device",
"evaluationTime": "2026-08-21T00:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Quc3VwcG9ydC5idW5kbGVNYW5pZmVzdC8yIiwKICAgICJwcm90b2NvbFZlcnNpb24iOiAidjEiLAogICAgImJ1bmRsZVVpZCI6ICIwMTk4ZjNhMS04MDAwLTdlNTAtOGY2MS00YTViNmM3ZDhlOTQiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiZGV2aWNlTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEvY2x1c3RlckRldmljZXMvMDE5OGYzYTEtNmUwMC03YzMwLWFkNDEtMmUzZjRhNWI2YzcyIiwKICAgICJkZXZpY2VLZXlJZCI6ICIzOWNhMjRjOGIwMmE1NTlmZDliZWIyYjFmNWQxOGNlZDIwYzRiYjI0NjU3N2I5MjkxNGFlNjgxNGMzZjcwYWNmIiwKICAgICJub25jZSI6ICJZVGZNTnJIaFRwa1pPUEM4eHd4Wkt1dzZVdTBvVW9iLVQ4RkpUSXI0Ry1BIiwKICAgICJwcm9kdWNlZEF0IjogIjIwMjYtMDgtMjBUMDk6MzA6MDBaIiwKICAgICJyZWRhY3Rpb25WZXJzaW9uIjogInJ1c3Rmcy5jb25uZWN0LnJlZGFjdGlvbi52MSIsCiAgICAicnVsZXNldEhhc2giOiAiYjM3NDM2ZDhlNzI1MTUzOTRhMTIyZDYzMzg2NWIxZGMwMjhkNGVjZTM0OTM1MmEwYTNhMjNmNTJjYTQyODVmMyIsCiAgICAiY2xhc3NpZmljYXRpb25SZWdpc3RyeVZlcnNpb24iOiAxLAogICAgImVudHJpZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicGF0aCI6ICJkaWFnbm9zdGljcy9vZmZsaW5lLXN1bW1hcnkuanNvbiIsCiAgICAgICAgICAgICJ0eXBlIjogIm9mZmxpbmUtZGlhZ25vc3RpYyIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiAyMDQ4LAogICAgICAgICAgICAic2hhMjU2IjogIjFkOTY1N2E2N2RmYzEyYTBjNmY5NzQ1ODZjMDQxZDZkY2U2ZjQ4MzNmYjkwZGZhNjBhYWYwYTQ1NWRkYzE2NzkiLAogICAgICAgICAgICAiY2xhc3NpZmljYXRpb24iOiAiTDAiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJwYXRoIjogImRpYWdub3N0aWNzL2hvc3Qtc3VtbWFyeS5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAib2ZmbGluZS1kaWFnbm9zdGljIiwKICAgICAgICAgICAgInNpemVCeXRlcyI6IDQwOTYsCiAgICAgICAgICAgICJzaGEyNTYiOiAiNDViYjZhMjgxYzk4MzBkOGUxNmQ4NTg3MDA2ODQwMTA0OGY2NDQ0YmFkY2UyMGFhOThlYTNmMGNjMGVkYjIwNCIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMSIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgInBhdGgiOiAicmVkYWN0aW9uL3JlcG9ydC5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAicmVkYWN0aW9uLXJlcG9ydCIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiA1MTIsCiAgICAgICAgICAgICJzaGEyNTYiOiAiZGFmMWQ2MTUyZmQzNzc1MmNiOTU1ZjQ2ZjM2ZTQ2YTUxZThjMTQ2ZTFiMzcxODE5NDY5NjljMWU1NTE3MzczNiIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMCIKICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "t87_fDlJnpnIDl9jhfyE65RFI0HNt7FqcXDRSgwPcRooC6VP_nMrGXxfJahRXDch61HxgXMocNT02olH6QizPg"
}
},
"expected": {
"signatureVerifies": true,
"accepted": false,
"reason": "UNSUPPORTED_FORMAT",
"supportBundleRejectionCode": "MANIFEST_INVALID"
}
},
{
"name": "manifest declaring an unsupported protocol major version",
"signerKey": "device",
"evaluationTime": "2026-08-21T00:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Quc3VwcG9ydC5idW5kbGVNYW5pZmVzdC8xIiwKICAgICJwcm90b2NvbFZlcnNpb24iOiAidjIiLAogICAgImJ1bmRsZVVpZCI6ICIwMTk4ZjNhMS04MDAwLTdlNTAtOGY2MS00YTViNmM3ZDhlOTQiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiZGV2aWNlTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEvY2x1c3RlckRldmljZXMvMDE5OGYzYTEtNmUwMC03YzMwLWFkNDEtMmUzZjRhNWI2YzcyIiwKICAgICJkZXZpY2VLZXlJZCI6ICIzOWNhMjRjOGIwMmE1NTlmZDliZWIyYjFmNWQxOGNlZDIwYzRiYjI0NjU3N2I5MjkxNGFlNjgxNGMzZjcwYWNmIiwKICAgICJub25jZSI6ICJVbjl2OWdTLUFQaGllX1RFOWR5NklHRW9XNUtCSzRFR3JSeTFjeFhmUHpNIiwKICAgICJwcm9kdWNlZEF0IjogIjIwMjYtMDgtMjBUMDk6MzA6MDBaIiwKICAgICJyZWRhY3Rpb25WZXJzaW9uIjogInJ1c3Rmcy5jb25uZWN0LnJlZGFjdGlvbi52MSIsCiAgICAicnVsZXNldEhhc2giOiAiYjM3NDM2ZDhlNzI1MTUzOTRhMTIyZDYzMzg2NWIxZGMwMjhkNGVjZTM0OTM1MmEwYTNhMjNmNTJjYTQyODVmMyIsCiAgICAiY2xhc3NpZmljYXRpb25SZWdpc3RyeVZlcnNpb24iOiAxLAogICAgImVudHJpZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicGF0aCI6ICJkaWFnbm9zdGljcy9vZmZsaW5lLXN1bW1hcnkuanNvbiIsCiAgICAgICAgICAgICJ0eXBlIjogIm9mZmxpbmUtZGlhZ25vc3RpYyIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiAyMDQ4LAogICAgICAgICAgICAic2hhMjU2IjogIjFkOTY1N2E2N2RmYzEyYTBjNmY5NzQ1ODZjMDQxZDZkY2U2ZjQ4MzNmYjkwZGZhNjBhYWYwYTQ1NWRkYzE2NzkiLAogICAgICAgICAgICAiY2xhc3NpZmljYXRpb24iOiAiTDAiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJwYXRoIjogImRpYWdub3N0aWNzL2hvc3Qtc3VtbWFyeS5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAib2ZmbGluZS1kaWFnbm9zdGljIiwKICAgICAgICAgICAgInNpemVCeXRlcyI6IDQwOTYsCiAgICAgICAgICAgICJzaGEyNTYiOiAiNDViYjZhMjgxYzk4MzBkOGUxNmQ4NTg3MDA2ODQwMTA0OGY2NDQ0YmFkY2UyMGFhOThlYTNmMGNjMGVkYjIwNCIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMSIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgInBhdGgiOiAicmVkYWN0aW9uL3JlcG9ydC5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAicmVkYWN0aW9uLXJlcG9ydCIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiA1MTIsCiAgICAgICAgICAgICJzaGEyNTYiOiAiZGFmMWQ2MTUyZmQzNzc1MmNiOTU1ZjQ2ZjM2ZTQ2YTUxZThjMTQ2ZTFiMzcxODE5NDY5NjljMWU1NTE3MzczNiIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMCIKICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "NHvQfH8tyFfWT3qmAYX56LnrVsVe6yfJgfS4OFJHAmgaGtQRtlRab-uqJGU2Pc_PTSK-VKEr8lWQt1Gn-hwHOA"
}
},
"expected": {
"signatureVerifies": true,
"accepted": false,
"reason": "UNSUPPORTED_PROTOCOL",
"supportBundleRejectionCode": "MANIFEST_INVALID"
}
},
{
"name": "manifest declaring a deferred classification level",
"signerKey": "device",
"evaluationTime": "2026-08-21T00:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Quc3VwcG9ydC5idW5kbGVNYW5pZmVzdC8xIiwKICAgICJwcm90b2NvbFZlcnNpb24iOiAidjEiLAogICAgImJ1bmRsZVVpZCI6ICIwMTk4ZjNhMS04MDAwLTdlNTAtOGY2MS00YTViNmM3ZDhlOTQiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiZGV2aWNlTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEvY2x1c3RlckRldmljZXMvMDE5OGYzYTEtNmUwMC03YzMwLWFkNDEtMmUzZjRhNWI2YzcyIiwKICAgICJkZXZpY2VLZXlJZCI6ICIzOWNhMjRjOGIwMmE1NTlmZDliZWIyYjFmNWQxOGNlZDIwYzRiYjI0NjU3N2I5MjkxNGFlNjgxNGMzZjcwYWNmIiwKICAgICJub25jZSI6ICJsbXpTSzFibXE4RmJFdDcyd3hNQnV3bjJZR2ZxeU1MMlNGNFlIWHZKejRvIiwKICAgICJwcm9kdWNlZEF0IjogIjIwMjYtMDgtMjBUMDk6MzA6MDBaIiwKICAgICJyZWRhY3Rpb25WZXJzaW9uIjogInJ1c3Rmcy5jb25uZWN0LnJlZGFjdGlvbi52MSIsCiAgICAicnVsZXNldEhhc2giOiAiYjM3NDM2ZDhlNzI1MTUzOTRhMTIyZDYzMzg2NWIxZGMwMjhkNGVjZTM0OTM1MmEwYTNhMjNmNTJjYTQyODVmMyIsCiAgICAiY2xhc3NpZmljYXRpb25SZWdpc3RyeVZlcnNpb24iOiAxLAogICAgImVudHJpZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicGF0aCI6ICJjb25maWcvcnVzdGZzLmpzb24iLAogICAgICAgICAgICAidHlwZSI6ICJvZmZsaW5lLWRpYWdub3N0aWMiLAogICAgICAgICAgICAic2l6ZUJ5dGVzIjogMTI4LAogICAgICAgICAgICAic2hhMjU2IjogIjBiNTlkNTA4MmE2Njk3ZWQzNjRmNzI1ZWY5NmJjZGVlNDA1ZTkwOGZkNzg1YmUzOTc2ZWUzODJmMTJlZGEzYjEiLAogICAgICAgICAgICAiY2xhc3NpZmljYXRpb24iOiAiTDIiCiAgICAgICAgfQogICAgXQp9Cg==",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "Huf_o0OKHatXYegSpJmL7-0cHcltNiWYqlM5ntKgVkdYpCgZ2brr3g9_Pub6G8FOqPXMr1lanYj212INlKgVng"
}
},
"expected": {
"signatureVerifies": true,
"accepted": false,
"reason": "CLASSIFICATION_NOT_PERMITTED",
"supportBundleRejectionCode": "MANIFEST_INVALID"
}
},
{
"name": "manifest declaring an entry type outside the closed set",
"signerKey": "device",
"evaluationTime": "2026-08-21T00:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Quc3VwcG9ydC5idW5kbGVNYW5pZmVzdC8xIiwKICAgICJwcm90b2NvbFZlcnNpb24iOiAidjEiLAogICAgImJ1bmRsZVVpZCI6ICIwMTk4ZjNhMS04MDAwLTdlNTAtOGY2MS00YTViNmM3ZDhlOTQiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiZGV2aWNlTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEvY2x1c3RlckRldmljZXMvMDE5OGYzYTEtNmUwMC03YzMwLWFkNDEtMmUzZjRhNWI2YzcyIiwKICAgICJkZXZpY2VLZXlJZCI6ICIzOWNhMjRjOGIwMmE1NTlmZDliZWIyYjFmNWQxOGNlZDIwYzRiYjI0NjU3N2I5MjkxNGFlNjgxNGMzZjcwYWNmIiwKICAgICJub25jZSI6ICJFZk01WFN3ZmR5cVl6amppOHN0US1XQ2ZIU1ByZTZEZHlEblRtbGIyUkF3IiwKICAgICJwcm9kdWNlZEF0IjogIjIwMjYtMDgtMjBUMDk6MzA6MDBaIiwKICAgICJyZWRhY3Rpb25WZXJzaW9uIjogInJ1c3Rmcy5jb25uZWN0LnJlZGFjdGlvbi52MSIsCiAgICAicnVsZXNldEhhc2giOiAiYjM3NDM2ZDhlNzI1MTUzOTRhMTIyZDYzMzg2NWIxZGMwMjhkNGVjZTM0OTM1MmEwYTNhMjNmNTJjYTQyODVmMyIsCiAgICAiY2xhc3NpZmljYXRpb25SZWdpc3RyeVZlcnNpb24iOiAxLAogICAgImVudHJpZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicGF0aCI6ICJsb2dzL3J1c3Rmcy5sb2ciLAogICAgICAgICAgICAidHlwZSI6ICJyYXctbG9nIiwKICAgICAgICAgICAgInNpemVCeXRlcyI6IDEyOCwKICAgICAgICAgICAgInNoYTI1NiI6ICJhZDg3ODBkM2UwM2MwYzBmOTAzYTk4ZmU2ODJmZjMzMDc3MDg2MDE2N2Q1YzBlYjI5YjBmNTQwNzVmNGQyZGM2IiwKICAgICAgICAgICAgImNsYXNzaWZpY2F0aW9uIjogIkwxIgogICAgICAgIH0KICAgIF0KfQo=",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "ToR73ejy6joSeLY0IEaiE8YRRU0XTWLwWpHW0He1eGcSgR2p3tMvF5no5PSljdZ0fDCg7dI1H_Fs0IOMd8rRqA"
}
},
"expected": {
"signatureVerifies": true,
"accepted": false,
"reason": "ENTRY_TYPE_UNKNOWN",
"supportBundleRejectionCode": "MANIFEST_INVALID"
}
},
{
"name": "manifest declaring a redaction ruleset Connect does not implement",
"signerKey": "device",
"evaluationTime": "2026-08-21T00:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Quc3VwcG9ydC5idW5kbGVNYW5pZmVzdC8xIiwKICAgICJwcm90b2NvbFZlcnNpb24iOiAidjEiLAogICAgImJ1bmRsZVVpZCI6ICIwMTk4ZjNhMS04MDAwLTdlNTAtOGY2MS00YTViNmM3ZDhlOTQiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiZGV2aWNlTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEvY2x1c3RlckRldmljZXMvMDE5OGYzYTEtNmUwMC03YzMwLWFkNDEtMmUzZjRhNWI2YzcyIiwKICAgICJkZXZpY2VLZXlJZCI6ICIzOWNhMjRjOGIwMmE1NTlmZDliZWIyYjFmNWQxOGNlZDIwYzRiYjI0NjU3N2I5MjkxNGFlNjgxNGMzZjcwYWNmIiwKICAgICJub25jZSI6ICJHdzliNi1XVHRXdDh0Z3o3MDNEOXgybk5WOW0yclNITG5waExJZ1FUQ2pZIiwKICAgICJwcm9kdWNlZEF0IjogIjIwMjYtMDgtMjBUMDk6MzA6MDBaIiwKICAgICJyZWRhY3Rpb25WZXJzaW9uIjogInJ1c3Rmcy5jb25uZWN0LnJlZGFjdGlvbi52MiIsCiAgICAicnVsZXNldEhhc2giOiAiYjM3NDM2ZDhlNzI1MTUzOTRhMTIyZDYzMzg2NWIxZGMwMjhkNGVjZTM0OTM1MmEwYTNhMjNmNTJjYTQyODVmMyIsCiAgICAiY2xhc3NpZmljYXRpb25SZWdpc3RyeVZlcnNpb24iOiAxLAogICAgImVudHJpZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicGF0aCI6ICJkaWFnbm9zdGljcy9vZmZsaW5lLXN1bW1hcnkuanNvbiIsCiAgICAgICAgICAgICJ0eXBlIjogIm9mZmxpbmUtZGlhZ25vc3RpYyIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiAyMDQ4LAogICAgICAgICAgICAic2hhMjU2IjogIjFkOTY1N2E2N2RmYzEyYTBjNmY5NzQ1ODZjMDQxZDZkY2U2ZjQ4MzNmYjkwZGZhNjBhYWYwYTQ1NWRkYzE2NzkiLAogICAgICAgICAgICAiY2xhc3NpZmljYXRpb24iOiAiTDAiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJwYXRoIjogImRpYWdub3N0aWNzL2hvc3Qtc3VtbWFyeS5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAib2ZmbGluZS1kaWFnbm9zdGljIiwKICAgICAgICAgICAgInNpemVCeXRlcyI6IDQwOTYsCiAgICAgICAgICAgICJzaGEyNTYiOiAiNDViYjZhMjgxYzk4MzBkOGUxNmQ4NTg3MDA2ODQwMTA0OGY2NDQ0YmFkY2UyMGFhOThlYTNmMGNjMGVkYjIwNCIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMSIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgInBhdGgiOiAicmVkYWN0aW9uL3JlcG9ydC5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAicmVkYWN0aW9uLXJlcG9ydCIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiA1MTIsCiAgICAgICAgICAgICJzaGEyNTYiOiAiZGFmMWQ2MTUyZmQzNzc1MmNiOTU1ZjQ2ZjM2ZTQ2YTUxZThjMTQ2ZTFiMzcxODE5NDY5NjljMWU1NTE3MzczNiIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMCIKICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "uAfgEybRN4aDwgYIKVUpZChTFD8han8azDWaor-Qb6R-9jr9byuERDLB6YglwDgFOqMEHlfITuTo0bRKiBikjg"
}
},
"expected": {
"signatureVerifies": true,
"accepted": false,
"reason": "REDACTION_VERSION_UNKNOWN",
"supportBundleRejectionCode": "MANIFEST_INVALID"
}
},
{
"name": "manifest pairing a known redaction identifier with a foreign ruleset hash",
"signerKey": "device",
"evaluationTime": "2026-08-21T00:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Quc3VwcG9ydC5idW5kbGVNYW5pZmVzdC8xIiwKICAgICJwcm90b2NvbFZlcnNpb24iOiAidjEiLAogICAgImJ1bmRsZVVpZCI6ICIwMTk4ZjNhMS04MDAwLTdlNTAtOGY2MS00YTViNmM3ZDhlOTQiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiZGV2aWNlTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEvY2x1c3RlckRldmljZXMvMDE5OGYzYTEtNmUwMC03YzMwLWFkNDEtMmUzZjRhNWI2YzcyIiwKICAgICJkZXZpY2VLZXlJZCI6ICIzOWNhMjRjOGIwMmE1NTlmZDliZWIyYjFmNWQxOGNlZDIwYzRiYjI0NjU3N2I5MjkxNGFlNjgxNGMzZjcwYWNmIiwKICAgICJub25jZSI6ICJQYVhjRE1ScDhDcEphbGE5NGVDdTJxdFRaOUhlV05LeXltakxvUWdHSUpvIiwKICAgICJwcm9kdWNlZEF0IjogIjIwMjYtMDgtMjBUMDk6MzA6MDBaIiwKICAgICJyZWRhY3Rpb25WZXJzaW9uIjogInJ1c3Rmcy5jb25uZWN0LnJlZGFjdGlvbi52MSIsCiAgICAicnVsZXNldEhhc2giOiAiYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYSIsCiAgICAiY2xhc3NpZmljYXRpb25SZWdpc3RyeVZlcnNpb24iOiAxLAogICAgImVudHJpZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicGF0aCI6ICJkaWFnbm9zdGljcy9vZmZsaW5lLXN1bW1hcnkuanNvbiIsCiAgICAgICAgICAgICJ0eXBlIjogIm9mZmxpbmUtZGlhZ25vc3RpYyIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiAyMDQ4LAogICAgICAgICAgICAic2hhMjU2IjogIjFkOTY1N2E2N2RmYzEyYTBjNmY5NzQ1ODZjMDQxZDZkY2U2ZjQ4MzNmYjkwZGZhNjBhYWYwYTQ1NWRkYzE2NzkiLAogICAgICAgICAgICAiY2xhc3NpZmljYXRpb24iOiAiTDAiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJwYXRoIjogImRpYWdub3N0aWNzL2hvc3Qtc3VtbWFyeS5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAib2ZmbGluZS1kaWFnbm9zdGljIiwKICAgICAgICAgICAgInNpemVCeXRlcyI6IDQwOTYsCiAgICAgICAgICAgICJzaGEyNTYiOiAiNDViYjZhMjgxYzk4MzBkOGUxNmQ4NTg3MDA2ODQwMTA0OGY2NDQ0YmFkY2UyMGFhOThlYTNmMGNjMGVkYjIwNCIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMSIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgInBhdGgiOiAicmVkYWN0aW9uL3JlcG9ydC5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAicmVkYWN0aW9uLXJlcG9ydCIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiA1MTIsCiAgICAgICAgICAgICJzaGEyNTYiOiAiZGFmMWQ2MTUyZmQzNzc1MmNiOTU1ZjQ2ZjM2ZTQ2YTUxZThjMTQ2ZTFiMzcxODE5NDY5NjljMWU1NTE3MzczNiIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMCIKICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "OpHhhD9keGiGFn6ZsN9IUX4_wueOMI5Xerai0ICZD-8mNjJTy8hXPDWSFwBFztZCshkRhP50p3AvThoWfphXcw"
}
},
"expected": {
"signatureVerifies": true,
"accepted": false,
"reason": "REDACTION_RULESET_MISMATCH",
"supportBundleRejectionCode": "MANIFEST_INVALID"
}
},
{
"name": "manifest reusing the nonce of an already accepted bundle",
"signerKey": "device",
"evaluationTime": "2026-08-21T00:00:00Z",
"nonceAlreadySeen": true,
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Quc3VwcG9ydC5idW5kbGVNYW5pZmVzdC8xIiwKICAgICJwcm90b2NvbFZlcnNpb24iOiAidjEiLAogICAgImJ1bmRsZVVpZCI6ICIwMTk4ZjNhMS04MDAwLTdlNTAtOGY2MS00YTViNmM3ZDhmMDUiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiZGV2aWNlTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEvY2x1c3RlckRldmljZXMvMDE5OGYzYTEtNmUwMC03YzMwLWFkNDEtMmUzZjRhNWI2YzcyIiwKICAgICJkZXZpY2VLZXlJZCI6ICIzOWNhMjRjOGIwMmE1NTlmZDliZWIyYjFmNWQxOGNlZDIwYzRiYjI0NjU3N2I5MjkxNGFlNjgxNGMzZjcwYWNmIiwKICAgICJub25jZSI6ICI0dE1NQndDY1hqRVUxTEM1cDlNeGJaLTM3VGVZRFFSWWpSanNnbTR5dWUwIiwKICAgICJwcm9kdWNlZEF0IjogIjIwMjYtMDgtMjBUMDk6MzA6MDBaIiwKICAgICJyZWRhY3Rpb25WZXJzaW9uIjogInJ1c3Rmcy5jb25uZWN0LnJlZGFjdGlvbi52MSIsCiAgICAicnVsZXNldEhhc2giOiAiYjM3NDM2ZDhlNzI1MTUzOTRhMTIyZDYzMzg2NWIxZGMwMjhkNGVjZTM0OTM1MmEwYTNhMjNmNTJjYTQyODVmMyIsCiAgICAiY2xhc3NpZmljYXRpb25SZWdpc3RyeVZlcnNpb24iOiAxLAogICAgImVudHJpZXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicGF0aCI6ICJkaWFnbm9zdGljcy9vZmZsaW5lLXN1bW1hcnkuanNvbiIsCiAgICAgICAgICAgICJ0eXBlIjogIm9mZmxpbmUtZGlhZ25vc3RpYyIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiAyMDQ4LAogICAgICAgICAgICAic2hhMjU2IjogIjFkOTY1N2E2N2RmYzEyYTBjNmY5NzQ1ODZjMDQxZDZkY2U2ZjQ4MzNmYjkwZGZhNjBhYWYwYTQ1NWRkYzE2NzkiLAogICAgICAgICAgICAiY2xhc3NpZmljYXRpb24iOiAiTDAiCiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJwYXRoIjogImRpYWdub3N0aWNzL2hvc3Qtc3VtbWFyeS5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAib2ZmbGluZS1kaWFnbm9zdGljIiwKICAgICAgICAgICAgInNpemVCeXRlcyI6IDQwOTYsCiAgICAgICAgICAgICJzaGEyNTYiOiAiNDViYjZhMjgxYzk4MzBkOGUxNmQ4NTg3MDA2ODQwMTA0OGY2NDQ0YmFkY2UyMGFhOThlYTNmMGNjMGVkYjIwNCIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMSIKICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgInBhdGgiOiAicmVkYWN0aW9uL3JlcG9ydC5qc29uIiwKICAgICAgICAgICAgInR5cGUiOiAicmVkYWN0aW9uLXJlcG9ydCIsCiAgICAgICAgICAgICJzaXplQnl0ZXMiOiA1MTIsCiAgICAgICAgICAgICJzaGEyNTYiOiAiZGFmMWQ2MTUyZmQzNzc1MmNiOTU1ZjQ2ZjM2ZTQ2YTUxZThjMTQ2ZTFiMzcxODE5NDY5NjljMWU1NTE3MzczNiIsCiAgICAgICAgICAgICJjbGFzc2lmaWNhdGlvbiI6ICJMMCIKICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "S24sWFQ8_T3y4e0F25YVoA5DFjgVLsQJZN0IsYu0ZvIUsTBMkpcV2Z3RrAvFqIYtenu1dUIyF0bO5wDS97zOTw"
}
},
"sameNonceAs": "manifest signed by the effective device key",
"expected": {
"signatureVerifies": true,
"accepted": false,
"reason": "BUNDLE_REPLAYED",
"supportBundleRejectionCode": "MANIFEST_INVALID"
}
}
]
}
@@ -0,0 +1,52 @@
{
"protocolVersion": "v1",
"maxFilesPerSet": 12,
"manifestFile": "MANIFEST.sha256",
"consumerCopy": {
"repository": "rustfs/rustfs",
"path": "protocol/agent/v1/fixtures",
"requirement": "Byte-identical copy of every populated set, compared by make protocol-compat."
},
"sets": [
{
"name": "auth",
"status": "populated",
"purpose": "Client certificate profile, RFC 9440 header profile, authentication accept and reject vectors, surface separation, and the frozen error reason registry."
},
{
"name": "version",
"status": "populated",
"purpose": "Protocol version negotiation decisions, the frozen v1 negotiation field registry, and additive compatibility in both skew directions."
},
{
"name": "registration",
"status": "populated",
"purpose": "Registration token exchange, proof of possession, replay rejection, and certificate issuance."
},
{
"name": "heartbeat",
"status": "reserved",
"purpose": "Heartbeat payloads, Connect receive time, and freshness window behavior."
},
{
"name": "inventory",
"status": "populated",
"purpose": "Inventory snapshot payloads and their allow-listed collection fields."
},
{
"name": "offline-enrollment",
"status": "populated",
"purpose": "Air-gapped device enrolment and signed artifact exchange without a certificate."
},
{
"name": "bundle",
"status": "populated",
"purpose": "Support bundle manifests, upload authorization, and archive validation."
},
{
"name": "redaction",
"status": "populated",
"purpose": "Deterministic redaction of telemetry and support bundle content."
}
]
}
@@ -0,0 +1,7 @@
d1a73b0a348845bf3ed9fb68301babc5abbc6e72243a610db1cd4794b1328070 canonical-hash.json
f1107d3e6accbaee468f1a1fdb79d7103fb2aadafd85c39020fbbca5173b03b4 field-registry.json
b3b2e7f761198d4823c94440637a48153437183f4cacec5118570e1920f73b29 old-agent-vectors.json
bec2f30dea2fd4839e501acd94c9d935817f4f1b67cd14d33cad59c0351a23ea reject-vectors.json
5493ba0d1477ad3762ed87c410d5830b47fe7aedac511f97b96385a51e258c32 secret-like-vectors.json
6e2df36bf266fcca4b2c9a856d0a9ca8ab7e2cfaf7534b9af176c6597cb38137 unknown-field-vectors.json
46e5d3398719a31dcc912379e66b4ab06a433d125366cce5239c49956fc4ab1b valid-vectors.json
@@ -0,0 +1,63 @@
{
"protocolVersion": "v1",
"fixtureSet": "inventory",
"fixture": "canonical-hash",
"description": "The canonical content hash of an L0 inventory snapshot, defined precisely enough that an independent implementation reproduces it byte for byte. The prose form is docs/data-collection/inventory-l0.md; this file is the machine-checkable copy.",
"algorithm": "SHA-256",
"output": {
"encoding": "lowercase hexadecimal",
"length": 64,
"pattern": "^[0-9a-f]{64}$"
},
"hashedBytes": "domainPrefix followed immediately by canonicalJson encoded as UTF-8, with nothing between them and nothing after",
"domainPrefix": {
"ascii": "rustfs-connect/agent/v1/inventory-snapshot\n",
"hex": "7275737466732d636f6e6e6563742f6167656e742f76312f696e76656e746f72792d736e617073686f740a",
"byteLength": 43,
"reason": "Domain separation. An inventory hash can never equal a heartbeat, bundle, or job hash computed over the same member values."
},
"input": {
"startsFrom": "the normalized document, after the version gate, after unknown members and unknown coarse flag tokens have been discarded, and after schema and cross-field validation succeeded",
"excludedMembers": ["protocolVersion"],
"excludedMembersReason": "protocolVersion is negotiation input, not content. Excluding it means the same cluster state hashes identically across protocol versions, and it is why an unknown member can never change the hash.",
"materializedDefaults": {
"osVersion": null,
"coarseFlags": []
},
"materializedDefaultsReason": "Absent optional members take their documented default and the default is serialized, so the canonical object always carries exactly the same seven member names. An old agent that omits an optional member and a new agent that sends its default explicitly produce the same hash."
},
"serialization": {
"form": "one line of UTF-8 with no insignificant whitespace: no space after a colon or comma, no newline, no trailing newline",
"objects": "{ then member,member,... then }, where a member is \"name\":value; an empty object is {}",
"arrays": "[ then value,value,... then ]; an empty array is []",
"memberOrder": "ascending by the UTF-8 bytes of the member name, compared as unsigned bytes; no locale, no case folding, no UTF-16 code unit order",
"arrayOrder": "the normalized order, which for coarseFlags is de-duplicated and sorted ascending by the same unsigned-byte comparison",
"null": "the four bytes null",
"integers": "base ten, no plus sign, no leading zero except the single digit 0, no decimal point, no exponent; every v1 value is in 0..9007199254740991 so no implementation needs arbitrary precision",
"strings": "opened and closed by a double quote with the value's bytes between them",
"stringEscaping": "none is ever emitted. Every string value the canonical input can hold matches ^[a-z0-9.]{1,32}$ and every member name matches ^[a-zA-Z]{1,32}$, so no character requiring a JSON escape can reach the hash. A value outside those classes is a schema violation and is rejected before hashing.",
"canonicalValueCharacterClass": "^[a-z0-9.]{1,32}$",
"canonicalMemberNameCharacterClass": "^[a-zA-Z]{1,32}$",
"absentTypes": "the canonical input contains no boolean, no floating point number, and no nested array of objects. Adding a member of one of those types requires extending these rules in a reviewed protocol change."
},
"examples": [
{
"name": "fully populated snapshot",
"canonicalJson": "{\"capacityTotalBytes\":1099511627776,\"capacityUsedBytes\":412316860416,\"coarseFlags\":[\"cluster.degraded\",\"drive.offline\"],\"driveCount\":96,\"nodeCount\":8,\"osVersion\":{\"family\":\"linux\",\"major\":6,\"minor\":8},\"rustfsVersion\":\"1.4.2\"}",
"canonicalJsonByteLength": 225,
"contentHash": "e1cba9f30a26b94bb289a8a4b630a704dafaeb4960bffd38cef98e3d2581518f"
},
{
"name": "every limit at its maximum",
"canonicalJson": "{\"capacityTotalBytes\":9007199254740991,\"capacityUsedBytes\":9007199254740991,\"coarseFlags\":[\"capacity.critical\",\"capacity.warning\",\"clock.skew\",\"cluster.degraded\",\"cluster.healing\",\"cluster.readonly\",\"drive.offline\",\"node.offline\"],\"driveCount\":1048576,\"nodeCount\":4096,\"osVersion\":{\"family\":\"other\",\"major\":9999,\"minor\":9999},\"rustfsVersion\":\"9999.9999.9999\"}",
"canonicalJsonByteLength": 359,
"contentHash": "b69d51f898a53562a7057faa5bda1e21699ee6615517cd8b1455096f9171bce4"
},
{
"name": "every limit at its minimum",
"canonicalJson": "{\"capacityTotalBytes\":0,\"capacityUsedBytes\":0,\"coarseFlags\":[],\"driveCount\":0,\"nodeCount\":1,\"osVersion\":null,\"rustfsVersion\":\"0.0.0\"}",
"canonicalJsonByteLength": 133,
"contentHash": "08ebc03ee906c05d686ef32c0998154213f2811a2d07408a9ef7d0b205cc701b"
}
]
}
@@ -0,0 +1,203 @@
{
"protocolVersion": "v1",
"fixtureSet": "inventory",
"fixture": "field-registry",
"description": "The frozen v1 inventory snapshot envelope. Every collected member cites its data-collection registry id from protocol/data-collection-fields.json. Any member not listed here is unknown: it is accepted, discarded before validation, never persisted, never echoed back, and its raw text is never logged.",
"envelope": "InventorySnapshot",
"schema": "protocol/agent/v1/inventory-snapshot.schema.json",
"classificationRegistry": "protocol/data-collection-fields.json",
"level": "L0",
"source": "inventory",
"retentionDays": 90,
"cadence": "per-inventory",
"supportedMajorVersions": [1],
"protocolVersionPattern": "^v[1-9][0-9]{0,3}$",
"unknownFieldPolicy": "accept-and-discard",
"unknownCoarseFlagPolicy": "discard",
"unknownMajorVersionPolicy": "reject",
"fields": [
{
"name": "protocolVersion",
"registryId": null,
"collected": false,
"requiredness": "required",
"type": "string",
"default": null,
"limits": {
"type": "string",
"pattern": "^v[1-9][0-9]{0,3}$"
},
"includedInContentHash": false,
"note": "Negotiation input shared with the frozen v1 negotiation envelope. It gates processing, is never persisted as an inventory attribute, and therefore has no data-collection registry id."
},
{
"name": "rustfsVersion",
"registryId": "inventory.rustfsVersion",
"collected": true,
"requiredness": "required",
"type": "string",
"default": null,
"limits": {
"type": "string",
"pattern": "^(0|[1-9][0-9]{0,3})\\.(0|[1-9][0-9]{0,3})\\.(0|[1-9][0-9]{0,3})$"
},
"includedInContentHash": true,
"note": "Coarse release only. Pre-release and build metadata are rejected because a build identifier fingerprints a private build."
},
{
"name": "osVersion",
"registryId": "inventory.osVersion",
"collected": true,
"requiredness": "optional",
"type": ["object", "null"],
"default": null,
"limits": {
"type": ["object", "null"],
"additionalProperties": false,
"required": ["family", "major", "minor"]
},
"memberLimits": {
"family": {
"type": "string",
"enum": ["linux", "darwin", "windows", "freebsd", "other"]
},
"major": { "type": "integer", "minimum": 0, "maximum": 9999 },
"minor": { "type": "integer", "minimum": 0, "maximum": 9999 }
},
"includedInContentHash": true,
"note": "The family is a closed vocabulary, never free text. An unlisted operating system reports 'other'. Unknown members of this object are discarded exactly like unknown top-level members."
},
{
"name": "nodeCount",
"registryId": "inventory.nodeCount",
"collected": true,
"requiredness": "required",
"type": "integer",
"default": null,
"limits": {
"type": "integer",
"minimum": 1,
"maximum": 4096
},
"includedInContentHash": true,
"note": "A cluster reporting inventory has at least one node."
},
{
"name": "driveCount",
"registryId": "inventory.driveCount",
"collected": true,
"requiredness": "required",
"type": "integer",
"default": null,
"limits": {
"type": "integer",
"minimum": 0,
"maximum": 1048576
},
"includedInContentHash": true,
"note": "Zero is legal while a cluster is being provisioned."
},
{
"name": "capacityTotalBytes",
"registryId": "inventory.capacityTotalBytes",
"collected": true,
"requiredness": "required",
"type": "integer",
"default": null,
"limits": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"includedInContentHash": true,
"note": "2^53-1 is the largest integer Rust u64, PHP int, JavaScript number, and JSON all represent exactly. Above the cap is a validation error, never a silent truncation."
},
{
"name": "capacityUsedBytes",
"registryId": "inventory.capacityUsedBytes",
"collected": true,
"requiredness": "required",
"type": "integer",
"default": null,
"limits": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"includedInContentHash": true,
"note": "Must not exceed capacityTotalBytes. JSON Schema cannot compare two members, so the validator enforces that invariant separately."
},
{
"name": "coarseFlags",
"registryId": "inventory.coarseFlags",
"collected": true,
"requiredness": "optional",
"type": "array",
"default": [],
"limits": {
"type": "array",
"maxItems": 8,
"uniqueItems": true
},
"itemLimits": {
"type": "string",
"enum": [
"capacity.critical",
"capacity.warning",
"clock.skew",
"cluster.degraded",
"cluster.healing",
"cluster.readonly",
"drive.offline",
"node.offline"
]
},
"includedInContentHash": true,
"note": "Allow-listed conditions only. Normalization discards unknown tokens, de-duplicates, and sorts ascending by UTF-8 bytes, so agent-side ordering never changes the content hash."
}
],
"errorReasons": {
"description": "Inventory-scoped ErrorInfo reasons. The negotiation reason is not redeclared here: an unsupported major version fails with UNSUPPORTED_PROTOCOL from protocol/agent/v1/fixtures/auth/error-codes.json, before any inventory member is read. INVENTORY_FIELD_INVALID is new in this issue and must not collide with a reason already frozen for authentication.",
"versionFailure": "UNSUPPORTED_PROTOCOL",
"payloadFailure": {
"reason": "INVENTORY_FIELD_INVALID",
"status": "INVALID_ARGUMENT",
"httpStatus": 400,
"domain": "rustfs.connect",
"meaning": "The normalized inventory snapshot violates the frozen schema or a cross-field invariant."
},
"disclosureRules": [
"A rejection names the frozen member path that failed and never the value that failed.",
"A rejection never names, quotes, or counts an unknown member, so discarded text cannot reach an error body or a log line.",
"A raw input bound rejection names the bound that was exceeded and never the body."
]
},
"rawInputBounds": {
"description": "Denial-of-service bounds applied to the raw body after the version gate and before normalization. They are deliberately far above what a v1 snapshot needs so that additive growth never trips them. A violation is rejected without naming or echoing the offending value.",
"maxBodyBytes": 8192,
"maxDepth": 8,
"maxTopLevelMembers": 64,
"maxRawCoarseFlagItems": 64,
"reason": "INVENTORY_FIELD_INVALID",
"httpStatus": 400
},
"crossFieldInvariants": [
{
"name": "usedNeverExceedsTotal",
"expression": "capacityUsedBytes <= capacityTotalBytes",
"reason": "INVENTORY_FIELD_INVALID"
}
],
"forbiddenFieldClasses": {
"description": "L0 inventory cannot express any of these. The frozen schema sets additionalProperties false at every level and every string member is an enum or a numeric-component pattern, so none of these sample values can be carried by any member, known or unknown, that survives normalization.",
"classes": [
{ "class": "bucket", "samples": ["customer-backups", "prod-media-eu"] },
{ "class": "object", "samples": ["invoices/2026/08/inv-1.pdf", "db-dump.sql.gz"] },
{ "class": "path", "samples": ["/var/lib/rustfs/data/disk1", "C:\\rustfs\\data"] },
{ "class": "endpoint", "samples": ["https://rustfs.internal:9000", "10.4.2.17:9000", "node-3.storage.example.com"] },
{ "class": "configuration", "samples": ["RUSTFS_ERASURE_SET_SIZE=12", "{\"tls\":{\"minVersion\":\"1.3\"}}"] },
{ "class": "credential", "samples": ["AKIAIOSFODNN7EXAMPLE", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "Bearer eyJhbGciOiJIUzI1NiJ9.e30.c2ln", "-----BEGIN PRIVATE KEY-----"] },
{ "class": "identifier", "samples": ["node-3.storage.example.com", "aa:bb:cc:dd:ee:ff", "acme-corp"] }
]
}
}
@@ -0,0 +1,253 @@
{
"protocolVersion": "v1",
"fixtureSet": "inventory",
"fixture": "old-agent-vectors",
"description": "Release skew in both directions, plus the version gate. The version rule is not restated here: it is the one frozen in protocol/agent/v1/fixtures/version/field-registry.json, and the inventory field registry repeats its pattern and supported majors so the two can be compared. An unsupported major fails closed before any inventory member is read, so nothing is normalized, validated, hashed, stored, or echoed.",
"versionRule": {
"inheritedFrom": "protocol/agent/v1/fixtures/version/field-registry.json",
"protocolVersionPattern": "^v[1-9][0-9]{0,3}$",
"supportedMajorVersions": [1],
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400
},
"baseContentHash": "1d6f2b1d767df63fbf72d6f7abcc98d5c47ed6783739e449f898ea5898e5e356",
"vectors": [
{
"name": "old agent omits every optional member",
"category": "old-agent",
"direction": "old-agent-to-new-connect",
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.0.0",
"nodeCount": 4,
"driveCount": 16,
"capacityTotalBytes": 549755813888,
"capacityUsedBytes": 137438953472
},
"expected": {
"decision": "ACCEPT",
"reason": null,
"httpStatus": null,
"retained": [
"protocolVersion",
"rustfsVersion",
"nodeCount",
"driveCount",
"capacityTotalBytes",
"capacityUsedBytes"
],
"discarded": [],
"defaultsApplied": { "osVersion": null, "coarseFlags": [] },
"canonicalJson": "{\"capacityTotalBytes\":549755813888,\"capacityUsedBytes\":137438953472,\"coarseFlags\":[],\"driveCount\":16,\"nodeCount\":4,\"osVersion\":null,\"rustfsVersion\":\"1.0.0\"}",
"contentHash": "1d6f2b1d767df63fbf72d6f7abcc98d5c47ed6783739e449f898ea5898e5e356",
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "newer agent sends the documented defaults explicitly",
"category": "old-agent",
"direction": "new-agent-to-old-connect",
"sameContentHashAs": "old agent omits every optional member",
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.0.0",
"osVersion": null,
"nodeCount": 4,
"driveCount": 16,
"capacityTotalBytes": 549755813888,
"capacityUsedBytes": 137438953472,
"coarseFlags": []
},
"expected": {
"decision": "ACCEPT",
"reason": null,
"httpStatus": null,
"retained": [
"protocolVersion",
"rustfsVersion",
"osVersion",
"nodeCount",
"driveCount",
"capacityTotalBytes",
"capacityUsedBytes",
"coarseFlags"
],
"discarded": [],
"defaultsApplied": {},
"canonicalJson": "{\"capacityTotalBytes\":549755813888,\"capacityUsedBytes\":137438953472,\"coarseFlags\":[],\"driveCount\":16,\"nodeCount\":4,\"osVersion\":null,\"rustfsVersion\":\"1.0.0\"}",
"contentHash": "1d6f2b1d767df63fbf72d6f7abcc98d5c47ed6783739e449f898ea5898e5e356",
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "newer agent adds a member this Connect does not know",
"category": "old-agent",
"direction": "new-agent-to-old-connect",
"sameContentHashAs": "old agent omits every optional member",
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.0.0",
"nodeCount": 4,
"driveCount": 16,
"capacityTotalBytes": 549755813888,
"capacityUsedBytes": 137438953472,
"poolCount": 2
},
"expected": {
"decision": "ACCEPT",
"reason": null,
"httpStatus": null,
"retained": [
"protocolVersion",
"rustfsVersion",
"nodeCount",
"driveCount",
"capacityTotalBytes",
"capacityUsedBytes"
],
"discarded": ["poolCount"],
"defaultsApplied": { "osVersion": null, "coarseFlags": [] },
"canonicalJson": "{\"capacityTotalBytes\":549755813888,\"capacityUsedBytes\":137438953472,\"coarseFlags\":[],\"driveCount\":16,\"nodeCount\":4,\"osVersion\":null,\"rustfsVersion\":\"1.0.0\"}",
"contentHash": "1d6f2b1d767df63fbf72d6f7abcc98d5c47ed6783739e449f898ea5898e5e356",
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "next major version from a future agent",
"category": "old-agent",
"direction": "new-agent-to-old-connect",
"input": {
"protocolVersion": "v2",
"rustfsVersion": "2.0.0",
"nodeCount": 4,
"driveCount": 16,
"capacityTotalBytes": 549755813888,
"capacityUsedBytes": 137438953472
},
"expected": {
"decision": "REJECT",
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400,
"retained": [],
"discarded": [],
"defaultsApplied": {},
"canonicalJson": null,
"contentHash": null,
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "far future major version",
"category": "old-agent",
"direction": "new-agent-to-old-connect",
"input": {
"protocolVersion": "v9999",
"rustfsVersion": "9999.0.0",
"nodeCount": 4,
"driveCount": 16,
"capacityTotalBytes": 549755813888,
"capacityUsedBytes": 137438953472
},
"expected": {
"decision": "REJECT",
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400,
"retained": [],
"discarded": [],
"defaultsApplied": {},
"canonicalJson": null,
"contentHash": null,
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "a secret-like unknown member does not rescue an unsupported major version",
"category": "old-agent",
"direction": "new-agent-to-old-connect",
"input": {
"protocolVersion": "v2",
"compatibilityShim": "v1",
"secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"rustfsVersion": "2.0.0",
"nodeCount": 4,
"driveCount": 16,
"capacityTotalBytes": 549755813888,
"capacityUsedBytes": 137438953472
},
"expected": {
"decision": "REJECT",
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400,
"retained": [],
"discarded": [],
"defaultsApplied": {},
"canonicalJson": null,
"contentHash": null,
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "an agent that omits the protocol version",
"category": "old-agent",
"direction": "old-agent-to-new-connect",
"input": {
"rustfsVersion": "1.0.0",
"nodeCount": 4,
"driveCount": 16,
"capacityTotalBytes": 549755813888,
"capacityUsedBytes": 137438953472
},
"expected": {
"decision": "REJECT",
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400,
"retained": [],
"discarded": [],
"defaultsApplied": {},
"canonicalJson": null,
"contentHash": null,
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "a dotted protocol version is not a major version",
"category": "old-agent",
"direction": "old-agent-to-new-connect",
"input": {
"protocolVersion": "v1.2",
"rustfsVersion": "1.0.0",
"nodeCount": 4,
"driveCount": 16,
"capacityTotalBytes": 549755813888,
"capacityUsedBytes": 137438953472
},
"expected": {
"decision": "REJECT",
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400,
"retained": [],
"discarded": [],
"defaultsApplied": {},
"canonicalJson": null,
"contentHash": null,
"echoedBack": [],
"stored": [],
"logged": []
}
}
]
}
@@ -0,0 +1,329 @@
{
"protocolVersion": "v1",
"fixtureSet": "inventory",
"fixture": "reject-vectors",
"description": "Snapshots the frozen schema and its cross-field invariant refuse. Every vector is rejected after normalization, so nothing here is stored, echoed, or hashed. violation names the member and the schema keyword that must fire; a rejection that fires on a different member is as much a regression as one that does not fire at all. The negative category covers malformed, missing, negative, and mistyped values; the overflow category covers values above a frozen maximum.",
"reason": "INVENTORY_FIELD_INVALID",
"status": "INVALID_ARGUMENT",
"httpStatus": 400,
"vectors": [
{
"name": "a node count of zero",
"category": "negative",
"violation": { "member": "nodeCount", "rule": "minimum" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"nodeCount": 0,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416
}
},
{
"name": "a negative node count",
"category": "negative",
"violation": { "member": "nodeCount", "rule": "minimum" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"nodeCount": -1,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416
}
},
{
"name": "a negative drive count",
"category": "negative",
"violation": { "member": "driveCount", "rule": "minimum" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"nodeCount": 8,
"driveCount": -1,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416
}
},
{
"name": "a negative used capacity",
"category": "negative",
"violation": { "member": "capacityUsedBytes", "rule": "minimum" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": -1
}
},
{
"name": "a negative capacity on both members",
"category": "negative",
"violation": { "member": "capacityTotalBytes", "rule": "minimum" },
"note": "A negative total with a non-negative used member would break the cross-field invariant as well, which would make it ambiguous which rule fired. Both members are negative so the minimum keyword is the only thing this vector can trip.",
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": -1,
"capacityUsedBytes": -1
}
},
{
"name": "a negative operating system major version",
"category": "negative",
"violation": { "member": "osVersion.major", "rule": "minimum" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"osVersion": { "family": "linux", "major": -1, "minor": 8 },
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416
}
},
{
"name": "a missing RustFS version",
"category": "negative",
"violation": { "member": "rustfsVersion", "rule": "required" },
"input": {
"protocolVersion": "v1",
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416
}
},
{
"name": "a missing used capacity",
"category": "negative",
"violation": { "member": "capacityUsedBytes", "rule": "required" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776
}
},
{
"name": "a two component RustFS version",
"category": "negative",
"violation": { "member": "rustfsVersion", "rule": "pattern" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4",
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416
}
},
{
"name": "a RustFS version carrying pre-release and build metadata",
"category": "negative",
"violation": { "member": "rustfsVersion", "rule": "pattern" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2-rc.1+9f2c1a",
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416
}
},
{
"name": "a RustFS version component with a leading zero",
"category": "negative",
"violation": { "member": "rustfsVersion", "rule": "pattern" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "01.4.2",
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416
}
},
{
"name": "an operating system family outside the closed vocabulary",
"category": "negative",
"violation": { "member": "osVersion.family", "rule": "enum" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"osVersion": { "family": "ubuntu", "major": 24, "minor": 4 },
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416
}
},
{
"name": "an operating system version missing its minor component",
"category": "negative",
"violation": { "member": "osVersion.minor", "rule": "required" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"osVersion": { "family": "linux", "major": 6 },
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416
}
},
{
"name": "used capacity above total capacity",
"category": "negative",
"violation": { "member": "capacityUsedBytes", "rule": "usedNeverExceedsTotal" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 1099511627777
}
},
{
"name": "a node count sent as a string",
"category": "negative",
"violation": { "member": "nodeCount", "rule": "type" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"nodeCount": "8",
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416
}
},
{
"name": "a fractional capacity",
"category": "negative",
"violation": { "member": "capacityTotalBytes", "rule": "type" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776.5,
"capacityUsedBytes": 412316860416
}
},
{
"name": "coarse flags sent as a string",
"category": "negative",
"violation": { "member": "coarseFlags", "rule": "type" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416,
"coarseFlags": "cluster.degraded"
}
},
{
"name": "an operating system version sent as a string",
"category": "negative",
"violation": { "member": "osVersion", "rule": "type" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"osVersion": "linux 6.8",
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416
}
},
{
"name": "a node count above the frozen maximum",
"category": "overflow",
"violation": { "member": "nodeCount", "rule": "maximum" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"nodeCount": 4097,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416
}
},
{
"name": "a drive count above the frozen maximum",
"category": "overflow",
"violation": { "member": "driveCount", "rule": "maximum" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"nodeCount": 8,
"driveCount": 1048577,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416
}
},
{
"name": "a total capacity one byte above the exactly representable maximum",
"category": "overflow",
"violation": { "member": "capacityTotalBytes", "rule": "maximum" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 9007199254740992,
"capacityUsedBytes": 412316860416
}
},
{
"name": "both capacities one byte above the exactly representable maximum",
"category": "overflow",
"violation": { "member": "capacityUsedBytes", "rule": "maximum" },
"note": "The used member carries the same cap as the total member. Used capacity can only exceed the cap when total capacity does too, so this vector raises both and asserts the used member is bounded in its own right.",
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 9007199254740992,
"capacityUsedBytes": 9007199254740992
}
},
{
"name": "a RustFS version component above the frozen maximum",
"category": "overflow",
"violation": { "member": "rustfsVersion", "rule": "pattern" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "10000.0.0",
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416
}
},
{
"name": "an operating system major version above the frozen maximum",
"category": "overflow",
"violation": { "member": "osVersion.major", "rule": "maximum" },
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"osVersion": { "family": "linux", "major": 10000, "minor": 0 },
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416
}
}
]
}
@@ -0,0 +1,219 @@
{
"protocolVersion": "v1",
"fixtureSet": "inventory",
"fixture": "secret-like-vectors",
"description": "An agent, a proxy, or an attacker attaches credential-shaped and customer-identifying members to an otherwise valid snapshot. Every one is an unknown optional member, so the frozen rule applies unchanged: accepted, discarded, never persisted, never echoed back, and never logged in raw form. Rejecting them instead would break additive compatibility and would put the secret text into an error message, so discarding is the safer and the frozen behaviour. Each vector carries the same content hash as the valid fixture's fully populated snapshot, which proves nothing here reaches storage. The literal values below are non-functional examples, not real credentials.",
"baseVector": "fully populated snapshot",
"baseContentHash": "e1cba9f30a26b94bb289a8a4b630a704dafaeb4960bffd38cef98e3d2581518f",
"errorMessagesNameUnknownMembers": false,
"vectors": [
{
"name": "S3 access key and secret key attached to the snapshot",
"category": "secret-like",
"sameContentHashAs": "fully populated snapshot",
"secretLikeMembers": ["accessKeyId", "secretAccessKey", "sessionToken"],
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"osVersion": { "family": "linux", "major": 6, "minor": 8 },
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416,
"coarseFlags": ["cluster.degraded", "drive.offline"],
"accessKeyId": "AKIAIOSFODNN7EXAMPLE",
"secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"sessionToken": "FwoGZXIvYXdzEExampleSessionTokenValue"
},
"expected": {
"decision": "ACCEPT",
"reason": null,
"httpStatus": null,
"retained": [
"protocolVersion",
"rustfsVersion",
"osVersion",
"nodeCount",
"driveCount",
"capacityTotalBytes",
"capacityUsedBytes",
"coarseFlags"
],
"discarded": ["accessKeyId", "secretAccessKey", "sessionToken"],
"defaultsApplied": {},
"canonicalJson": "{\"capacityTotalBytes\":1099511627776,\"capacityUsedBytes\":412316860416,\"coarseFlags\":[\"cluster.degraded\",\"drive.offline\"],\"driveCount\":96,\"nodeCount\":8,\"osVersion\":{\"family\":\"linux\",\"major\":6,\"minor\":8},\"rustfsVersion\":\"1.4.2\"}",
"contentHash": "e1cba9f30a26b94bb289a8a4b630a704dafaeb4960bffd38cef98e3d2581518f",
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "bearer token and private key material attached to the snapshot",
"category": "secret-like",
"sameContentHashAs": "fully populated snapshot",
"secretLikeMembers": ["authorization", "devicePrivateKeyPem", "kmsMasterKey"],
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"osVersion": { "family": "linux", "major": 6, "minor": 8 },
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416,
"coarseFlags": ["cluster.degraded", "drive.offline"],
"authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.e30.c2lnbmF0dXJl",
"devicePrivateKeyPem": "-----BEGIN PRIVATE KEY-----\nMEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCBleGFtcGxl\n-----END PRIVATE KEY-----",
"kmsMasterKey": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
},
"expected": {
"decision": "ACCEPT",
"reason": null,
"httpStatus": null,
"retained": [
"protocolVersion",
"rustfsVersion",
"osVersion",
"nodeCount",
"driveCount",
"capacityTotalBytes",
"capacityUsedBytes",
"coarseFlags"
],
"discarded": ["authorization", "devicePrivateKeyPem", "kmsMasterKey"],
"defaultsApplied": {},
"canonicalJson": "{\"capacityTotalBytes\":1099511627776,\"capacityUsedBytes\":412316860416,\"coarseFlags\":[\"cluster.degraded\",\"drive.offline\"],\"driveCount\":96,\"nodeCount\":8,\"osVersion\":{\"family\":\"linux\",\"major\":6,\"minor\":8},\"rustfsVersion\":\"1.4.2\"}",
"contentHash": "e1cba9f30a26b94bb289a8a4b630a704dafaeb4960bffd38cef98e3d2581518f",
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "bucket object path and endpoint members attached to the snapshot",
"category": "secret-like",
"sameContentHashAs": "fully populated snapshot",
"secretLikeMembers": ["buckets", "largestObjectKey", "dataPaths", "endpoint"],
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"osVersion": { "family": "linux", "major": 6, "minor": 8 },
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416,
"coarseFlags": ["cluster.degraded", "drive.offline"],
"buckets": ["customer-backups", "prod-media-eu"],
"largestObjectKey": "invoices/2026/08/inv-1.pdf",
"dataPaths": ["/var/lib/rustfs/data/disk1", "C:\\rustfs\\data"],
"endpoint": "https://rustfs.internal:9000"
},
"expected": {
"decision": "ACCEPT",
"reason": null,
"httpStatus": null,
"retained": [
"protocolVersion",
"rustfsVersion",
"osVersion",
"nodeCount",
"driveCount",
"capacityTotalBytes",
"capacityUsedBytes",
"coarseFlags"
],
"discarded": ["buckets", "dataPaths", "endpoint", "largestObjectKey"],
"defaultsApplied": {},
"canonicalJson": "{\"capacityTotalBytes\":1099511627776,\"capacityUsedBytes\":412316860416,\"coarseFlags\":[\"cluster.degraded\",\"drive.offline\"],\"driveCount\":96,\"nodeCount\":8,\"osVersion\":{\"family\":\"linux\",\"major\":6,\"minor\":8},\"rustfsVersion\":\"1.4.2\"}",
"contentHash": "e1cba9f30a26b94bb289a8a4b630a704dafaeb4960bffd38cef98e3d2581518f",
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "redacted configuration and customer identifiers attached to the snapshot",
"category": "secret-like",
"sameContentHashAs": "fully populated snapshot",
"secretLikeMembers": ["configuration", "customerName", "nodeHostnames"],
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"osVersion": { "family": "linux", "major": 6, "minor": 8 },
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416,
"coarseFlags": ["cluster.degraded", "drive.offline"],
"configuration": { "tls": { "minVersion": "1.3" }, "erasure": "RUSTFS_ERASURE_SET_SIZE=12" },
"customerName": "acme-corp",
"nodeHostnames": ["node-3.storage.example.com", "10.4.2.17"]
},
"expected": {
"decision": "ACCEPT",
"reason": null,
"httpStatus": null,
"retained": [
"protocolVersion",
"rustfsVersion",
"osVersion",
"nodeCount",
"driveCount",
"capacityTotalBytes",
"capacityUsedBytes",
"coarseFlags"
],
"discarded": ["configuration", "customerName", "nodeHostnames"],
"defaultsApplied": {},
"canonicalJson": "{\"capacityTotalBytes\":1099511627776,\"capacityUsedBytes\":412316860416,\"coarseFlags\":[\"cluster.degraded\",\"drive.offline\"],\"driveCount\":96,\"nodeCount\":8,\"osVersion\":{\"family\":\"linux\",\"major\":6,\"minor\":8},\"rustfsVersion\":\"1.4.2\"}",
"contentHash": "e1cba9f30a26b94bb289a8a4b630a704dafaeb4960bffd38cef98e3d2581518f",
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "secret material hidden inside osVersion and inside a coarse flag token",
"category": "secret-like",
"sameContentHashAs": "fully populated snapshot",
"secretLikeMembers": ["osVersion.licenseKey", "coarseFlags[akiaiosfodnn7example]"],
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"osVersion": {
"family": "linux",
"major": 6,
"minor": 8,
"licenseKey": "RUSTFS-PROD-4821-9930-ACME"
},
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416,
"coarseFlags": ["cluster.degraded", "drive.offline", "akiaiosfodnn7example"]
},
"expected": {
"decision": "ACCEPT",
"reason": null,
"httpStatus": null,
"retained": [
"protocolVersion",
"rustfsVersion",
"osVersion",
"nodeCount",
"driveCount",
"capacityTotalBytes",
"capacityUsedBytes",
"coarseFlags"
],
"discarded": ["coarseFlags[akiaiosfodnn7example]", "osVersion.licenseKey"],
"defaultsApplied": {},
"canonicalJson": "{\"capacityTotalBytes\":1099511627776,\"capacityUsedBytes\":412316860416,\"coarseFlags\":[\"cluster.degraded\",\"drive.offline\"],\"driveCount\":96,\"nodeCount\":8,\"osVersion\":{\"family\":\"linux\",\"major\":6,\"minor\":8},\"rustfsVersion\":\"1.4.2\"}",
"contentHash": "e1cba9f30a26b94bb289a8a4b630a704dafaeb4960bffd38cef98e3d2581518f",
"echoedBack": [],
"stored": [],
"logged": []
}
}
]
}
@@ -0,0 +1,230 @@
{
"protocolVersion": "v1",
"fixtureSet": "inventory",
"fixture": "unknown-field-vectors",
"description": "Unknown optional members and unknown coarse flag tokens. Every one is accepted, discarded before validation, never persisted, never echoed back, and never logged in raw form. Every vector here carries the same content hash as the valid fixture's fully populated snapshot, which is the executable statement that an unknown member cannot influence stored inventory. The discarded list is bookkeeping: its membership is the contract, not its order.",
"baseVector": "fully populated snapshot",
"baseContentHash": "e1cba9f30a26b94bb289a8a4b630a704dafaeb4960bffd38cef98e3d2581518f",
"vectors": [
{
"name": "one unknown optional member from a newer agent",
"category": "unknown-field",
"sameContentHashAs": "fully populated snapshot",
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"osVersion": { "family": "linux", "major": 6, "minor": 8 },
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416,
"coarseFlags": ["cluster.degraded", "drive.offline"],
"telemetryProfile": "extended"
},
"expected": {
"decision": "ACCEPT",
"reason": null,
"httpStatus": null,
"retained": [
"protocolVersion",
"rustfsVersion",
"osVersion",
"nodeCount",
"driveCount",
"capacityTotalBytes",
"capacityUsedBytes",
"coarseFlags"
],
"discarded": ["telemetryProfile"],
"defaultsApplied": {},
"canonicalJson": "{\"capacityTotalBytes\":1099511627776,\"capacityUsedBytes\":412316860416,\"coarseFlags\":[\"cluster.degraded\",\"drive.offline\"],\"driveCount\":96,\"nodeCount\":8,\"osVersion\":{\"family\":\"linux\",\"major\":6,\"minor\":8},\"rustfsVersion\":\"1.4.2\"}",
"contentHash": "e1cba9f30a26b94bb289a8a4b630a704dafaeb4960bffd38cef98e3d2581518f",
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "unknown optional members of every JSON type at once",
"category": "unknown-field",
"sameContentHashAs": "fully populated snapshot",
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"osVersion": { "family": "linux", "major": 6, "minor": 8 },
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416,
"coarseFlags": ["cluster.degraded", "drive.offline"],
"erasureSetSize": 12,
"experimentalFlags": { "fastInventory": true },
"regionHints": ["eu-west", "us-east"],
"supersededBy": null,
"telemetryProfile": "extended"
},
"expected": {
"decision": "ACCEPT",
"reason": null,
"httpStatus": null,
"retained": [
"protocolVersion",
"rustfsVersion",
"osVersion",
"nodeCount",
"driveCount",
"capacityTotalBytes",
"capacityUsedBytes",
"coarseFlags"
],
"discarded": [
"erasureSetSize",
"experimentalFlags",
"regionHints",
"supersededBy",
"telemetryProfile"
],
"defaultsApplied": {},
"canonicalJson": "{\"capacityTotalBytes\":1099511627776,\"capacityUsedBytes\":412316860416,\"coarseFlags\":[\"cluster.degraded\",\"drive.offline\"],\"driveCount\":96,\"nodeCount\":8,\"osVersion\":{\"family\":\"linux\",\"major\":6,\"minor\":8},\"rustfsVersion\":\"1.4.2\"}",
"contentHash": "e1cba9f30a26b94bb289a8a4b630a704dafaeb4960bffd38cef98e3d2581518f",
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "unknown member nested inside osVersion",
"category": "unknown-field",
"sameContentHashAs": "fully populated snapshot",
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"osVersion": {
"family": "linux",
"major": 6,
"minor": 8,
"distribution": "debian",
"kernelRelease": "6.8.0-41-generic"
},
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416,
"coarseFlags": ["cluster.degraded", "drive.offline"]
},
"expected": {
"decision": "ACCEPT",
"reason": null,
"httpStatus": null,
"retained": [
"protocolVersion",
"rustfsVersion",
"osVersion",
"nodeCount",
"driveCount",
"capacityTotalBytes",
"capacityUsedBytes",
"coarseFlags"
],
"discarded": ["osVersion.distribution", "osVersion.kernelRelease"],
"defaultsApplied": {},
"canonicalJson": "{\"capacityTotalBytes\":1099511627776,\"capacityUsedBytes\":412316860416,\"coarseFlags\":[\"cluster.degraded\",\"drive.offline\"],\"driveCount\":96,\"nodeCount\":8,\"osVersion\":{\"family\":\"linux\",\"major\":6,\"minor\":8},\"rustfsVersion\":\"1.4.2\"}",
"contentHash": "e1cba9f30a26b94bb289a8a4b630a704dafaeb4960bffd38cef98e3d2581518f",
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "coarse flag tokens this Connect does not know",
"category": "unknown-field",
"sameContentHashAs": "fully populated snapshot",
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"osVersion": { "family": "linux", "major": 6, "minor": 8 },
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416,
"coarseFlags": [
"cluster.degraded",
"drive.offline",
"rebalance.pending",
"tier.transition.stalled"
]
},
"expected": {
"decision": "ACCEPT",
"reason": null,
"httpStatus": null,
"retained": [
"protocolVersion",
"rustfsVersion",
"osVersion",
"nodeCount",
"driveCount",
"capacityTotalBytes",
"capacityUsedBytes",
"coarseFlags"
],
"discarded": [
"coarseFlags[rebalance.pending]",
"coarseFlags[tier.transition.stalled]"
],
"defaultsApplied": {},
"canonicalJson": "{\"capacityTotalBytes\":1099511627776,\"capacityUsedBytes\":412316860416,\"coarseFlags\":[\"cluster.degraded\",\"drive.offline\"],\"driveCount\":96,\"nodeCount\":8,\"osVersion\":{\"family\":\"linux\",\"major\":6,\"minor\":8},\"rustfsVersion\":\"1.4.2\"}",
"contentHash": "e1cba9f30a26b94bb289a8a4b630a704dafaeb4960bffd38cef98e3d2581518f",
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "near-miss member names are unknown because matching is exact and case sensitive",
"category": "unknown-field",
"sameContentHashAs": "fully populated snapshot",
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"osVersion": { "family": "linux", "major": 6, "minor": 8 },
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416,
"coarseFlags": ["cluster.degraded", "drive.offline"],
"RustfsVersion": "1.4.2",
"capacityFreeBytes": 687194767360,
"capacity_used_bytes": 412316860416,
"nodeCounts": 8
},
"expected": {
"decision": "ACCEPT",
"reason": null,
"httpStatus": null,
"retained": [
"protocolVersion",
"rustfsVersion",
"osVersion",
"nodeCount",
"driveCount",
"capacityTotalBytes",
"capacityUsedBytes",
"coarseFlags"
],
"discarded": [
"RustfsVersion",
"capacityFreeBytes",
"capacity_used_bytes",
"nodeCounts"
],
"defaultsApplied": {},
"canonicalJson": "{\"capacityTotalBytes\":1099511627776,\"capacityUsedBytes\":412316860416,\"coarseFlags\":[\"cluster.degraded\",\"drive.offline\"],\"driveCount\":96,\"nodeCount\":8,\"osVersion\":{\"family\":\"linux\",\"major\":6,\"minor\":8},\"rustfsVersion\":\"1.4.2\"}",
"contentHash": "e1cba9f30a26b94bb289a8a4b630a704dafaeb4960bffd38cef98e3d2581518f",
"echoedBack": [],
"stored": [],
"logged": []
}
}
]
}
@@ -0,0 +1,198 @@
{
"protocolVersion": "v1",
"fixtureSet": "inventory",
"fixture": "valid-vectors",
"description": "Accepted L0 inventory snapshots with their normalized form, canonical hash input, and content hash. canonicalJson is the exact byte string that is hashed after the domain prefix, so an independent implementation can diff its serialization before it ever compares a digest.",
"vectors": [
{
"name": "fully populated snapshot",
"category": "valid",
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"osVersion": { "family": "linux", "major": 6, "minor": 8 },
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416,
"coarseFlags": ["cluster.degraded", "drive.offline"]
},
"expected": {
"decision": "ACCEPT",
"reason": null,
"httpStatus": null,
"retained": [
"protocolVersion",
"rustfsVersion",
"osVersion",
"nodeCount",
"driveCount",
"capacityTotalBytes",
"capacityUsedBytes",
"coarseFlags"
],
"discarded": [],
"defaultsApplied": {},
"canonicalJson": "{\"capacityTotalBytes\":1099511627776,\"capacityUsedBytes\":412316860416,\"coarseFlags\":[\"cluster.degraded\",\"drive.offline\"],\"driveCount\":96,\"nodeCount\":8,\"osVersion\":{\"family\":\"linux\",\"major\":6,\"minor\":8},\"rustfsVersion\":\"1.4.2\"}",
"contentHash": "e1cba9f30a26b94bb289a8a4b630a704dafaeb4960bffd38cef98e3d2581518f",
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "unsorted duplicated and unknown coarse flags normalize to the same snapshot",
"category": "valid",
"sameContentHashAs": "fully populated snapshot",
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"osVersion": { "family": "linux", "major": 6, "minor": 8 },
"nodeCount": 8,
"driveCount": 96,
"capacityTotalBytes": 1099511627776,
"capacityUsedBytes": 412316860416,
"coarseFlags": ["drive.offline", "cluster.degraded", "drive.offline", "cluster.on.fire"]
},
"expected": {
"decision": "ACCEPT",
"reason": null,
"httpStatus": null,
"retained": [
"protocolVersion",
"rustfsVersion",
"osVersion",
"nodeCount",
"driveCount",
"capacityTotalBytes",
"capacityUsedBytes",
"coarseFlags"
],
"discarded": ["coarseFlags[cluster.on.fire]"],
"defaultsApplied": {},
"canonicalJson": "{\"capacityTotalBytes\":1099511627776,\"capacityUsedBytes\":412316860416,\"coarseFlags\":[\"cluster.degraded\",\"drive.offline\"],\"driveCount\":96,\"nodeCount\":8,\"osVersion\":{\"family\":\"linux\",\"major\":6,\"minor\":8},\"rustfsVersion\":\"1.4.2\"}",
"contentHash": "e1cba9f30a26b94bb289a8a4b630a704dafaeb4960bffd38cef98e3d2581518f",
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "every limit at its maximum",
"category": "valid",
"input": {
"protocolVersion": "v1",
"rustfsVersion": "9999.9999.9999",
"osVersion": { "family": "other", "major": 9999, "minor": 9999 },
"nodeCount": 4096,
"driveCount": 1048576,
"capacityTotalBytes": 9007199254740991,
"capacityUsedBytes": 9007199254740991,
"coarseFlags": [
"capacity.critical",
"capacity.warning",
"clock.skew",
"cluster.degraded",
"cluster.healing",
"cluster.readonly",
"drive.offline",
"node.offline"
]
},
"expected": {
"decision": "ACCEPT",
"reason": null,
"httpStatus": null,
"retained": [
"protocolVersion",
"rustfsVersion",
"osVersion",
"nodeCount",
"driveCount",
"capacityTotalBytes",
"capacityUsedBytes",
"coarseFlags"
],
"discarded": [],
"defaultsApplied": {},
"canonicalJson": "{\"capacityTotalBytes\":9007199254740991,\"capacityUsedBytes\":9007199254740991,\"coarseFlags\":[\"capacity.critical\",\"capacity.warning\",\"clock.skew\",\"cluster.degraded\",\"cluster.healing\",\"cluster.readonly\",\"drive.offline\",\"node.offline\"],\"driveCount\":1048576,\"nodeCount\":4096,\"osVersion\":{\"family\":\"other\",\"major\":9999,\"minor\":9999},\"rustfsVersion\":\"9999.9999.9999\"}",
"contentHash": "b69d51f898a53562a7057faa5bda1e21699ee6615517cd8b1455096f9171bce4",
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "every limit at its minimum",
"category": "valid",
"input": {
"protocolVersion": "v1",
"rustfsVersion": "0.0.0",
"osVersion": null,
"nodeCount": 1,
"driveCount": 0,
"capacityTotalBytes": 0,
"capacityUsedBytes": 0,
"coarseFlags": []
},
"expected": {
"decision": "ACCEPT",
"reason": null,
"httpStatus": null,
"retained": [
"protocolVersion",
"rustfsVersion",
"osVersion",
"nodeCount",
"driveCount",
"capacityTotalBytes",
"capacityUsedBytes",
"coarseFlags"
],
"discarded": [],
"defaultsApplied": {},
"canonicalJson": "{\"capacityTotalBytes\":0,\"capacityUsedBytes\":0,\"coarseFlags\":[],\"driveCount\":0,\"nodeCount\":1,\"osVersion\":null,\"rustfsVersion\":\"0.0.0\"}",
"contentHash": "08ebc03ee906c05d686ef32c0998154213f2811a2d07408a9ef7d0b205cc701b",
"echoedBack": [],
"stored": [],
"logged": []
}
},
{
"name": "a full cluster reports used equal to total",
"category": "valid",
"input": {
"protocolVersion": "v1",
"rustfsVersion": "1.4.2",
"osVersion": { "family": "freebsd", "major": 14, "minor": 1 },
"nodeCount": 4,
"driveCount": 32,
"capacityTotalBytes": 549755813888,
"capacityUsedBytes": 549755813888,
"coarseFlags": ["capacity.critical", "cluster.readonly"]
},
"expected": {
"decision": "ACCEPT",
"reason": null,
"httpStatus": null,
"retained": [
"protocolVersion",
"rustfsVersion",
"osVersion",
"nodeCount",
"driveCount",
"capacityTotalBytes",
"capacityUsedBytes",
"coarseFlags"
],
"discarded": [],
"defaultsApplied": {},
"canonicalJson": "{\"capacityTotalBytes\":549755813888,\"capacityUsedBytes\":549755813888,\"coarseFlags\":[\"capacity.critical\",\"cluster.readonly\"],\"driveCount\":32,\"nodeCount\":4,\"osVersion\":{\"family\":\"freebsd\",\"major\":14,\"minor\":1},\"rustfsVersion\":\"1.4.2\"}",
"contentHash": "0ab2308e84d2a9660650526edcd07795fe08a0fea047f16718846ce883a150b3",
"echoedBack": [],
"stored": [],
"logged": []
}
}
]
}
@@ -0,0 +1,5 @@
5133761d19d6a64c18b6b5f871d646f6a2da4ceccc998d3cf7e22f692ca2d925 accept-vectors.json
c7da10d173e7fafa112743d9a41e2bc94df58bf88a0542d80350b74da8f382a5 error-codes.json
e98cfbedfb385defdaa9d001c85fdebcf9df2b4d054930951ff59dfa1385e52f reject-vectors.json
69d43c8266d7bb29b4df7105c49250293943583f2202b93d922d9a924fca0c09 trust-chain.json
e60cfca04bf0ce2f69495c49a95e4cc42e92e8114f6ad43449084527b06a0939 trust-model.json
@@ -0,0 +1,129 @@
{
"protocolVersion": "v1",
"fixtureSet": "offline-enrollment",
"fixture": "accept-vectors",
"description": "Offline enrollment artifacts that verify. evaluationTime is the verifier clock the vector is evaluated at; the artifact bytes are frozen, so a window is a property of the evaluation and not of the bytes.",
"vectors": [
{
"name": "challenge signed by a chained signing key under the pinned root",
"artifact": "challenge",
"signerKey": "signing",
"evaluationTime": "2026-08-20T12:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50Q2hhbGxlbmdlLzEiLAogICAgInByb3RvY29sVmVyc2lvbiI6ICJ2MSIsCiAgICAiY2hhbGxlbmdlSWQiOiAiMDE5OGYzYTEtN2YwMC03ZDQwLWJlNTEtM2Y0YTViNmM3ZDgzIiwKICAgICJvcmdhbml6YXRpb25OYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwIiwKICAgICJjbHVzdGVyTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEiLAogICAgIm5vbmNlIjogIlRWeDRqS2FQZ1dQbGRUU203X1N6S1BsSDVIZmhrcHFxcjlieWVDalhkSFUiLAogICAgImlzc3VlZEF0IjogIjIwMjYtMDgtMTdUMDA6MDA6MDBaIiwKICAgICJleHBpcmVzQXQiOiAiMjAyNi0wOC0yNFQwMDowMDowMFoiLAogICAgImNvbm5lY3RLZXlJZCI6ICIwOGU3Mjk1YzhmOWQwNDNlMjJiMmI4MGZkYjE0ODBiMGJlYzA2MGRhY2JjZTdkZTlkZDJlM2Q1ODNmOTNkN2U4IiwKICAgICJ0cnVzdENoYWluIjogWwogICAgICAgIHsKICAgICAgICAgICAgImJ5dGVzIjogImV3b2dJQ0FnSW1admNtMWhkRlpsY25OcGIyNGlPaUFpY25WemRHWnpMbU52Ym01bFkzUXViMlptYkdsdVpTNTBjblZ6ZEV4cGJtc3ZNU0lzQ2lBZ0lDQWljSEp2ZEc5amIyeFdaWEp6YVc5dUlqb2dJbll4SWl3S0lDQWdJQ0p6WlhKcFlXd2lPaUFpWXpJMU9HRTJaR1JqT1RoaVlqVmhOV1U1TkRSbU5qSmxNRFkzT1dFM05HVWlMQW9nSUNBZ0luSnZiR1VpT2lBaWFXNTBaWEp0WldScFlYUmxJaXdLSUNBZ0lDSnBjM04xWlhKTFpYbEpaQ0k2SUNKa1pqSXlaVEk0TURZeE1USmtaV0ppWlRrMU16WTNNbUZoWm1FeE9EWmtOams1WVdZd1pUazNaR1F6Wm1ReVlqQTVabUU0TXpVNU1EQTFabVV6TkRobUlpd0tJQ0FnSUNKemRXSnFaV04wUzJWNVNXUWlPaUFpTURSbFltTTNOR1F6TURBeU1HWTNNamM1T0Roak9UWmpNbVptWWpJeU9ESXhPRGd6WldVMllqSmxOR014TVRkbU5tVTVOVGxrWW1RMk0yWXpNMlE0TnlJc0NpQWdJQ0FpYzNWaWFtVmpkRkIxWW14cFkwdGxlU0k2SUNKQ1JIZzFWbk5YU1ZwS1YzcERTMkZVTURoZmVGTklhR2RoTFdsek9UVmhXVTlvTUcxa1FUVTBOMll5UVd4R1ZFeG1lV1ZhYWpBeGVtVk9hV3ROZFdSalFXWk1WSGcwUkVoWVJHRkhjM0ZRUkZSWmRGWjZSRzhpTEFvZ0lDQWdJbTV2ZEVKbFptOXlaU0k2SUNJeU1ESTJMVEF4TFRBeFZEQXdPakF3T2pBd1dpSXNDaUFnSUNBaWJtOTBRV1owWlhJaU9pQWlNakF5Tnkwd01TMHdNVlF3TURvd01Eb3dNRm9pQ24wSyIsCiAgICAgICAgICAgICJzaWduYXR1cmUiOiB7CiAgICAgICAgICAgICAgICAiYWxnb3JpdGhtIjogIkVTMjU2IiwKICAgICAgICAgICAgICAgICJrZXlJZCI6ICJkZjIyZTI4MDYxMTJkZWJiZTk1MzY3MmFhZmExODZkNjk5YWYwZTk3ZGQzZmQyYjA5ZmE4MzU5MDA1ZmUzNDhmIiwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJsWEl4RG1rdlJYMkNLTF9OU01KX3ltLUhKWTQ4cW9VejRoM2JQUHgzM0lGRXYwRTFnblNPV2QxTTJYcHJtSVZSQ3Z4OHhONUNmWUowTDd3enNEQV9GUSIKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiYnl0ZXMiOiAiZXdvZ0lDQWdJbVp2Y20xaGRGWmxjbk5wYjI0aU9pQWljblZ6ZEdaekxtTnZibTVsWTNRdWIyWm1iR2x1WlM1MGNuVnpkRXhwYm1zdk1TSXNDaUFnSUNBaWNISnZkRzlqYjJ4V1pYSnphVzl1SWpvZ0luWXhJaXdLSUNBZ0lDSnpaWEpwWVd3aU9pQWlOMkptWm1ObU1EYzRaRGMyTkRnNU9XRTNNR1prWWpGaFpqbGlZbU5rTmpJaUxBb2dJQ0FnSW5KdmJHVWlPaUFpYzJsbmJtbHVaeUlzQ2lBZ0lDQWlhWE56ZFdWeVMyVjVTV1FpT2lBaU1EUmxZbU0zTkdRek1EQXlNR1kzTWpjNU9EaGpPVFpqTW1abVlqSXlPREl4T0RnelpXVTJZakpsTkdNeE1UZG1ObVU1TlRsa1ltUTJNMll6TTJRNE55SXNDaUFnSUNBaWMzVmlhbVZqZEV0bGVVbGtJam9nSWpBNFpUY3lPVFZqT0dZNVpEQTBNMlV5TW1JeVlqZ3dabVJpTVRRNE1HSXdZbVZqTURZd1pHRmpZbU5sTjJSbE9XUmtNbVV6WkRVNE0yWTVNMlEzWlRnaUxBb2dJQ0FnSW5OMVltcGxZM1JRZFdKc2FXTkxaWGtpT2lBaVFrZFdTVlUzZVRWU2FXZ3lhRWs0TFZCZmFXd3RSM1ZJVm5Sa1VVeEdlVEpFYUZGbFJsVTNjV2g1WW1KME1qa3hlbE5DYTE5MWVGbEtTazVoUWtSa1pEbDNUVU0wUkdac1dUVlJRbEJVT0cxU05qZEZaMk5CSWl3S0lDQWdJQ0p1YjNSQ1pXWnZjbVVpT2lBaU1qQXlOaTB3T0Mwd01WUXdNRG93TURvd01Gb2lMQW9nSUNBZ0ltNXZkRUZtZEdWeUlqb2dJakl3TWpZdE1EZ3RNekZVTURBNk1EQTZNREJhSWdwOUNnPT0iLAogICAgICAgICAgICAic2lnbmF0dXJlIjogewogICAgICAgICAgICAgICAgImFsZ29yaXRobSI6ICJFUzI1NiIsCiAgICAgICAgICAgICAgICAia2V5SWQiOiAiMDRlYmM3NGQzMDAyMGY3Mjc5ODhjOTZjMmZmYjIyODIxODgzZWU2YjJlNGMxMTdmNmU5NTlkYmQ2M2YzM2Q4NyIsCiAgICAgICAgICAgICAgICAidmFsdWUiOiAiamhrZFozeWpuOC1Od0JxWjZjRUtZVlNsYV9VblVmVFJWVmNxazZJSndUTWx3S1ZyWU8xWEZSVTA1bTBWc0VNMThYM1ppOE96bGxoSVAyUjlxWUMwa0EiCiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "08e7295c8f9d043e22b2b80fdb1480b0bec060dacbce7de9dd2e3d583f93d7e8",
"value": "jiDV4Wy81WwqQwlxVqF0eFTh9jEMnD3mkUWB5XqGmpkSw0WnTdAjuVH1wqRMeUEYkZLDTu7fB5YJRvBN2wrUtA"
}
},
"expected": {
"signatureVerifies": true,
"chainVerifies": true,
"rootPinned": true,
"withinWindow": true,
"accepted": true,
"reason": null
}
},
{
"name": "challenge evaluated 120 seconds before its issuedAt is inside the skew tolerance",
"artifact": "challenge",
"signerKey": "signing",
"evaluationTime": "2026-08-16T23:58:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50Q2hhbGxlbmdlLzEiLAogICAgInByb3RvY29sVmVyc2lvbiI6ICJ2MSIsCiAgICAiY2hhbGxlbmdlSWQiOiAiMDE5OGYzYTEtN2YwMC03ZDQwLWJlNTEtM2Y0YTViNmM3ZDgzIiwKICAgICJvcmdhbml6YXRpb25OYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwIiwKICAgICJjbHVzdGVyTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEiLAogICAgIm5vbmNlIjogIlRWeDRqS2FQZ1dQbGRUU203X1N6S1BsSDVIZmhrcHFxcjlieWVDalhkSFUiLAogICAgImlzc3VlZEF0IjogIjIwMjYtMDgtMTdUMDA6MDA6MDBaIiwKICAgICJleHBpcmVzQXQiOiAiMjAyNi0wOC0yNFQwMDowMDowMFoiLAogICAgImNvbm5lY3RLZXlJZCI6ICIwOGU3Mjk1YzhmOWQwNDNlMjJiMmI4MGZkYjE0ODBiMGJlYzA2MGRhY2JjZTdkZTlkZDJlM2Q1ODNmOTNkN2U4IiwKICAgICJ0cnVzdENoYWluIjogWwogICAgICAgIHsKICAgICAgICAgICAgImJ5dGVzIjogImV3b2dJQ0FnSW1admNtMWhkRlpsY25OcGIyNGlPaUFpY25WemRHWnpMbU52Ym01bFkzUXViMlptYkdsdVpTNTBjblZ6ZEV4cGJtc3ZNU0lzQ2lBZ0lDQWljSEp2ZEc5amIyeFdaWEp6YVc5dUlqb2dJbll4SWl3S0lDQWdJQ0p6WlhKcFlXd2lPaUFpWXpJMU9HRTJaR1JqT1RoaVlqVmhOV1U1TkRSbU5qSmxNRFkzT1dFM05HVWlMQW9nSUNBZ0luSnZiR1VpT2lBaWFXNTBaWEp0WldScFlYUmxJaXdLSUNBZ0lDSnBjM04xWlhKTFpYbEpaQ0k2SUNKa1pqSXlaVEk0TURZeE1USmtaV0ppWlRrMU16WTNNbUZoWm1FeE9EWmtOams1WVdZd1pUazNaR1F6Wm1ReVlqQTVabUU0TXpVNU1EQTFabVV6TkRobUlpd0tJQ0FnSUNKemRXSnFaV04wUzJWNVNXUWlPaUFpTURSbFltTTNOR1F6TURBeU1HWTNNamM1T0Roak9UWmpNbVptWWpJeU9ESXhPRGd6WldVMllqSmxOR014TVRkbU5tVTVOVGxrWW1RMk0yWXpNMlE0TnlJc0NpQWdJQ0FpYzNWaWFtVmpkRkIxWW14cFkwdGxlU0k2SUNKQ1JIZzFWbk5YU1ZwS1YzcERTMkZVTURoZmVGTklhR2RoTFdsek9UVmhXVTlvTUcxa1FUVTBOMll5UVd4R1ZFeG1lV1ZhYWpBeGVtVk9hV3ROZFdSalFXWk1WSGcwUkVoWVJHRkhjM0ZRUkZSWmRGWjZSRzhpTEFvZ0lDQWdJbTV2ZEVKbFptOXlaU0k2SUNJeU1ESTJMVEF4TFRBeFZEQXdPakF3T2pBd1dpSXNDaUFnSUNBaWJtOTBRV1owWlhJaU9pQWlNakF5Tnkwd01TMHdNVlF3TURvd01Eb3dNRm9pQ24wSyIsCiAgICAgICAgICAgICJzaWduYXR1cmUiOiB7CiAgICAgICAgICAgICAgICAiYWxnb3JpdGhtIjogIkVTMjU2IiwKICAgICAgICAgICAgICAgICJrZXlJZCI6ICJkZjIyZTI4MDYxMTJkZWJiZTk1MzY3MmFhZmExODZkNjk5YWYwZTk3ZGQzZmQyYjA5ZmE4MzU5MDA1ZmUzNDhmIiwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJsWEl4RG1rdlJYMkNLTF9OU01KX3ltLUhKWTQ4cW9VejRoM2JQUHgzM0lGRXYwRTFnblNPV2QxTTJYcHJtSVZSQ3Z4OHhONUNmWUowTDd3enNEQV9GUSIKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiYnl0ZXMiOiAiZXdvZ0lDQWdJbVp2Y20xaGRGWmxjbk5wYjI0aU9pQWljblZ6ZEdaekxtTnZibTVsWTNRdWIyWm1iR2x1WlM1MGNuVnpkRXhwYm1zdk1TSXNDaUFnSUNBaWNISnZkRzlqYjJ4V1pYSnphVzl1SWpvZ0luWXhJaXdLSUNBZ0lDSnpaWEpwWVd3aU9pQWlOMkptWm1ObU1EYzRaRGMyTkRnNU9XRTNNR1prWWpGaFpqbGlZbU5rTmpJaUxBb2dJQ0FnSW5KdmJHVWlPaUFpYzJsbmJtbHVaeUlzQ2lBZ0lDQWlhWE56ZFdWeVMyVjVTV1FpT2lBaU1EUmxZbU0zTkdRek1EQXlNR1kzTWpjNU9EaGpPVFpqTW1abVlqSXlPREl4T0RnelpXVTJZakpsTkdNeE1UZG1ObVU1TlRsa1ltUTJNMll6TTJRNE55SXNDaUFnSUNBaWMzVmlhbVZqZEV0bGVVbGtJam9nSWpBNFpUY3lPVFZqT0dZNVpEQTBNMlV5TW1JeVlqZ3dabVJpTVRRNE1HSXdZbVZqTURZd1pHRmpZbU5sTjJSbE9XUmtNbVV6WkRVNE0yWTVNMlEzWlRnaUxBb2dJQ0FnSW5OMVltcGxZM1JRZFdKc2FXTkxaWGtpT2lBaVFrZFdTVlUzZVRWU2FXZ3lhRWs0TFZCZmFXd3RSM1ZJVm5Sa1VVeEdlVEpFYUZGbFJsVTNjV2g1WW1KME1qa3hlbE5DYTE5MWVGbEtTazVoUWtSa1pEbDNUVU0wUkdac1dUVlJRbEJVT0cxU05qZEZaMk5CSWl3S0lDQWdJQ0p1YjNSQ1pXWnZjbVVpT2lBaU1qQXlOaTB3T0Mwd01WUXdNRG93TURvd01Gb2lMQW9nSUNBZ0ltNXZkRUZtZEdWeUlqb2dJakl3TWpZdE1EZ3RNekZVTURBNk1EQTZNREJhSWdwOUNnPT0iLAogICAgICAgICAgICAic2lnbmF0dXJlIjogewogICAgICAgICAgICAgICAgImFsZ29yaXRobSI6ICJFUzI1NiIsCiAgICAgICAgICAgICAgICAia2V5SWQiOiAiMDRlYmM3NGQzMDAyMGY3Mjc5ODhjOTZjMmZmYjIyODIxODgzZWU2YjJlNGMxMTdmNmU5NTlkYmQ2M2YzM2Q4NyIsCiAgICAgICAgICAgICAgICAidmFsdWUiOiAiamhrZFozeWpuOC1Od0JxWjZjRUtZVlNsYV9VblVmVFJWVmNxazZJSndUTWx3S1ZyWU8xWEZSVTA1bTBWc0VNMThYM1ppOE96bGxoSVAyUjlxWUMwa0EiCiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "08e7295c8f9d043e22b2b80fdb1480b0bec060dacbce7de9dd2e3d583f93d7e8",
"value": "jiDV4Wy81WwqQwlxVqF0eFTh9jEMnD3mkUWB5XqGmpkSw0WnTdAjuVH1wqRMeUEYkZLDTu7fB5YJRvBN2wrUtA"
}
},
"expected": {
"signatureVerifies": true,
"chainVerifies": true,
"rootPinned": true,
"withinWindow": true,
"accepted": true,
"reason": null
}
},
{
"name": "challenge evaluated 300 seconds after its expiresAt is still inside the skew tolerance",
"artifact": "challenge",
"signerKey": "signing",
"evaluationTime": "2026-08-24T00:05:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50Q2hhbGxlbmdlLzEiLAogICAgInByb3RvY29sVmVyc2lvbiI6ICJ2MSIsCiAgICAiY2hhbGxlbmdlSWQiOiAiMDE5OGYzYTEtN2YwMC03ZDQwLWJlNTEtM2Y0YTViNmM3ZDgzIiwKICAgICJvcmdhbml6YXRpb25OYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwIiwKICAgICJjbHVzdGVyTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEiLAogICAgIm5vbmNlIjogIlRWeDRqS2FQZ1dQbGRUU203X1N6S1BsSDVIZmhrcHFxcjlieWVDalhkSFUiLAogICAgImlzc3VlZEF0IjogIjIwMjYtMDgtMTdUMDA6MDA6MDBaIiwKICAgICJleHBpcmVzQXQiOiAiMjAyNi0wOC0yNFQwMDowMDowMFoiLAogICAgImNvbm5lY3RLZXlJZCI6ICIwOGU3Mjk1YzhmOWQwNDNlMjJiMmI4MGZkYjE0ODBiMGJlYzA2MGRhY2JjZTdkZTlkZDJlM2Q1ODNmOTNkN2U4IiwKICAgICJ0cnVzdENoYWluIjogWwogICAgICAgIHsKICAgICAgICAgICAgImJ5dGVzIjogImV3b2dJQ0FnSW1admNtMWhkRlpsY25OcGIyNGlPaUFpY25WemRHWnpMbU52Ym01bFkzUXViMlptYkdsdVpTNTBjblZ6ZEV4cGJtc3ZNU0lzQ2lBZ0lDQWljSEp2ZEc5amIyeFdaWEp6YVc5dUlqb2dJbll4SWl3S0lDQWdJQ0p6WlhKcFlXd2lPaUFpWXpJMU9HRTJaR1JqT1RoaVlqVmhOV1U1TkRSbU5qSmxNRFkzT1dFM05HVWlMQW9nSUNBZ0luSnZiR1VpT2lBaWFXNTBaWEp0WldScFlYUmxJaXdLSUNBZ0lDSnBjM04xWlhKTFpYbEpaQ0k2SUNKa1pqSXlaVEk0TURZeE1USmtaV0ppWlRrMU16WTNNbUZoWm1FeE9EWmtOams1WVdZd1pUazNaR1F6Wm1ReVlqQTVabUU0TXpVNU1EQTFabVV6TkRobUlpd0tJQ0FnSUNKemRXSnFaV04wUzJWNVNXUWlPaUFpTURSbFltTTNOR1F6TURBeU1HWTNNamM1T0Roak9UWmpNbVptWWpJeU9ESXhPRGd6WldVMllqSmxOR014TVRkbU5tVTVOVGxrWW1RMk0yWXpNMlE0TnlJc0NpQWdJQ0FpYzNWaWFtVmpkRkIxWW14cFkwdGxlU0k2SUNKQ1JIZzFWbk5YU1ZwS1YzcERTMkZVTURoZmVGTklhR2RoTFdsek9UVmhXVTlvTUcxa1FUVTBOMll5UVd4R1ZFeG1lV1ZhYWpBeGVtVk9hV3ROZFdSalFXWk1WSGcwUkVoWVJHRkhjM0ZRUkZSWmRGWjZSRzhpTEFvZ0lDQWdJbTV2ZEVKbFptOXlaU0k2SUNJeU1ESTJMVEF4TFRBeFZEQXdPakF3T2pBd1dpSXNDaUFnSUNBaWJtOTBRV1owWlhJaU9pQWlNakF5Tnkwd01TMHdNVlF3TURvd01Eb3dNRm9pQ24wSyIsCiAgICAgICAgICAgICJzaWduYXR1cmUiOiB7CiAgICAgICAgICAgICAgICAiYWxnb3JpdGhtIjogIkVTMjU2IiwKICAgICAgICAgICAgICAgICJrZXlJZCI6ICJkZjIyZTI4MDYxMTJkZWJiZTk1MzY3MmFhZmExODZkNjk5YWYwZTk3ZGQzZmQyYjA5ZmE4MzU5MDA1ZmUzNDhmIiwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJsWEl4RG1rdlJYMkNLTF9OU01KX3ltLUhKWTQ4cW9VejRoM2JQUHgzM0lGRXYwRTFnblNPV2QxTTJYcHJtSVZSQ3Z4OHhONUNmWUowTDd3enNEQV9GUSIKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiYnl0ZXMiOiAiZXdvZ0lDQWdJbVp2Y20xaGRGWmxjbk5wYjI0aU9pQWljblZ6ZEdaekxtTnZibTVsWTNRdWIyWm1iR2x1WlM1MGNuVnpkRXhwYm1zdk1TSXNDaUFnSUNBaWNISnZkRzlqYjJ4V1pYSnphVzl1SWpvZ0luWXhJaXdLSUNBZ0lDSnpaWEpwWVd3aU9pQWlOMkptWm1ObU1EYzRaRGMyTkRnNU9XRTNNR1prWWpGaFpqbGlZbU5rTmpJaUxBb2dJQ0FnSW5KdmJHVWlPaUFpYzJsbmJtbHVaeUlzQ2lBZ0lDQWlhWE56ZFdWeVMyVjVTV1FpT2lBaU1EUmxZbU0zTkdRek1EQXlNR1kzTWpjNU9EaGpPVFpqTW1abVlqSXlPREl4T0RnelpXVTJZakpsTkdNeE1UZG1ObVU1TlRsa1ltUTJNMll6TTJRNE55SXNDaUFnSUNBaWMzVmlhbVZqZEV0bGVVbGtJam9nSWpBNFpUY3lPVFZqT0dZNVpEQTBNMlV5TW1JeVlqZ3dabVJpTVRRNE1HSXdZbVZqTURZd1pHRmpZbU5sTjJSbE9XUmtNbVV6WkRVNE0yWTVNMlEzWlRnaUxBb2dJQ0FnSW5OMVltcGxZM1JRZFdKc2FXTkxaWGtpT2lBaVFrZFdTVlUzZVRWU2FXZ3lhRWs0TFZCZmFXd3RSM1ZJVm5Sa1VVeEdlVEpFYUZGbFJsVTNjV2g1WW1KME1qa3hlbE5DYTE5MWVGbEtTazVoUWtSa1pEbDNUVU0wUkdac1dUVlJRbEJVT0cxU05qZEZaMk5CSWl3S0lDQWdJQ0p1YjNSQ1pXWnZjbVVpT2lBaU1qQXlOaTB3T0Mwd01WUXdNRG93TURvd01Gb2lMQW9nSUNBZ0ltNXZkRUZtZEdWeUlqb2dJakl3TWpZdE1EZ3RNekZVTURBNk1EQTZNREJhSWdwOUNnPT0iLAogICAgICAgICAgICAic2lnbmF0dXJlIjogewogICAgICAgICAgICAgICAgImFsZ29yaXRobSI6ICJFUzI1NiIsCiAgICAgICAgICAgICAgICAia2V5SWQiOiAiMDRlYmM3NGQzMDAyMGY3Mjc5ODhjOTZjMmZmYjIyODIxODgzZWU2YjJlNGMxMTdmNmU5NTlkYmQ2M2YzM2Q4NyIsCiAgICAgICAgICAgICAgICAidmFsdWUiOiAiamhrZFozeWpuOC1Od0JxWjZjRUtZVlNsYV9VblVmVFJWVmNxazZJSndUTWx3S1ZyWU8xWEZSVTA1bTBWc0VNMThYM1ppOE96bGxoSVAyUjlxWUMwa0EiCiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "08e7295c8f9d043e22b2b80fdb1480b0bec060dacbce7de9dd2e3d583f93d7e8",
"value": "jiDV4Wy81WwqQwlxVqF0eFTh9jEMnD3mkUWB5XqGmpkSw0WnTdAjuVH1wqRMeUEYkZLDTu7fB5YJRvBN2wrUtA"
}
},
"expected": {
"signatureVerifies": true,
"chainVerifies": true,
"rootPinned": true,
"withinWindow": true,
"accepted": true,
"reason": null
}
},
{
"name": "response binding the device public key and the challenge proof",
"artifact": "response",
"answersChallenge": "challenge signed by a chained signing key under the pinned root",
"signerKey": "device",
"evaluationTime": "2026-08-20T12:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50UmVzcG9uc2UvMSIsCiAgICAicHJvdG9jb2xWZXJzaW9uIjogInYxIiwKICAgICJjaGFsbGVuZ2VJZCI6ICIwMTk4ZjNhMS03ZjAwLTdkNDAtYmU1MS0zZjRhNWI2YzdkODMiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiY2hhbGxlbmdlTm9uY2UiOiAiVFZ4NGpLYVBnV1BsZFRTbTdfU3pLUGxINUhmaGtwcXFyOWJ5ZUNqWGRIVSIsCiAgICAiY2hhbGxlbmdlUHJvb2YiOiAiamlEVjRXeTgxV3dxUXdseFZxRjBlRlRoOWpFTW5EM21rVVdCNVhxR21wa1N3MFduVGRBanVWSDF3cVJNZVVFWWtaTERUdTdmQjVZSlJ2Qk4yd3JVdEEiLAogICAgImRldmljZUtleUlkIjogIjM5Y2EyNGM4YjAyYTU1OWZkOWJlYjJiMWY1ZDE4Y2VkMjBjNGJiMjQ2NTc3YjkyOTE0YWU2ODE0YzNmNzBhY2YiLAogICAgImRldmljZVB1YmxpY0tleSI6ICJCT1R1eUp6RkhqVzQyaFJsckNZM2JtTUJLMWdmdFQzYU5MaVRnQ3NKMV9lTlNkcjFHTTlhWTh3Mzlwb3BWTG9lZHdvZUFHQUJncDYxaEVhd2tFS3FJUDAiLAogICAgImRldmljZU5vbmNlIjogInB3bUwxVW9jSG5NYVUwa2w2ZVc0M3BfcllOakxBOGtJaUN2TDlibktwUGMiLAogICAgInByb2R1Y2VkQXQiOiAiMjAyNi0wOC0yMFQxMjowMDowMFoiCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "n4joB8c1KbYvw7MSjeGs1BEYeYe8dFpy47Me_iD7MO1gUSKDpGl6MyCxuZ8KWmjzNMUWz1sgREEW8HsgpNlsIQ"
}
},
"expected": {
"signatureVerifies": true,
"devicePublicKeyIsTheVerifyingKey": true,
"challengeProofMatches": true,
"organizationMatches": true,
"clusterMatches": true,
"withinWindow": true,
"accepted": true,
"reason": null
}
},
{
"name": "response carrying an unknown optional field is accepted and the field is discarded",
"artifact": "response",
"answersChallenge": "challenge signed by a chained signing key under the pinned root",
"signerKey": "device",
"evaluationTime": "2026-08-20T12:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50UmVzcG9uc2UvMSIsCiAgICAicHJvdG9jb2xWZXJzaW9uIjogInYxIiwKICAgICJjaGFsbGVuZ2VJZCI6ICIwMTk4ZjNhMS03ZjAwLTdkNDAtYmU1MS0zZjRhNWI2YzdkODMiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiY2hhbGxlbmdlTm9uY2UiOiAiVFZ4NGpLYVBnV1BsZFRTbTdfU3pLUGxINUhmaGtwcXFyOWJ5ZUNqWGRIVSIsCiAgICAiY2hhbGxlbmdlUHJvb2YiOiAiamlEVjRXeTgxV3dxUXdseFZxRjBlRlRoOWpFTW5EM21rVVdCNVhxR21wa1N3MFduVGRBanVWSDF3cVJNZVVFWWtaTERUdTdmQjVZSlJ2Qk4yd3JVdEEiLAogICAgImRldmljZUtleUlkIjogIjM5Y2EyNGM4YjAyYTU1OWZkOWJlYjJiMWY1ZDE4Y2VkMjBjNGJiMjQ2NTc3YjkyOTE0YWU2ODE0YzNmNzBhY2YiLAogICAgImRldmljZVB1YmxpY0tleSI6ICJCT1R1eUp6RkhqVzQyaFJsckNZM2JtTUJLMWdmdFQzYU5MaVRnQ3NKMV9lTlNkcjFHTTlhWTh3Mzlwb3BWTG9lZHdvZUFHQUJncDYxaEVhd2tFS3FJUDAiLAogICAgImRldmljZU5vbmNlIjogIm5fYWVJb0ZkeldyRnppWjZ4b1BManBobnhaZVB5US1YaTdQSzRYUFhDNUEiLAogICAgInByb2R1Y2VkQXQiOiAiMjAyNi0wOC0yMFQxMjowMDowMFoiLAogICAgInRlbGVtZXRyeUhpbnQiOiAiaWdub3JlZCIKfQo=",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "tdMIDSpMk2kcZKT1FC8e3TDxNwujk8CVCgw7c3Np3ols8nUMx5Hx-TuznC57lqEq0Yo08H4r4AaztSktISHbXQ"
}
},
"expected": {
"signatureVerifies": true,
"devicePublicKeyIsTheVerifyingKey": true,
"challengeProofMatches": true,
"organizationMatches": true,
"clusterMatches": true,
"withinWindow": true,
"accepted": true,
"reason": null,
"discardedFields": [
"telemetryHint"
],
"echoedBack": [],
"stored": []
}
}
]
}
@@ -0,0 +1,111 @@
{
"protocolVersion": "v1",
"fixtureSet": "offline-enrollment",
"fixture": "error-codes",
"description": "Frozen ErrorInfo reasons for offline enrollment. Clients branch on status and reason, never on message.",
"domain": "rustfs.connect",
"detailType": "type.googleapis.com/google.rpc.ErrorInfo",
"disclosureRules": [
"A rejection never reveals whether a presented key or challenge belongs to another tenant.",
"A rejection never contains key material, signature octets, nonces, or document bytes.",
"A rejection never reports which of several failed checks failed first beyond the single frozen reason."
],
"reasons": [
{
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"meaning": "The protocolVersion is missing, malformed, or names an unsupported major version. Identical to the rule frozen in protocol/agent/v1/authentication.md."
},
{
"reason": "UNSUPPORTED_FORMAT",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"meaning": "The formatVersion is not one of the frozen supported format versions."
},
{
"reason": "SIGNATURE_MALFORMED",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"meaning": "The signature is not 64 octets of fixed-width r||s in unpadded base64url, or r or s is out of range."
},
{
"reason": "SIGNATURE_NOT_CANONICAL",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"meaning": "The signature is well formed and verifies, but its s exceeds half the group order. Only the low-S form is accepted."
},
{
"reason": "SIGNATURE_INVALID",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "ECDSA verification over the received octets failed. The document was altered, or it was signed by another key."
},
{
"reason": "ENROLLMENT_ROOT_UNKNOWN",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "The first trust link is issued by a key that is not pinned in this build. There is no path from this to acceptance: the root is never learned."
},
{
"reason": "TRUST_CHAIN_INVALID",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "A trust link failed its own signature check, named the wrong issuer, carried an unknown role, or was outside its validity at the challenge issuedAt."
},
{
"reason": "CONNECT_KEY_UNCHAINED",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "The challenge connectKeyId is not the subject of the last trust link, so nothing under the pinned root vouches for the signing key."
},
{
"reason": "CHALLENGE_UNKNOWN",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "Connect has no issued challenge with this challengeId."
},
{
"reason": "CHALLENGE_NOT_YET_VALID",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "The evaluation time is more than the skew tolerance before issuedAt."
},
{
"reason": "CHALLENGE_EXPIRED",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "The evaluation time is more than the skew tolerance after expiresAt."
},
{
"reason": "CHALLENGE_PROOF_INVALID",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "The response nonce or challengeProof is not the one Connect issued for this challenge."
},
{
"reason": "DEVICE_PROOF_INVALID",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "The response signature does not verify under the devicePublicKey it presents, or deviceKeyId is not that key fingerprint. Proof of possession failed."
},
{
"reason": "ENROLLMENT_REPLAYED",
"httpStatus": 409,
"status": "ABORTED",
"meaning": "The challenge was already consumed. A challenge is single use even when the replayed response is byte identical."
},
{
"reason": "ORGANIZATION_MISMATCH",
"httpStatus": 403,
"status": "PERMISSION_DENIED",
"meaning": "The response names a different organization than the challenge it answers."
},
{
"reason": "CLUSTER_MISMATCH",
"httpStatus": 403,
"status": "PERMISSION_DENIED",
"meaning": "The response names a different cluster than the challenge it answers."
}
]
}
@@ -0,0 +1,352 @@
{
"protocolVersion": "v1",
"fixtureSet": "offline-enrollment",
"fixture": "reject-vectors",
"description": "Offline enrollment artifacts that must never be accepted. signatureVerifies records whether the raw ECDSA verification over the received octets succeeds, so a vector that fails only on a rule beyond the mathematics is visibly distinct from a forgery.",
"vectors": [
{
"name": "tampered challenge bytes with the original signature",
"artifact": "challenge",
"signerKey": "signing",
"evaluationTime": "2026-08-20T12:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50Q2hhbGxlbmdlLzEiLAogICAgInByb3RvY29sVmVyc2lvbiI6ICJ2MSIsCiAgICAiY2hhbGxlbmdlSWQiOiAiMDE5OGYzYTEtN2YwMC03ZDQwLWJlNTEtM2Y0YTViNmM3ZDgzIiwKICAgICJvcmdhbml6YXRpb25OYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwIiwKICAgICJjbHVzdGVyTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEiLAogICAgIm5vbmNlIjogIkFWeDRqS2FQZ1dQbGRUU203X1N6S1BsSDVIZmhrcHFxcjlieWVDalhkSFUiLAogICAgImlzc3VlZEF0IjogIjIwMjYtMDgtMTdUMDA6MDA6MDBaIiwKICAgICJleHBpcmVzQXQiOiAiMjAyNi0wOC0yNFQwMDowMDowMFoiLAogICAgImNvbm5lY3RLZXlJZCI6ICIwOGU3Mjk1YzhmOWQwNDNlMjJiMmI4MGZkYjE0ODBiMGJlYzA2MGRhY2JjZTdkZTlkZDJlM2Q1ODNmOTNkN2U4IiwKICAgICJ0cnVzdENoYWluIjogWwogICAgICAgIHsKICAgICAgICAgICAgImJ5dGVzIjogImV3b2dJQ0FnSW1admNtMWhkRlpsY25OcGIyNGlPaUFpY25WemRHWnpMbU52Ym01bFkzUXViMlptYkdsdVpTNTBjblZ6ZEV4cGJtc3ZNU0lzQ2lBZ0lDQWljSEp2ZEc5amIyeFdaWEp6YVc5dUlqb2dJbll4SWl3S0lDQWdJQ0p6WlhKcFlXd2lPaUFpWXpJMU9HRTJaR1JqT1RoaVlqVmhOV1U1TkRSbU5qSmxNRFkzT1dFM05HVWlMQW9nSUNBZ0luSnZiR1VpT2lBaWFXNTBaWEp0WldScFlYUmxJaXdLSUNBZ0lDSnBjM04xWlhKTFpYbEpaQ0k2SUNKa1pqSXlaVEk0TURZeE1USmtaV0ppWlRrMU16WTNNbUZoWm1FeE9EWmtOams1WVdZd1pUazNaR1F6Wm1ReVlqQTVabUU0TXpVNU1EQTFabVV6TkRobUlpd0tJQ0FnSUNKemRXSnFaV04wUzJWNVNXUWlPaUFpTURSbFltTTNOR1F6TURBeU1HWTNNamM1T0Roak9UWmpNbVptWWpJeU9ESXhPRGd6WldVMllqSmxOR014TVRkbU5tVTVOVGxrWW1RMk0yWXpNMlE0TnlJc0NpQWdJQ0FpYzNWaWFtVmpkRkIxWW14cFkwdGxlU0k2SUNKQ1JIZzFWbk5YU1ZwS1YzcERTMkZVTURoZmVGTklhR2RoTFdsek9UVmhXVTlvTUcxa1FUVTBOMll5UVd4R1ZFeG1lV1ZhYWpBeGVtVk9hV3ROZFdSalFXWk1WSGcwUkVoWVJHRkhjM0ZRUkZSWmRGWjZSRzhpTEFvZ0lDQWdJbTV2ZEVKbFptOXlaU0k2SUNJeU1ESTJMVEF4TFRBeFZEQXdPakF3T2pBd1dpSXNDaUFnSUNBaWJtOTBRV1owWlhJaU9pQWlNakF5Tnkwd01TMHdNVlF3TURvd01Eb3dNRm9pQ24wSyIsCiAgICAgICAgICAgICJzaWduYXR1cmUiOiB7CiAgICAgICAgICAgICAgICAiYWxnb3JpdGhtIjogIkVTMjU2IiwKICAgICAgICAgICAgICAgICJrZXlJZCI6ICJkZjIyZTI4MDYxMTJkZWJiZTk1MzY3MmFhZmExODZkNjk5YWYwZTk3ZGQzZmQyYjA5ZmE4MzU5MDA1ZmUzNDhmIiwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJsWEl4RG1rdlJYMkNLTF9OU01KX3ltLUhKWTQ4cW9VejRoM2JQUHgzM0lGRXYwRTFnblNPV2QxTTJYcHJtSVZSQ3Z4OHhONUNmWUowTDd3enNEQV9GUSIKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiYnl0ZXMiOiAiZXdvZ0lDQWdJbVp2Y20xaGRGWmxjbk5wYjI0aU9pQWljblZ6ZEdaekxtTnZibTVsWTNRdWIyWm1iR2x1WlM1MGNuVnpkRXhwYm1zdk1TSXNDaUFnSUNBaWNISnZkRzlqYjJ4V1pYSnphVzl1SWpvZ0luWXhJaXdLSUNBZ0lDSnpaWEpwWVd3aU9pQWlOMkptWm1ObU1EYzRaRGMyTkRnNU9XRTNNR1prWWpGaFpqbGlZbU5rTmpJaUxBb2dJQ0FnSW5KdmJHVWlPaUFpYzJsbmJtbHVaeUlzQ2lBZ0lDQWlhWE56ZFdWeVMyVjVTV1FpT2lBaU1EUmxZbU0zTkdRek1EQXlNR1kzTWpjNU9EaGpPVFpqTW1abVlqSXlPREl4T0RnelpXVTJZakpsTkdNeE1UZG1ObVU1TlRsa1ltUTJNMll6TTJRNE55SXNDaUFnSUNBaWMzVmlhbVZqZEV0bGVVbGtJam9nSWpBNFpUY3lPVFZqT0dZNVpEQTBNMlV5TW1JeVlqZ3dabVJpTVRRNE1HSXdZbVZqTURZd1pHRmpZbU5sTjJSbE9XUmtNbVV6WkRVNE0yWTVNMlEzWlRnaUxBb2dJQ0FnSW5OMVltcGxZM1JRZFdKc2FXTkxaWGtpT2lBaVFrZFdTVlUzZVRWU2FXZ3lhRWs0TFZCZmFXd3RSM1ZJVm5Sa1VVeEdlVEpFYUZGbFJsVTNjV2g1WW1KME1qa3hlbE5DYTE5MWVGbEtTazVoUWtSa1pEbDNUVU0wUkdac1dUVlJRbEJVT0cxU05qZEZaMk5CSWl3S0lDQWdJQ0p1YjNSQ1pXWnZjbVVpT2lBaU1qQXlOaTB3T0Mwd01WUXdNRG93TURvd01Gb2lMQW9nSUNBZ0ltNXZkRUZtZEdWeUlqb2dJakl3TWpZdE1EZ3RNekZVTURBNk1EQTZNREJhSWdwOUNnPT0iLAogICAgICAgICAgICAic2lnbmF0dXJlIjogewogICAgICAgICAgICAgICAgImFsZ29yaXRobSI6ICJFUzI1NiIsCiAgICAgICAgICAgICAgICAia2V5SWQiOiAiMDRlYmM3NGQzMDAyMGY3Mjc5ODhjOTZjMmZmYjIyODIxODgzZWU2YjJlNGMxMTdmNmU5NTlkYmQ2M2YzM2Q4NyIsCiAgICAgICAgICAgICAgICAidmFsdWUiOiAiamhrZFozeWpuOC1Od0JxWjZjRUtZVlNsYV9VblVmVFJWVmNxazZJSndUTWx3S1ZyWU8xWEZSVTA1bTBWc0VNMThYM1ppOE96bGxoSVAyUjlxWUMwa0EiCiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "08e7295c8f9d043e22b2b80fdb1480b0bec060dacbce7de9dd2e3d583f93d7e8",
"value": "jiDV4Wy81WwqQwlxVqF0eFTh9jEMnD3mkUWB5XqGmpkSw0WnTdAjuVH1wqRMeUEYkZLDTu7fB5YJRvBN2wrUtA"
}
},
"expected": {
"signatureVerifies": false,
"accepted": false,
"reason": "SIGNATURE_INVALID"
}
},
{
"name": "chain rooted at a key that is not pinned in the build",
"artifact": "challenge",
"signerKey": "rogueSigning",
"evaluationTime": "2026-08-20T12:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50Q2hhbGxlbmdlLzEiLAogICAgInByb3RvY29sVmVyc2lvbiI6ICJ2MSIsCiAgICAiY2hhbGxlbmdlSWQiOiAiMDE5OGYzYTEtN2YwMC03ZDQwLWJlNTEtM2Y0YTViNmM3ZDgzIiwKICAgICJvcmdhbml6YXRpb25OYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwIiwKICAgICJjbHVzdGVyTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEiLAogICAgIm5vbmNlIjogIjBZcVFTc2xEYlc5Nm5fcmQ4M1dKQllmX1RSWTNoZkh2WHFCRHkwWVdMY00iLAogICAgImlzc3VlZEF0IjogIjIwMjYtMDgtMTdUMDA6MDA6MDBaIiwKICAgICJleHBpcmVzQXQiOiAiMjAyNi0wOC0yNFQwMDowMDowMFoiLAogICAgImNvbm5lY3RLZXlJZCI6ICI5MGYzOGEwZGMyYzVmOTQ4ZjQ3ODg3YTAxMGVhM2NiOWU0MjVkMDQwOWVkYjJhNDAxNGM3Zjc1MzliYzIyNDcxIiwKICAgICJ0cnVzdENoYWluIjogWwogICAgICAgIHsKICAgICAgICAgICAgImJ5dGVzIjogImV3b2dJQ0FnSW1admNtMWhkRlpsY25OcGIyNGlPaUFpY25WemRHWnpMbU52Ym01bFkzUXViMlptYkdsdVpTNTBjblZ6ZEV4cGJtc3ZNU0lzQ2lBZ0lDQWljSEp2ZEc5amIyeFdaWEp6YVc5dUlqb2dJbll4SWl3S0lDQWdJQ0p6WlhKcFlXd2lPaUFpTVROalpHSTNORGhpTVRoak1ETmlNakEwTnpCalpqWTJZVEprWWpaaFpqZ2lMQW9nSUNBZ0luSnZiR1VpT2lBaWMybG5ibWx1WnlJc0NpQWdJQ0FpYVhOemRXVnlTMlY1U1dRaU9pQWlOV1ptTXpjNU1UQmhZVFJrTmprNU5EbGxNbU0wT0RobU9UaGtOakEzTW1ZeE1HRXpZek5sTnpOa056YzJOams0T1RZek9EY3lOVGd5TmpRMFpqY3pNU0lzQ2lBZ0lDQWljM1ZpYW1WamRFdGxlVWxrSWpvZ0lqa3daak00WVRCa1l6SmpOV1k1TkRobU5EYzRPRGRoTURFd1pXRXpZMkk1WlRReU5XUXdOREE1WldSaU1tRTBNREUwWXpkbU56VXpPV0pqTWpJME56RWlMQW9nSUNBZ0luTjFZbXBsWTNSUWRXSnNhV05MWlhraU9pQWlRa2xIVG01elQzRk5Ra2swVEZCRU9FcDVVMmMzTjBVemRrTm9TVGM0VWpSQlNHZHZiSGhHWTBwT2REbGFPVWR6VFc5V1MwbGxPVnBQWkVwUFJUTTRjMFphZEROdlJqVmpjbFV3U0hGa2VtbEhabWxGY0RsVklpd0tJQ0FnSUNKdWIzUkNaV1p2Y21VaU9pQWlNakF5Tmkwd09DMHdNVlF3TURvd01Eb3dNRm9pTEFvZ0lDQWdJbTV2ZEVGbWRHVnlJam9nSWpJd01qWXRNRGd0TXpGVU1EQTZNREE2TURCYUlncDlDZz09IiwKICAgICAgICAgICAgInNpZ25hdHVyZSI6IHsKICAgICAgICAgICAgICAgICJhbGdvcml0aG0iOiAiRVMyNTYiLAogICAgICAgICAgICAgICAgImtleUlkIjogIjVmZjM3OTEwYWE0ZDY5OTQ5ZTJjNDg4Zjk4ZDYwNzJmMTBhM2MzZTczZDc3NjY5ODk2Mzg3MjU4MjY0NGY3MzEiLAogICAgICAgICAgICAgICAgInZhbHVlIjogInN1eFViWjlJczFsaVFsaGpmd1hRb1RlemU0bWU5ZjdRZGRyMzI5OWxtenBuLThWekgya2dUQTVZdk05QmktYjRBVGVITkxQQm1rRkRoYVZveFg2NEx3IgogICAgICAgICAgICB9CiAgICAgICAgfQogICAgXQp9Cg==",
"signature": {
"algorithm": "ES256",
"keyId": "90f38a0dc2c5f948f47887a010ea3cb9e425d0409edb2a4014c7f7539bc22471",
"value": "WcptrTFWDSY9WhA9Lu8U-OtJZtqroeoPypZuev4S3eEvEN1L1GsAGfYAAcF9LkHUO10WI6c7NVurZdtSzoU56g"
}
},
"expected": {
"signatureVerifies": true,
"rootPinned": false,
"accepted": false,
"reason": "ENROLLMENT_ROOT_UNKNOWN"
}
},
{
"name": "signing link expired before the challenge was issued",
"artifact": "challenge",
"signerKey": "signing",
"evaluationTime": "2026-08-20T12:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50Q2hhbGxlbmdlLzEiLAogICAgInByb3RvY29sVmVyc2lvbiI6ICJ2MSIsCiAgICAiY2hhbGxlbmdlSWQiOiAiMDE5OGYzYTEtN2YwMC03ZDQwLWJlNTEtM2Y0YTViNmM3ZDgzIiwKICAgICJvcmdhbml6YXRpb25OYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwIiwKICAgICJjbHVzdGVyTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEiLAogICAgIm5vbmNlIjogIjd4UWJEQUREbTc3NGJCZzhFc2tfUnhDajFrRUdjaTJOOE8yUmVLSGdNNlUiLAogICAgImlzc3VlZEF0IjogIjIwMjYtMDgtMTdUMDA6MDA6MDBaIiwKICAgICJleHBpcmVzQXQiOiAiMjAyNi0wOC0yNFQwMDowMDowMFoiLAogICAgImNvbm5lY3RLZXlJZCI6ICIwOGU3Mjk1YzhmOWQwNDNlMjJiMmI4MGZkYjE0ODBiMGJlYzA2MGRhY2JjZTdkZTlkZDJlM2Q1ODNmOTNkN2U4IiwKICAgICJ0cnVzdENoYWluIjogWwogICAgICAgIHsKICAgICAgICAgICAgImJ5dGVzIjogImV3b2dJQ0FnSW1admNtMWhkRlpsY25OcGIyNGlPaUFpY25WemRHWnpMbU52Ym01bFkzUXViMlptYkdsdVpTNTBjblZ6ZEV4cGJtc3ZNU0lzQ2lBZ0lDQWljSEp2ZEc5amIyeFdaWEp6YVc5dUlqb2dJbll4SWl3S0lDQWdJQ0p6WlhKcFlXd2lPaUFpWXpJMU9HRTJaR1JqT1RoaVlqVmhOV1U1TkRSbU5qSmxNRFkzT1dFM05HVWlMQW9nSUNBZ0luSnZiR1VpT2lBaWFXNTBaWEp0WldScFlYUmxJaXdLSUNBZ0lDSnBjM04xWlhKTFpYbEpaQ0k2SUNKa1pqSXlaVEk0TURZeE1USmtaV0ppWlRrMU16WTNNbUZoWm1FeE9EWmtOams1WVdZd1pUazNaR1F6Wm1ReVlqQTVabUU0TXpVNU1EQTFabVV6TkRobUlpd0tJQ0FnSUNKemRXSnFaV04wUzJWNVNXUWlPaUFpTURSbFltTTNOR1F6TURBeU1HWTNNamM1T0Roak9UWmpNbVptWWpJeU9ESXhPRGd6WldVMllqSmxOR014TVRkbU5tVTVOVGxrWW1RMk0yWXpNMlE0TnlJc0NpQWdJQ0FpYzNWaWFtVmpkRkIxWW14cFkwdGxlU0k2SUNKQ1JIZzFWbk5YU1ZwS1YzcERTMkZVTURoZmVGTklhR2RoTFdsek9UVmhXVTlvTUcxa1FUVTBOMll5UVd4R1ZFeG1lV1ZhYWpBeGVtVk9hV3ROZFdSalFXWk1WSGcwUkVoWVJHRkhjM0ZRUkZSWmRGWjZSRzhpTEFvZ0lDQWdJbTV2ZEVKbFptOXlaU0k2SUNJeU1ESTJMVEF4TFRBeFZEQXdPakF3T2pBd1dpSXNDaUFnSUNBaWJtOTBRV1owWlhJaU9pQWlNakF5Tnkwd01TMHdNVlF3TURvd01Eb3dNRm9pQ24wSyIsCiAgICAgICAgICAgICJzaWduYXR1cmUiOiB7CiAgICAgICAgICAgICAgICAiYWxnb3JpdGhtIjogIkVTMjU2IiwKICAgICAgICAgICAgICAgICJrZXlJZCI6ICJkZjIyZTI4MDYxMTJkZWJiZTk1MzY3MmFhZmExODZkNjk5YWYwZTk3ZGQzZmQyYjA5ZmE4MzU5MDA1ZmUzNDhmIiwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJsWEl4RG1rdlJYMkNLTF9OU01KX3ltLUhKWTQ4cW9VejRoM2JQUHgzM0lGRXYwRTFnblNPV2QxTTJYcHJtSVZSQ3Z4OHhONUNmWUowTDd3enNEQV9GUSIKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiYnl0ZXMiOiAiZXdvZ0lDQWdJbVp2Y20xaGRGWmxjbk5wYjI0aU9pQWljblZ6ZEdaekxtTnZibTVsWTNRdWIyWm1iR2x1WlM1MGNuVnpkRXhwYm1zdk1TSXNDaUFnSUNBaWNISnZkRzlqYjJ4V1pYSnphVzl1SWpvZ0luWXhJaXdLSUNBZ0lDSnpaWEpwWVd3aU9pQWlZMkV3T1dZelpUVmhaRGt4T1RFd05tUXhNalpqT0RSbFpHUmpORFZpWlRnaUxBb2dJQ0FnSW5KdmJHVWlPaUFpYzJsbmJtbHVaeUlzQ2lBZ0lDQWlhWE56ZFdWeVMyVjVTV1FpT2lBaU1EUmxZbU0zTkdRek1EQXlNR1kzTWpjNU9EaGpPVFpqTW1abVlqSXlPREl4T0RnelpXVTJZakpsTkdNeE1UZG1ObVU1TlRsa1ltUTJNMll6TTJRNE55SXNDaUFnSUNBaWMzVmlhbVZqZEV0bGVVbGtJam9nSWpBNFpUY3lPVFZqT0dZNVpEQTBNMlV5TW1JeVlqZ3dabVJpTVRRNE1HSXdZbVZqTURZd1pHRmpZbU5sTjJSbE9XUmtNbVV6WkRVNE0yWTVNMlEzWlRnaUxBb2dJQ0FnSW5OMVltcGxZM1JRZFdKc2FXTkxaWGtpT2lBaVFrZFdTVlUzZVRWU2FXZ3lhRWs0TFZCZmFXd3RSM1ZJVm5Sa1VVeEdlVEpFYUZGbFJsVTNjV2g1WW1KME1qa3hlbE5DYTE5MWVGbEtTazVoUWtSa1pEbDNUVU0wUkdac1dUVlJRbEJVT0cxU05qZEZaMk5CSWl3S0lDQWdJQ0p1YjNSQ1pXWnZjbVVpT2lBaU1qQXlOaTB3Tmkwd01WUXdNRG93TURvd01Gb2lMQW9nSUNBZ0ltNXZkRUZtZEdWeUlqb2dJakl3TWpZdE1EY3RNREZVTURBNk1EQTZNREJhSWdwOUNnPT0iLAogICAgICAgICAgICAic2lnbmF0dXJlIjogewogICAgICAgICAgICAgICAgImFsZ29yaXRobSI6ICJFUzI1NiIsCiAgICAgICAgICAgICAgICAia2V5SWQiOiAiMDRlYmM3NGQzMDAyMGY3Mjc5ODhjOTZjMmZmYjIyODIxODgzZWU2YjJlNGMxMTdmNmU5NTlkYmQ2M2YzM2Q4NyIsCiAgICAgICAgICAgICAgICAidmFsdWUiOiAiU1g3NVdkT1ZMdnBoUk1jY2UweHE3VzUtRXIwSFNWaEJjc1VuZzFVdjhSQVBSMjNwckNINW9KT0YwNkFfaDd4SEFqOVhMN1lldXhSU3RQc19wa1ROckEiCiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "08e7295c8f9d043e22b2b80fdb1480b0bec060dacbce7de9dd2e3d583f93d7e8",
"value": "xFOTJIOO0sMTsbCNGeNF31-A7R1aVyPWDx06qOuvb2BwTGNwARo95z-3zcPsZ68TSrKIocNbG5KRd3jM9zeVNQ"
}
},
"expected": {
"signatureVerifies": true,
"chainVerifies": false,
"accepted": false,
"reason": "TRUST_CHAIN_INVALID"
}
},
{
"name": "challenge signed by a key the chain does not name",
"artifact": "challenge",
"signerKey": "stray",
"evaluationTime": "2026-08-20T12:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50Q2hhbGxlbmdlLzEiLAogICAgInByb3RvY29sVmVyc2lvbiI6ICJ2MSIsCiAgICAiY2hhbGxlbmdlSWQiOiAiMDE5OGYzYTEtN2YwMC03ZDQwLWJlNTEtM2Y0YTViNmM3ZDgzIiwKICAgICJvcmdhbml6YXRpb25OYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwIiwKICAgICJjbHVzdGVyTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEiLAogICAgIm5vbmNlIjogInNXZXhMdUV5MVRtQmVydnpSX1g3SlBLa3BBdnNlM051eU10NUJIZmpqNGsiLAogICAgImlzc3VlZEF0IjogIjIwMjYtMDgtMTdUMDA6MDA6MDBaIiwKICAgICJleHBpcmVzQXQiOiAiMjAyNi0wOC0yNFQwMDowMDowMFoiLAogICAgImNvbm5lY3RLZXlJZCI6ICJmNmZiZTA1MGRlZmRlZDE4YjUwNDc3YWNlMzhjOTUxNWZiNjFiODE1N2U1N2IyZjBlN2U4Y2E2OWM4NjJiNmNhIiwKICAgICJ0cnVzdENoYWluIjogWwogICAgICAgIHsKICAgICAgICAgICAgImJ5dGVzIjogImV3b2dJQ0FnSW1admNtMWhkRlpsY25OcGIyNGlPaUFpY25WemRHWnpMbU52Ym01bFkzUXViMlptYkdsdVpTNTBjblZ6ZEV4cGJtc3ZNU0lzQ2lBZ0lDQWljSEp2ZEc5amIyeFdaWEp6YVc5dUlqb2dJbll4SWl3S0lDQWdJQ0p6WlhKcFlXd2lPaUFpWXpJMU9HRTJaR1JqT1RoaVlqVmhOV1U1TkRSbU5qSmxNRFkzT1dFM05HVWlMQW9nSUNBZ0luSnZiR1VpT2lBaWFXNTBaWEp0WldScFlYUmxJaXdLSUNBZ0lDSnBjM04xWlhKTFpYbEpaQ0k2SUNKa1pqSXlaVEk0TURZeE1USmtaV0ppWlRrMU16WTNNbUZoWm1FeE9EWmtOams1WVdZd1pUazNaR1F6Wm1ReVlqQTVabUU0TXpVNU1EQTFabVV6TkRobUlpd0tJQ0FnSUNKemRXSnFaV04wUzJWNVNXUWlPaUFpTURSbFltTTNOR1F6TURBeU1HWTNNamM1T0Roak9UWmpNbVptWWpJeU9ESXhPRGd6WldVMllqSmxOR014TVRkbU5tVTVOVGxrWW1RMk0yWXpNMlE0TnlJc0NpQWdJQ0FpYzNWaWFtVmpkRkIxWW14cFkwdGxlU0k2SUNKQ1JIZzFWbk5YU1ZwS1YzcERTMkZVTURoZmVGTklhR2RoTFdsek9UVmhXVTlvTUcxa1FUVTBOMll5UVd4R1ZFeG1lV1ZhYWpBeGVtVk9hV3ROZFdSalFXWk1WSGcwUkVoWVJHRkhjM0ZRUkZSWmRGWjZSRzhpTEFvZ0lDQWdJbTV2ZEVKbFptOXlaU0k2SUNJeU1ESTJMVEF4TFRBeFZEQXdPakF3T2pBd1dpSXNDaUFnSUNBaWJtOTBRV1owWlhJaU9pQWlNakF5Tnkwd01TMHdNVlF3TURvd01Eb3dNRm9pQ24wSyIsCiAgICAgICAgICAgICJzaWduYXR1cmUiOiB7CiAgICAgICAgICAgICAgICAiYWxnb3JpdGhtIjogIkVTMjU2IiwKICAgICAgICAgICAgICAgICJrZXlJZCI6ICJkZjIyZTI4MDYxMTJkZWJiZTk1MzY3MmFhZmExODZkNjk5YWYwZTk3ZGQzZmQyYjA5ZmE4MzU5MDA1ZmUzNDhmIiwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJsWEl4RG1rdlJYMkNLTF9OU01KX3ltLUhKWTQ4cW9VejRoM2JQUHgzM0lGRXYwRTFnblNPV2QxTTJYcHJtSVZSQ3Z4OHhONUNmWUowTDd3enNEQV9GUSIKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiYnl0ZXMiOiAiZXdvZ0lDQWdJbVp2Y20xaGRGWmxjbk5wYjI0aU9pQWljblZ6ZEdaekxtTnZibTVsWTNRdWIyWm1iR2x1WlM1MGNuVnpkRXhwYm1zdk1TSXNDaUFnSUNBaWNISnZkRzlqYjJ4V1pYSnphVzl1SWpvZ0luWXhJaXdLSUNBZ0lDSnpaWEpwWVd3aU9pQWlOMkptWm1ObU1EYzRaRGMyTkRnNU9XRTNNR1prWWpGaFpqbGlZbU5rTmpJaUxBb2dJQ0FnSW5KdmJHVWlPaUFpYzJsbmJtbHVaeUlzQ2lBZ0lDQWlhWE56ZFdWeVMyVjVTV1FpT2lBaU1EUmxZbU0zTkdRek1EQXlNR1kzTWpjNU9EaGpPVFpqTW1abVlqSXlPREl4T0RnelpXVTJZakpsTkdNeE1UZG1ObVU1TlRsa1ltUTJNMll6TTJRNE55SXNDaUFnSUNBaWMzVmlhbVZqZEV0bGVVbGtJam9nSWpBNFpUY3lPVFZqT0dZNVpEQTBNMlV5TW1JeVlqZ3dabVJpTVRRNE1HSXdZbVZqTURZd1pHRmpZbU5sTjJSbE9XUmtNbVV6WkRVNE0yWTVNMlEzWlRnaUxBb2dJQ0FnSW5OMVltcGxZM1JRZFdKc2FXTkxaWGtpT2lBaVFrZFdTVlUzZVRWU2FXZ3lhRWs0TFZCZmFXd3RSM1ZJVm5Sa1VVeEdlVEpFYUZGbFJsVTNjV2g1WW1KME1qa3hlbE5DYTE5MWVGbEtTazVoUWtSa1pEbDNUVU0wUkdac1dUVlJRbEJVT0cxU05qZEZaMk5CSWl3S0lDQWdJQ0p1YjNSQ1pXWnZjbVVpT2lBaU1qQXlOaTB3T0Mwd01WUXdNRG93TURvd01Gb2lMQW9nSUNBZ0ltNXZkRUZtZEdWeUlqb2dJakl3TWpZdE1EZ3RNekZVTURBNk1EQTZNREJhSWdwOUNnPT0iLAogICAgICAgICAgICAic2lnbmF0dXJlIjogewogICAgICAgICAgICAgICAgImFsZ29yaXRobSI6ICJFUzI1NiIsCiAgICAgICAgICAgICAgICAia2V5SWQiOiAiMDRlYmM3NGQzMDAyMGY3Mjc5ODhjOTZjMmZmYjIyODIxODgzZWU2YjJlNGMxMTdmNmU5NTlkYmQ2M2YzM2Q4NyIsCiAgICAgICAgICAgICAgICAidmFsdWUiOiAiamhrZFozeWpuOC1Od0JxWjZjRUtZVlNsYV9VblVmVFJWVmNxazZJSndUTWx3S1ZyWU8xWEZSVTA1bTBWc0VNMThYM1ppOE96bGxoSVAyUjlxWUMwa0EiCiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "f6fbe050defded18b50477ace38c9515fb61b8157e57b2f0e7e8ca69c862b6ca",
"value": "FoWcvh5OA-_Vm7bCTf_TQuw2oGq5lOwjpVdfY45fQRAy9-TvHBCRr7Z1x4QC_5bjjt_hbled0dVm6ekfpC2dvw"
}
},
"expected": {
"signatureVerifies": true,
"connectKeyChained": false,
"accepted": false,
"reason": "CONNECT_KEY_UNCHAINED"
}
},
{
"name": "challenge evaluated 301 seconds past its expiresAt",
"artifact": "challenge",
"signerKey": "signing",
"evaluationTime": "2026-08-24T00:05:01Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50Q2hhbGxlbmdlLzEiLAogICAgInByb3RvY29sVmVyc2lvbiI6ICJ2MSIsCiAgICAiY2hhbGxlbmdlSWQiOiAiMDE5OGYzYTEtN2YwMC03ZDQwLWJlNTEtM2Y0YTViNmM3ZDgzIiwKICAgICJvcmdhbml6YXRpb25OYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwIiwKICAgICJjbHVzdGVyTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEiLAogICAgIm5vbmNlIjogIlRWeDRqS2FQZ1dQbGRUU203X1N6S1BsSDVIZmhrcHFxcjlieWVDalhkSFUiLAogICAgImlzc3VlZEF0IjogIjIwMjYtMDgtMTdUMDA6MDA6MDBaIiwKICAgICJleHBpcmVzQXQiOiAiMjAyNi0wOC0yNFQwMDowMDowMFoiLAogICAgImNvbm5lY3RLZXlJZCI6ICIwOGU3Mjk1YzhmOWQwNDNlMjJiMmI4MGZkYjE0ODBiMGJlYzA2MGRhY2JjZTdkZTlkZDJlM2Q1ODNmOTNkN2U4IiwKICAgICJ0cnVzdENoYWluIjogWwogICAgICAgIHsKICAgICAgICAgICAgImJ5dGVzIjogImV3b2dJQ0FnSW1admNtMWhkRlpsY25OcGIyNGlPaUFpY25WemRHWnpMbU52Ym01bFkzUXViMlptYkdsdVpTNTBjblZ6ZEV4cGJtc3ZNU0lzQ2lBZ0lDQWljSEp2ZEc5amIyeFdaWEp6YVc5dUlqb2dJbll4SWl3S0lDQWdJQ0p6WlhKcFlXd2lPaUFpWXpJMU9HRTJaR1JqT1RoaVlqVmhOV1U1TkRSbU5qSmxNRFkzT1dFM05HVWlMQW9nSUNBZ0luSnZiR1VpT2lBaWFXNTBaWEp0WldScFlYUmxJaXdLSUNBZ0lDSnBjM04xWlhKTFpYbEpaQ0k2SUNKa1pqSXlaVEk0TURZeE1USmtaV0ppWlRrMU16WTNNbUZoWm1FeE9EWmtOams1WVdZd1pUazNaR1F6Wm1ReVlqQTVabUU0TXpVNU1EQTFabVV6TkRobUlpd0tJQ0FnSUNKemRXSnFaV04wUzJWNVNXUWlPaUFpTURSbFltTTNOR1F6TURBeU1HWTNNamM1T0Roak9UWmpNbVptWWpJeU9ESXhPRGd6WldVMllqSmxOR014TVRkbU5tVTVOVGxrWW1RMk0yWXpNMlE0TnlJc0NpQWdJQ0FpYzNWaWFtVmpkRkIxWW14cFkwdGxlU0k2SUNKQ1JIZzFWbk5YU1ZwS1YzcERTMkZVTURoZmVGTklhR2RoTFdsek9UVmhXVTlvTUcxa1FUVTBOMll5UVd4R1ZFeG1lV1ZhYWpBeGVtVk9hV3ROZFdSalFXWk1WSGcwUkVoWVJHRkhjM0ZRUkZSWmRGWjZSRzhpTEFvZ0lDQWdJbTV2ZEVKbFptOXlaU0k2SUNJeU1ESTJMVEF4TFRBeFZEQXdPakF3T2pBd1dpSXNDaUFnSUNBaWJtOTBRV1owWlhJaU9pQWlNakF5Tnkwd01TMHdNVlF3TURvd01Eb3dNRm9pQ24wSyIsCiAgICAgICAgICAgICJzaWduYXR1cmUiOiB7CiAgICAgICAgICAgICAgICAiYWxnb3JpdGhtIjogIkVTMjU2IiwKICAgICAgICAgICAgICAgICJrZXlJZCI6ICJkZjIyZTI4MDYxMTJkZWJiZTk1MzY3MmFhZmExODZkNjk5YWYwZTk3ZGQzZmQyYjA5ZmE4MzU5MDA1ZmUzNDhmIiwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJsWEl4RG1rdlJYMkNLTF9OU01KX3ltLUhKWTQ4cW9VejRoM2JQUHgzM0lGRXYwRTFnblNPV2QxTTJYcHJtSVZSQ3Z4OHhONUNmWUowTDd3enNEQV9GUSIKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiYnl0ZXMiOiAiZXdvZ0lDQWdJbVp2Y20xaGRGWmxjbk5wYjI0aU9pQWljblZ6ZEdaekxtTnZibTVsWTNRdWIyWm1iR2x1WlM1MGNuVnpkRXhwYm1zdk1TSXNDaUFnSUNBaWNISnZkRzlqYjJ4V1pYSnphVzl1SWpvZ0luWXhJaXdLSUNBZ0lDSnpaWEpwWVd3aU9pQWlOMkptWm1ObU1EYzRaRGMyTkRnNU9XRTNNR1prWWpGaFpqbGlZbU5rTmpJaUxBb2dJQ0FnSW5KdmJHVWlPaUFpYzJsbmJtbHVaeUlzQ2lBZ0lDQWlhWE56ZFdWeVMyVjVTV1FpT2lBaU1EUmxZbU0zTkdRek1EQXlNR1kzTWpjNU9EaGpPVFpqTW1abVlqSXlPREl4T0RnelpXVTJZakpsTkdNeE1UZG1ObVU1TlRsa1ltUTJNMll6TTJRNE55SXNDaUFnSUNBaWMzVmlhbVZqZEV0bGVVbGtJam9nSWpBNFpUY3lPVFZqT0dZNVpEQTBNMlV5TW1JeVlqZ3dabVJpTVRRNE1HSXdZbVZqTURZd1pHRmpZbU5sTjJSbE9XUmtNbVV6WkRVNE0yWTVNMlEzWlRnaUxBb2dJQ0FnSW5OMVltcGxZM1JRZFdKc2FXTkxaWGtpT2lBaVFrZFdTVlUzZVRWU2FXZ3lhRWs0TFZCZmFXd3RSM1ZJVm5Sa1VVeEdlVEpFYUZGbFJsVTNjV2g1WW1KME1qa3hlbE5DYTE5MWVGbEtTazVoUWtSa1pEbDNUVU0wUkdac1dUVlJRbEJVT0cxU05qZEZaMk5CSWl3S0lDQWdJQ0p1YjNSQ1pXWnZjbVVpT2lBaU1qQXlOaTB3T0Mwd01WUXdNRG93TURvd01Gb2lMQW9nSUNBZ0ltNXZkRUZtZEdWeUlqb2dJakl3TWpZdE1EZ3RNekZVTURBNk1EQTZNREJhSWdwOUNnPT0iLAogICAgICAgICAgICAic2lnbmF0dXJlIjogewogICAgICAgICAgICAgICAgImFsZ29yaXRobSI6ICJFUzI1NiIsCiAgICAgICAgICAgICAgICAia2V5SWQiOiAiMDRlYmM3NGQzMDAyMGY3Mjc5ODhjOTZjMmZmYjIyODIxODgzZWU2YjJlNGMxMTdmNmU5NTlkYmQ2M2YzM2Q4NyIsCiAgICAgICAgICAgICAgICAidmFsdWUiOiAiamhrZFozeWpuOC1Od0JxWjZjRUtZVlNsYV9VblVmVFJWVmNxazZJSndUTWx3S1ZyWU8xWEZSVTA1bTBWc0VNMThYM1ppOE96bGxoSVAyUjlxWUMwa0EiCiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "08e7295c8f9d043e22b2b80fdb1480b0bec060dacbce7de9dd2e3d583f93d7e8",
"value": "jiDV4Wy81WwqQwlxVqF0eFTh9jEMnD3mkUWB5XqGmpkSw0WnTdAjuVH1wqRMeUEYkZLDTu7fB5YJRvBN2wrUtA"
}
},
"expected": {
"signatureVerifies": true,
"withinWindow": false,
"accepted": false,
"reason": "CHALLENGE_EXPIRED"
}
},
{
"name": "challenge evaluated 301 seconds before its issuedAt",
"artifact": "challenge",
"signerKey": "signing",
"evaluationTime": "2026-08-16T23:54:59Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50Q2hhbGxlbmdlLzEiLAogICAgInByb3RvY29sVmVyc2lvbiI6ICJ2MSIsCiAgICAiY2hhbGxlbmdlSWQiOiAiMDE5OGYzYTEtN2YwMC03ZDQwLWJlNTEtM2Y0YTViNmM3ZDgzIiwKICAgICJvcmdhbml6YXRpb25OYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwIiwKICAgICJjbHVzdGVyTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEiLAogICAgIm5vbmNlIjogIlRWeDRqS2FQZ1dQbGRUU203X1N6S1BsSDVIZmhrcHFxcjlieWVDalhkSFUiLAogICAgImlzc3VlZEF0IjogIjIwMjYtMDgtMTdUMDA6MDA6MDBaIiwKICAgICJleHBpcmVzQXQiOiAiMjAyNi0wOC0yNFQwMDowMDowMFoiLAogICAgImNvbm5lY3RLZXlJZCI6ICIwOGU3Mjk1YzhmOWQwNDNlMjJiMmI4MGZkYjE0ODBiMGJlYzA2MGRhY2JjZTdkZTlkZDJlM2Q1ODNmOTNkN2U4IiwKICAgICJ0cnVzdENoYWluIjogWwogICAgICAgIHsKICAgICAgICAgICAgImJ5dGVzIjogImV3b2dJQ0FnSW1admNtMWhkRlpsY25OcGIyNGlPaUFpY25WemRHWnpMbU52Ym01bFkzUXViMlptYkdsdVpTNTBjblZ6ZEV4cGJtc3ZNU0lzQ2lBZ0lDQWljSEp2ZEc5amIyeFdaWEp6YVc5dUlqb2dJbll4SWl3S0lDQWdJQ0p6WlhKcFlXd2lPaUFpWXpJMU9HRTJaR1JqT1RoaVlqVmhOV1U1TkRSbU5qSmxNRFkzT1dFM05HVWlMQW9nSUNBZ0luSnZiR1VpT2lBaWFXNTBaWEp0WldScFlYUmxJaXdLSUNBZ0lDSnBjM04xWlhKTFpYbEpaQ0k2SUNKa1pqSXlaVEk0TURZeE1USmtaV0ppWlRrMU16WTNNbUZoWm1FeE9EWmtOams1WVdZd1pUazNaR1F6Wm1ReVlqQTVabUU0TXpVNU1EQTFabVV6TkRobUlpd0tJQ0FnSUNKemRXSnFaV04wUzJWNVNXUWlPaUFpTURSbFltTTNOR1F6TURBeU1HWTNNamM1T0Roak9UWmpNbVptWWpJeU9ESXhPRGd6WldVMllqSmxOR014TVRkbU5tVTVOVGxrWW1RMk0yWXpNMlE0TnlJc0NpQWdJQ0FpYzNWaWFtVmpkRkIxWW14cFkwdGxlU0k2SUNKQ1JIZzFWbk5YU1ZwS1YzcERTMkZVTURoZmVGTklhR2RoTFdsek9UVmhXVTlvTUcxa1FUVTBOMll5UVd4R1ZFeG1lV1ZhYWpBeGVtVk9hV3ROZFdSalFXWk1WSGcwUkVoWVJHRkhjM0ZRUkZSWmRGWjZSRzhpTEFvZ0lDQWdJbTV2ZEVKbFptOXlaU0k2SUNJeU1ESTJMVEF4TFRBeFZEQXdPakF3T2pBd1dpSXNDaUFnSUNBaWJtOTBRV1owWlhJaU9pQWlNakF5Tnkwd01TMHdNVlF3TURvd01Eb3dNRm9pQ24wSyIsCiAgICAgICAgICAgICJzaWduYXR1cmUiOiB7CiAgICAgICAgICAgICAgICAiYWxnb3JpdGhtIjogIkVTMjU2IiwKICAgICAgICAgICAgICAgICJrZXlJZCI6ICJkZjIyZTI4MDYxMTJkZWJiZTk1MzY3MmFhZmExODZkNjk5YWYwZTk3ZGQzZmQyYjA5ZmE4MzU5MDA1ZmUzNDhmIiwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJsWEl4RG1rdlJYMkNLTF9OU01KX3ltLUhKWTQ4cW9VejRoM2JQUHgzM0lGRXYwRTFnblNPV2QxTTJYcHJtSVZSQ3Z4OHhONUNmWUowTDd3enNEQV9GUSIKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiYnl0ZXMiOiAiZXdvZ0lDQWdJbVp2Y20xaGRGWmxjbk5wYjI0aU9pQWljblZ6ZEdaekxtTnZibTVsWTNRdWIyWm1iR2x1WlM1MGNuVnpkRXhwYm1zdk1TSXNDaUFnSUNBaWNISnZkRzlqYjJ4V1pYSnphVzl1SWpvZ0luWXhJaXdLSUNBZ0lDSnpaWEpwWVd3aU9pQWlOMkptWm1ObU1EYzRaRGMyTkRnNU9XRTNNR1prWWpGaFpqbGlZbU5rTmpJaUxBb2dJQ0FnSW5KdmJHVWlPaUFpYzJsbmJtbHVaeUlzQ2lBZ0lDQWlhWE56ZFdWeVMyVjVTV1FpT2lBaU1EUmxZbU0zTkdRek1EQXlNR1kzTWpjNU9EaGpPVFpqTW1abVlqSXlPREl4T0RnelpXVTJZakpsTkdNeE1UZG1ObVU1TlRsa1ltUTJNMll6TTJRNE55SXNDaUFnSUNBaWMzVmlhbVZqZEV0bGVVbGtJam9nSWpBNFpUY3lPVFZqT0dZNVpEQTBNMlV5TW1JeVlqZ3dabVJpTVRRNE1HSXdZbVZqTURZd1pHRmpZbU5sTjJSbE9XUmtNbVV6WkRVNE0yWTVNMlEzWlRnaUxBb2dJQ0FnSW5OMVltcGxZM1JRZFdKc2FXTkxaWGtpT2lBaVFrZFdTVlUzZVRWU2FXZ3lhRWs0TFZCZmFXd3RSM1ZJVm5Sa1VVeEdlVEpFYUZGbFJsVTNjV2g1WW1KME1qa3hlbE5DYTE5MWVGbEtTazVoUWtSa1pEbDNUVU0wUkdac1dUVlJRbEJVT0cxU05qZEZaMk5CSWl3S0lDQWdJQ0p1YjNSQ1pXWnZjbVVpT2lBaU1qQXlOaTB3T0Mwd01WUXdNRG93TURvd01Gb2lMQW9nSUNBZ0ltNXZkRUZtZEdWeUlqb2dJakl3TWpZdE1EZ3RNekZVTURBNk1EQTZNREJhSWdwOUNnPT0iLAogICAgICAgICAgICAic2lnbmF0dXJlIjogewogICAgICAgICAgICAgICAgImFsZ29yaXRobSI6ICJFUzI1NiIsCiAgICAgICAgICAgICAgICAia2V5SWQiOiAiMDRlYmM3NGQzMDAyMGY3Mjc5ODhjOTZjMmZmYjIyODIxODgzZWU2YjJlNGMxMTdmNmU5NTlkYmQ2M2YzM2Q4NyIsCiAgICAgICAgICAgICAgICAidmFsdWUiOiAiamhrZFozeWpuOC1Od0JxWjZjRUtZVlNsYV9VblVmVFJWVmNxazZJSndUTWx3S1ZyWU8xWEZSVTA1bTBWc0VNMThYM1ppOE96bGxoSVAyUjlxWUMwa0EiCiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "08e7295c8f9d043e22b2b80fdb1480b0bec060dacbce7de9dd2e3d583f93d7e8",
"value": "jiDV4Wy81WwqQwlxVqF0eFTh9jEMnD3mkUWB5XqGmpkSw0WnTdAjuVH1wqRMeUEYkZLDTu7fB5YJRvBN2wrUtA"
}
},
"expected": {
"signatureVerifies": true,
"withinWindow": false,
"accepted": false,
"reason": "CHALLENGE_NOT_YET_VALID"
}
},
{
"name": "challenge declaring an unknown formatVersion",
"artifact": "challenge",
"signerKey": "signing",
"evaluationTime": "2026-08-20T12:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50Q2hhbGxlbmdlLzIiLAogICAgInByb3RvY29sVmVyc2lvbiI6ICJ2MSIsCiAgICAiY2hhbGxlbmdlSWQiOiAiMDE5OGYzYTEtN2YwMC03ZDQwLWJlNTEtM2Y0YTViNmM3ZDgzIiwKICAgICJvcmdhbml6YXRpb25OYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwIiwKICAgICJjbHVzdGVyTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEiLAogICAgIm5vbmNlIjogInp4Y2VGd1hVSkFlVGQyWE5yZVp6bElNVEhMX1RGa1ROd2FjUWNsaWJta0UiLAogICAgImlzc3VlZEF0IjogIjIwMjYtMDgtMTdUMDA6MDA6MDBaIiwKICAgICJleHBpcmVzQXQiOiAiMjAyNi0wOC0yNFQwMDowMDowMFoiLAogICAgImNvbm5lY3RLZXlJZCI6ICIwOGU3Mjk1YzhmOWQwNDNlMjJiMmI4MGZkYjE0ODBiMGJlYzA2MGRhY2JjZTdkZTlkZDJlM2Q1ODNmOTNkN2U4IiwKICAgICJ0cnVzdENoYWluIjogWwogICAgICAgIHsKICAgICAgICAgICAgImJ5dGVzIjogImV3b2dJQ0FnSW1admNtMWhkRlpsY25OcGIyNGlPaUFpY25WemRHWnpMbU52Ym01bFkzUXViMlptYkdsdVpTNTBjblZ6ZEV4cGJtc3ZNU0lzQ2lBZ0lDQWljSEp2ZEc5amIyeFdaWEp6YVc5dUlqb2dJbll4SWl3S0lDQWdJQ0p6WlhKcFlXd2lPaUFpWXpJMU9HRTJaR1JqT1RoaVlqVmhOV1U1TkRSbU5qSmxNRFkzT1dFM05HVWlMQW9nSUNBZ0luSnZiR1VpT2lBaWFXNTBaWEp0WldScFlYUmxJaXdLSUNBZ0lDSnBjM04xWlhKTFpYbEpaQ0k2SUNKa1pqSXlaVEk0TURZeE1USmtaV0ppWlRrMU16WTNNbUZoWm1FeE9EWmtOams1WVdZd1pUazNaR1F6Wm1ReVlqQTVabUU0TXpVNU1EQTFabVV6TkRobUlpd0tJQ0FnSUNKemRXSnFaV04wUzJWNVNXUWlPaUFpTURSbFltTTNOR1F6TURBeU1HWTNNamM1T0Roak9UWmpNbVptWWpJeU9ESXhPRGd6WldVMllqSmxOR014TVRkbU5tVTVOVGxrWW1RMk0yWXpNMlE0TnlJc0NpQWdJQ0FpYzNWaWFtVmpkRkIxWW14cFkwdGxlU0k2SUNKQ1JIZzFWbk5YU1ZwS1YzcERTMkZVTURoZmVGTklhR2RoTFdsek9UVmhXVTlvTUcxa1FUVTBOMll5UVd4R1ZFeG1lV1ZhYWpBeGVtVk9hV3ROZFdSalFXWk1WSGcwUkVoWVJHRkhjM0ZRUkZSWmRGWjZSRzhpTEFvZ0lDQWdJbTV2ZEVKbFptOXlaU0k2SUNJeU1ESTJMVEF4TFRBeFZEQXdPakF3T2pBd1dpSXNDaUFnSUNBaWJtOTBRV1owWlhJaU9pQWlNakF5Tnkwd01TMHdNVlF3TURvd01Eb3dNRm9pQ24wSyIsCiAgICAgICAgICAgICJzaWduYXR1cmUiOiB7CiAgICAgICAgICAgICAgICAiYWxnb3JpdGhtIjogIkVTMjU2IiwKICAgICAgICAgICAgICAgICJrZXlJZCI6ICJkZjIyZTI4MDYxMTJkZWJiZTk1MzY3MmFhZmExODZkNjk5YWYwZTk3ZGQzZmQyYjA5ZmE4MzU5MDA1ZmUzNDhmIiwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJsWEl4RG1rdlJYMkNLTF9OU01KX3ltLUhKWTQ4cW9VejRoM2JQUHgzM0lGRXYwRTFnblNPV2QxTTJYcHJtSVZSQ3Z4OHhONUNmWUowTDd3enNEQV9GUSIKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiYnl0ZXMiOiAiZXdvZ0lDQWdJbVp2Y20xaGRGWmxjbk5wYjI0aU9pQWljblZ6ZEdaekxtTnZibTVsWTNRdWIyWm1iR2x1WlM1MGNuVnpkRXhwYm1zdk1TSXNDaUFnSUNBaWNISnZkRzlqYjJ4V1pYSnphVzl1SWpvZ0luWXhJaXdLSUNBZ0lDSnpaWEpwWVd3aU9pQWlOMkptWm1ObU1EYzRaRGMyTkRnNU9XRTNNR1prWWpGaFpqbGlZbU5rTmpJaUxBb2dJQ0FnSW5KdmJHVWlPaUFpYzJsbmJtbHVaeUlzQ2lBZ0lDQWlhWE56ZFdWeVMyVjVTV1FpT2lBaU1EUmxZbU0zTkdRek1EQXlNR1kzTWpjNU9EaGpPVFpqTW1abVlqSXlPREl4T0RnelpXVTJZakpsTkdNeE1UZG1ObVU1TlRsa1ltUTJNMll6TTJRNE55SXNDaUFnSUNBaWMzVmlhbVZqZEV0bGVVbGtJam9nSWpBNFpUY3lPVFZqT0dZNVpEQTBNMlV5TW1JeVlqZ3dabVJpTVRRNE1HSXdZbVZqTURZd1pHRmpZbU5sTjJSbE9XUmtNbVV6WkRVNE0yWTVNMlEzWlRnaUxBb2dJQ0FnSW5OMVltcGxZM1JRZFdKc2FXTkxaWGtpT2lBaVFrZFdTVlUzZVRWU2FXZ3lhRWs0TFZCZmFXd3RSM1ZJVm5Sa1VVeEdlVEpFYUZGbFJsVTNjV2g1WW1KME1qa3hlbE5DYTE5MWVGbEtTazVoUWtSa1pEbDNUVU0wUkdac1dUVlJRbEJVT0cxU05qZEZaMk5CSWl3S0lDQWdJQ0p1YjNSQ1pXWnZjbVVpT2lBaU1qQXlOaTB3T0Mwd01WUXdNRG93TURvd01Gb2lMQW9nSUNBZ0ltNXZkRUZtZEdWeUlqb2dJakl3TWpZdE1EZ3RNekZVTURBNk1EQTZNREJhSWdwOUNnPT0iLAogICAgICAgICAgICAic2lnbmF0dXJlIjogewogICAgICAgICAgICAgICAgImFsZ29yaXRobSI6ICJFUzI1NiIsCiAgICAgICAgICAgICAgICAia2V5SWQiOiAiMDRlYmM3NGQzMDAyMGY3Mjc5ODhjOTZjMmZmYjIyODIxODgzZWU2YjJlNGMxMTdmNmU5NTlkYmQ2M2YzM2Q4NyIsCiAgICAgICAgICAgICAgICAidmFsdWUiOiAiamhrZFozeWpuOC1Od0JxWjZjRUtZVlNsYV9VblVmVFJWVmNxazZJSndUTWx3S1ZyWU8xWEZSVTA1bTBWc0VNMThYM1ppOE96bGxoSVAyUjlxWUMwa0EiCiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "08e7295c8f9d043e22b2b80fdb1480b0bec060dacbce7de9dd2e3d583f93d7e8",
"value": "qfe91lyiqI8ICHxDLyGjf34dIWTD2D8xtv9SH0Najv05g8VDKHTesAsmp9wbp0RvEHRm8Zh0gylkgsZnNUM7-w"
}
},
"expected": {
"signatureVerifies": true,
"accepted": false,
"reason": "UNSUPPORTED_FORMAT"
}
},
{
"name": "challenge declaring an unsupported protocol major version",
"artifact": "challenge",
"signerKey": "signing",
"evaluationTime": "2026-08-20T12:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50Q2hhbGxlbmdlLzEiLAogICAgInByb3RvY29sVmVyc2lvbiI6ICJ2MiIsCiAgICAiY2hhbGxlbmdlSWQiOiAiMDE5OGYzYTEtN2YwMC03ZDQwLWJlNTEtM2Y0YTViNmM3ZDgzIiwKICAgICJvcmdhbml6YXRpb25OYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwIiwKICAgICJjbHVzdGVyTmFtZSI6ICJvcmdhbml6YXRpb25zLzAxOThmM2ExLTRjMDAtN2ExMC04YjIxLTBjMWQyZTNmNGE1MC9jbHVzdGVycy8wMTk4ZjNhMS01ZDAwLTdiMjAtOWMzMS0xZDJlM2Y0YTViNjEiLAogICAgIm5vbmNlIjogIkh4WEY1b3lGR3JFdmIyRklRTUhXeXpGSVVHd3ZMeVhvYkJGOENXUnIwNnciLAogICAgImlzc3VlZEF0IjogIjIwMjYtMDgtMTdUMDA6MDA6MDBaIiwKICAgICJleHBpcmVzQXQiOiAiMjAyNi0wOC0yNFQwMDowMDowMFoiLAogICAgImNvbm5lY3RLZXlJZCI6ICIwOGU3Mjk1YzhmOWQwNDNlMjJiMmI4MGZkYjE0ODBiMGJlYzA2MGRhY2JjZTdkZTlkZDJlM2Q1ODNmOTNkN2U4IiwKICAgICJ0cnVzdENoYWluIjogWwogICAgICAgIHsKICAgICAgICAgICAgImJ5dGVzIjogImV3b2dJQ0FnSW1admNtMWhkRlpsY25OcGIyNGlPaUFpY25WemRHWnpMbU52Ym01bFkzUXViMlptYkdsdVpTNTBjblZ6ZEV4cGJtc3ZNU0lzQ2lBZ0lDQWljSEp2ZEc5amIyeFdaWEp6YVc5dUlqb2dJbll4SWl3S0lDQWdJQ0p6WlhKcFlXd2lPaUFpWXpJMU9HRTJaR1JqT1RoaVlqVmhOV1U1TkRSbU5qSmxNRFkzT1dFM05HVWlMQW9nSUNBZ0luSnZiR1VpT2lBaWFXNTBaWEp0WldScFlYUmxJaXdLSUNBZ0lDSnBjM04xWlhKTFpYbEpaQ0k2SUNKa1pqSXlaVEk0TURZeE1USmtaV0ppWlRrMU16WTNNbUZoWm1FeE9EWmtOams1WVdZd1pUazNaR1F6Wm1ReVlqQTVabUU0TXpVNU1EQTFabVV6TkRobUlpd0tJQ0FnSUNKemRXSnFaV04wUzJWNVNXUWlPaUFpTURSbFltTTNOR1F6TURBeU1HWTNNamM1T0Roak9UWmpNbVptWWpJeU9ESXhPRGd6WldVMllqSmxOR014TVRkbU5tVTVOVGxrWW1RMk0yWXpNMlE0TnlJc0NpQWdJQ0FpYzNWaWFtVmpkRkIxWW14cFkwdGxlU0k2SUNKQ1JIZzFWbk5YU1ZwS1YzcERTMkZVTURoZmVGTklhR2RoTFdsek9UVmhXVTlvTUcxa1FUVTBOMll5UVd4R1ZFeG1lV1ZhYWpBeGVtVk9hV3ROZFdSalFXWk1WSGcwUkVoWVJHRkhjM0ZRUkZSWmRGWjZSRzhpTEFvZ0lDQWdJbTV2ZEVKbFptOXlaU0k2SUNJeU1ESTJMVEF4TFRBeFZEQXdPakF3T2pBd1dpSXNDaUFnSUNBaWJtOTBRV1owWlhJaU9pQWlNakF5Tnkwd01TMHdNVlF3TURvd01Eb3dNRm9pQ24wSyIsCiAgICAgICAgICAgICJzaWduYXR1cmUiOiB7CiAgICAgICAgICAgICAgICAiYWxnb3JpdGhtIjogIkVTMjU2IiwKICAgICAgICAgICAgICAgICJrZXlJZCI6ICJkZjIyZTI4MDYxMTJkZWJiZTk1MzY3MmFhZmExODZkNjk5YWYwZTk3ZGQzZmQyYjA5ZmE4MzU5MDA1ZmUzNDhmIiwKICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJsWEl4RG1rdlJYMkNLTF9OU01KX3ltLUhKWTQ4cW9VejRoM2JQUHgzM0lGRXYwRTFnblNPV2QxTTJYcHJtSVZSQ3Z4OHhONUNmWUowTDd3enNEQV9GUSIKICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAiYnl0ZXMiOiAiZXdvZ0lDQWdJbVp2Y20xaGRGWmxjbk5wYjI0aU9pQWljblZ6ZEdaekxtTnZibTVsWTNRdWIyWm1iR2x1WlM1MGNuVnpkRXhwYm1zdk1TSXNDaUFnSUNBaWNISnZkRzlqYjJ4V1pYSnphVzl1SWpvZ0luWXhJaXdLSUNBZ0lDSnpaWEpwWVd3aU9pQWlOMkptWm1ObU1EYzRaRGMyTkRnNU9XRTNNR1prWWpGaFpqbGlZbU5rTmpJaUxBb2dJQ0FnSW5KdmJHVWlPaUFpYzJsbmJtbHVaeUlzQ2lBZ0lDQWlhWE56ZFdWeVMyVjVTV1FpT2lBaU1EUmxZbU0zTkdRek1EQXlNR1kzTWpjNU9EaGpPVFpqTW1abVlqSXlPREl4T0RnelpXVTJZakpsTkdNeE1UZG1ObVU1TlRsa1ltUTJNMll6TTJRNE55SXNDaUFnSUNBaWMzVmlhbVZqZEV0bGVVbGtJam9nSWpBNFpUY3lPVFZqT0dZNVpEQTBNMlV5TW1JeVlqZ3dabVJpTVRRNE1HSXdZbVZqTURZd1pHRmpZbU5sTjJSbE9XUmtNbVV6WkRVNE0yWTVNMlEzWlRnaUxBb2dJQ0FnSW5OMVltcGxZM1JRZFdKc2FXTkxaWGtpT2lBaVFrZFdTVlUzZVRWU2FXZ3lhRWs0TFZCZmFXd3RSM1ZJVm5Sa1VVeEdlVEpFYUZGbFJsVTNjV2g1WW1KME1qa3hlbE5DYTE5MWVGbEtTazVoUWtSa1pEbDNUVU0wUkdac1dUVlJRbEJVT0cxU05qZEZaMk5CSWl3S0lDQWdJQ0p1YjNSQ1pXWnZjbVVpT2lBaU1qQXlOaTB3T0Mwd01WUXdNRG93TURvd01Gb2lMQW9nSUNBZ0ltNXZkRUZtZEdWeUlqb2dJakl3TWpZdE1EZ3RNekZVTURBNk1EQTZNREJhSWdwOUNnPT0iLAogICAgICAgICAgICAic2lnbmF0dXJlIjogewogICAgICAgICAgICAgICAgImFsZ29yaXRobSI6ICJFUzI1NiIsCiAgICAgICAgICAgICAgICAia2V5SWQiOiAiMDRlYmM3NGQzMDAyMGY3Mjc5ODhjOTZjMmZmYjIyODIxODgzZWU2YjJlNGMxMTdmNmU5NTlkYmQ2M2YzM2Q4NyIsCiAgICAgICAgICAgICAgICAidmFsdWUiOiAiamhrZFozeWpuOC1Od0JxWjZjRUtZVlNsYV9VblVmVFJWVmNxazZJSndUTWx3S1ZyWU8xWEZSVTA1bTBWc0VNMThYM1ppOE96bGxoSVAyUjlxWUMwa0EiCiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICBdCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "08e7295c8f9d043e22b2b80fdb1480b0bec060dacbce7de9dd2e3d583f93d7e8",
"value": "yR9NE0AkqEwQT23IGbGMbM377H6d7NEuLIgWSTRpsRFFKPDjZxIeMpYnSgxjAxJwSxl3CTwwfHubEti4oVaRng"
}
},
"expected": {
"signatureVerifies": true,
"accepted": false,
"reason": "UNSUPPORTED_PROTOCOL"
}
},
{
"name": "response naming another organization than the challenge it answers",
"artifact": "response",
"answersChallenge": "challenge signed by a chained signing key under the pinned root",
"signerKey": "device",
"evaluationTime": "2026-08-20T12:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50UmVzcG9uc2UvMSIsCiAgICAicHJvdG9jb2xWZXJzaW9uIjogInYxIiwKICAgICJjaGFsbGVuZ2VJZCI6ICIwMTk4ZjNhMS03ZjAwLTdkNDAtYmU1MS0zZjRhNWI2YzdkODMiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRiNjAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiY2hhbGxlbmdlTm9uY2UiOiAiVFZ4NGpLYVBnV1BsZFRTbTdfU3pLUGxINUhmaGtwcXFyOWJ5ZUNqWGRIVSIsCiAgICAiY2hhbGxlbmdlUHJvb2YiOiAiamlEVjRXeTgxV3dxUXdseFZxRjBlRlRoOWpFTW5EM21rVVdCNVhxR21wa1N3MFduVGRBanVWSDF3cVJNZVVFWWtaTERUdTdmQjVZSlJ2Qk4yd3JVdEEiLAogICAgImRldmljZUtleUlkIjogIjM5Y2EyNGM4YjAyYTU1OWZkOWJlYjJiMWY1ZDE4Y2VkMjBjNGJiMjQ2NTc3YjkyOTE0YWU2ODE0YzNmNzBhY2YiLAogICAgImRldmljZVB1YmxpY0tleSI6ICJCT1R1eUp6RkhqVzQyaFJsckNZM2JtTUJLMWdmdFQzYU5MaVRnQ3NKMV9lTlNkcjFHTTlhWTh3Mzlwb3BWTG9lZHdvZUFHQUJncDYxaEVhd2tFS3FJUDAiLAogICAgImRldmljZU5vbmNlIjogInpldXU4dVlpZDZ1cl9fa2hrMk9YNDJwaHdxUmttTXVEVXpXeUhSYkk5YW8iLAogICAgInByb2R1Y2VkQXQiOiAiMjAyNi0wOC0yMFQxMjowMDowMFoiCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "Lgs5XNYe0XtZDTSLHpv5cxpCLzcZOgXzhJiQUMGBxb8aYng-d1B38yGJndmosm7ZWisFUW_fU7jHYRRgWroX1A"
}
},
"expected": {
"signatureVerifies": true,
"organizationMatches": false,
"accepted": false,
"reason": "ORGANIZATION_MISMATCH"
}
},
{
"name": "response naming another cluster than the challenge it answers",
"artifact": "response",
"answersChallenge": "challenge signed by a chained signing key under the pinned root",
"signerKey": "device",
"evaluationTime": "2026-08-20T12:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50UmVzcG9uc2UvMSIsCiAgICAicHJvdG9jb2xWZXJzaW9uIjogInYxIiwKICAgICJjaGFsbGVuZ2VJZCI6ICIwMTk4ZjNhMS03ZjAwLTdkNDAtYmU1MS0zZjRhNWI2YzdkODMiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YjYwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWM3MiIsCiAgICAiY2hhbGxlbmdlTm9uY2UiOiAiVFZ4NGpLYVBnV1BsZFRTbTdfU3pLUGxINUhmaGtwcXFyOWJ5ZUNqWGRIVSIsCiAgICAiY2hhbGxlbmdlUHJvb2YiOiAiamlEVjRXeTgxV3dxUXdseFZxRjBlRlRoOWpFTW5EM21rVVdCNVhxR21wa1N3MFduVGRBanVWSDF3cVJNZVVFWWtaTERUdTdmQjVZSlJ2Qk4yd3JVdEEiLAogICAgImRldmljZUtleUlkIjogIjM5Y2EyNGM4YjAyYTU1OWZkOWJlYjJiMWY1ZDE4Y2VkMjBjNGJiMjQ2NTc3YjkyOTE0YWU2ODE0YzNmNzBhY2YiLAogICAgImRldmljZVB1YmxpY0tleSI6ICJCT1R1eUp6RkhqVzQyaFJsckNZM2JtTUJLMWdmdFQzYU5MaVRnQ3NKMV9lTlNkcjFHTTlhWTh3Mzlwb3BWTG9lZHdvZUFHQUJncDYxaEVhd2tFS3FJUDAiLAogICAgImRldmljZU5vbmNlIjogIlpNZkc1SUNmWkRfSnNXelVUemk2aGprTnpKRnpTV1dZeWJYNFJEMnBIRkUiLAogICAgInByb2R1Y2VkQXQiOiAiMjAyNi0wOC0yMFQxMjowMDowMFoiCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "6sWuGJE0FXKPBhuyECUews-X6pfM9YDsTJ3ru8Pcljh5M1qPvoJBcvQ78KbwO7A-vYK4eS_s-7XFQ-YvFzCCwA"
}
},
"expected": {
"signatureVerifies": true,
"clusterMatches": false,
"accepted": false,
"reason": "CLUSTER_MISMATCH"
}
},
{
"name": "response echoing a nonce the challenge never carried",
"artifact": "response",
"answersChallenge": "challenge signed by a chained signing key under the pinned root",
"signerKey": "device",
"evaluationTime": "2026-08-20T12:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50UmVzcG9uc2UvMSIsCiAgICAicHJvdG9jb2xWZXJzaW9uIjogInYxIiwKICAgICJjaGFsbGVuZ2VJZCI6ICIwMTk4ZjNhMS03ZjAwLTdkNDAtYmU1MS0zZjRhNWI2YzdkODMiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiY2hhbGxlbmdlTm9uY2UiOiAiRlRpcHJ0bzF1eVpBZDJIbGV1eXFtbGtaUFpzaGJ3S3picHlqbGFuQnpBNCIsCiAgICAiY2hhbGxlbmdlUHJvb2YiOiAiamlEVjRXeTgxV3dxUXdseFZxRjBlRlRoOWpFTW5EM21rVVdCNVhxR21wa1N3MFduVGRBanVWSDF3cVJNZVVFWWtaTERUdTdmQjVZSlJ2Qk4yd3JVdEEiLAogICAgImRldmljZUtleUlkIjogIjM5Y2EyNGM4YjAyYTU1OWZkOWJlYjJiMWY1ZDE4Y2VkMjBjNGJiMjQ2NTc3YjkyOTE0YWU2ODE0YzNmNzBhY2YiLAogICAgImRldmljZVB1YmxpY0tleSI6ICJCT1R1eUp6RkhqVzQyaFJsckNZM2JtTUJLMWdmdFQzYU5MaVRnQ3NKMV9lTlNkcjFHTTlhWTh3Mzlwb3BWTG9lZHdvZUFHQUJncDYxaEVhd2tFS3FJUDAiLAogICAgImRldmljZU5vbmNlIjogIlA4VDhPemJzZFhTQ19RU25SaVNtU0RZakZIajMyTjVXZjJ1ZHF4ejVDakUiLAogICAgInByb2R1Y2VkQXQiOiAiMjAyNi0wOC0yMFQxMjowMDowMFoiCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "CBOfTV8YB8DHBNLN9KOndRjRBT295jVkdaqCZUmExUEomiMRpmM24sOuOHwDNzqBH-oJioRwpDeiVxvAe8cBOg"
}
},
"expected": {
"signatureVerifies": true,
"challengeNonceMatches": false,
"accepted": false,
"reason": "CHALLENGE_PROOF_INVALID"
}
},
{
"name": "response carrying a proof taken from a different challenge",
"artifact": "response",
"answersChallenge": "challenge signed by a chained signing key under the pinned root",
"signerKey": "device",
"evaluationTime": "2026-08-20T12:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50UmVzcG9uc2UvMSIsCiAgICAicHJvdG9jb2xWZXJzaW9uIjogInYxIiwKICAgICJjaGFsbGVuZ2VJZCI6ICIwMTk4ZjNhMS03ZjAwLTdkNDAtYmU1MS0zZjRhNWI2YzdkODMiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiY2hhbGxlbmdlTm9uY2UiOiAiVFZ4NGpLYVBnV1BsZFRTbTdfU3pLUGxINUhmaGtwcXFyOWJ5ZUNqWGRIVSIsCiAgICAiY2hhbGxlbmdlUHJvb2YiOiAiRm9XY3ZoNU9BLV9WbTdiQ1RmX1RRdXcyb0dxNWxPd2pwVmRmWTQ1ZlFSQXk5LVR2SEJDUnI3WjF4NFFDXzViamp0X2hibGVkMGRWbTZla2ZwQzJkdnciLAogICAgImRldmljZUtleUlkIjogIjM5Y2EyNGM4YjAyYTU1OWZkOWJlYjJiMWY1ZDE4Y2VkMjBjNGJiMjQ2NTc3YjkyOTE0YWU2ODE0YzNmNzBhY2YiLAogICAgImRldmljZVB1YmxpY0tleSI6ICJCT1R1eUp6RkhqVzQyaFJsckNZM2JtTUJLMWdmdFQzYU5MaVRnQ3NKMV9lTlNkcjFHTTlhWTh3Mzlwb3BWTG9lZHdvZUFHQUJncDYxaEVhd2tFS3FJUDAiLAogICAgImRldmljZU5vbmNlIjogIm8wdnh5Sm03cklHVjB1eGd1WGtaMmFfaHBXT1RmSEY3dlkwUVBRcTVicE0iLAogICAgInByb2R1Y2VkQXQiOiAiMjAyNi0wOC0yMFQxMjowMDowMFoiCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "K6uiu35Fy9ImZ3LdsIHg_avCJedTk1NqsBxqcQ5q8rocD_Fx_-kzzERCtyIgxHmivljeQf5BGaevJP-BBhR_FQ"
}
},
"expected": {
"signatureVerifies": true,
"challengeProofMatches": false,
"accepted": false,
"reason": "CHALLENGE_PROOF_INVALID"
}
},
{
"name": "response signed by a key other than the device public key it presents",
"artifact": "response",
"answersChallenge": "challenge signed by a chained signing key under the pinned root",
"signerKey": "foreignDevice",
"evaluationTime": "2026-08-20T12:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50UmVzcG9uc2UvMSIsCiAgICAicHJvdG9jb2xWZXJzaW9uIjogInYxIiwKICAgICJjaGFsbGVuZ2VJZCI6ICIwMTk4ZjNhMS03ZjAwLTdkNDAtYmU1MS0zZjRhNWI2YzdkODMiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiY2hhbGxlbmdlTm9uY2UiOiAiVFZ4NGpLYVBnV1BsZFRTbTdfU3pLUGxINUhmaGtwcXFyOWJ5ZUNqWGRIVSIsCiAgICAiY2hhbGxlbmdlUHJvb2YiOiAiamlEVjRXeTgxV3dxUXdseFZxRjBlRlRoOWpFTW5EM21rVVdCNVhxR21wa1N3MFduVGRBanVWSDF3cVJNZVVFWWtaTERUdTdmQjVZSlJ2Qk4yd3JVdEEiLAogICAgImRldmljZUtleUlkIjogIjM5Y2EyNGM4YjAyYTU1OWZkOWJlYjJiMWY1ZDE4Y2VkMjBjNGJiMjQ2NTc3YjkyOTE0YWU2ODE0YzNmNzBhY2YiLAogICAgImRldmljZVB1YmxpY0tleSI6ICJCT1R1eUp6RkhqVzQyaFJsckNZM2JtTUJLMWdmdFQzYU5MaVRnQ3NKMV9lTlNkcjFHTTlhWTh3Mzlwb3BWTG9lZHdvZUFHQUJncDYxaEVhd2tFS3FJUDAiLAogICAgImRldmljZU5vbmNlIjogIm9qUzBudHFIT3BKMzA3WUhIdlg5OUZ0U2tjb1lZR18zQVZpSldGUFZ4UzQiLAogICAgInByb2R1Y2VkQXQiOiAiMjAyNi0wOC0yMFQxMjowMDowMFoiCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "-mAQtMW6JTIndESRTbUIYMBWIkEM_xF8EEWSHfSljXwPOgEJ_59wdsnmfCsIKmWlALjtJRjWZUsqKamERJSLdg"
}
},
"expected": {
"signatureVerifies": false,
"devicePublicKeyIsTheVerifyingKey": false,
"accepted": false,
"reason": "DEVICE_PROOF_INVALID"
}
},
{
"name": "response produced 301 seconds after the challenge expiry tolerance",
"artifact": "response",
"answersChallenge": "challenge signed by a chained signing key under the pinned root",
"signerKey": "device",
"evaluationTime": "2026-08-24T00:06:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50UmVzcG9uc2UvMSIsCiAgICAicHJvdG9jb2xWZXJzaW9uIjogInYxIiwKICAgICJjaGFsbGVuZ2VJZCI6ICIwMTk4ZjNhMS03ZjAwLTdkNDAtYmU1MS0zZjRhNWI2YzdkODMiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiY2hhbGxlbmdlTm9uY2UiOiAiVFZ4NGpLYVBnV1BsZFRTbTdfU3pLUGxINUhmaGtwcXFyOWJ5ZUNqWGRIVSIsCiAgICAiY2hhbGxlbmdlUHJvb2YiOiAiamlEVjRXeTgxV3dxUXdseFZxRjBlRlRoOWpFTW5EM21rVVdCNVhxR21wa1N3MFduVGRBanVWSDF3cVJNZVVFWWtaTERUdTdmQjVZSlJ2Qk4yd3JVdEEiLAogICAgImRldmljZUtleUlkIjogIjM5Y2EyNGM4YjAyYTU1OWZkOWJlYjJiMWY1ZDE4Y2VkMjBjNGJiMjQ2NTc3YjkyOTE0YWU2ODE0YzNmNzBhY2YiLAogICAgImRldmljZVB1YmxpY0tleSI6ICJCT1R1eUp6RkhqVzQyaFJsckNZM2JtTUJLMWdmdFQzYU5MaVRnQ3NKMV9lTlNkcjFHTTlhWTh3Mzlwb3BWTG9lZHdvZUFHQUJncDYxaEVhd2tFS3FJUDAiLAogICAgImRldmljZU5vbmNlIjogIjNwYTRMRVlFeVpSX0t2cXJ2ZndrUllWWGpDVkMzNFN6TkdiWW9rcl95aWMiLAogICAgInByb2R1Y2VkQXQiOiAiMjAyNi0wOC0yNFQwMDowNTowMVoiCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "W-CScIVVVsLmQtopMmZEYt2T0L9wwgCsdBVADpbSMPIOsKv6VwObaCTorGVWcH1rs6nvy4UTQUyNkBIIC9U8Bw"
}
},
"expected": {
"signatureVerifies": true,
"withinWindow": false,
"accepted": false,
"reason": "CHALLENGE_EXPIRED"
}
},
{
"name": "response declaring an unknown formatVersion",
"artifact": "response",
"answersChallenge": "challenge signed by a chained signing key under the pinned root",
"signerKey": "device",
"evaluationTime": "2026-08-20T12:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50UmVzcG9uc2UvMiIsCiAgICAicHJvdG9jb2xWZXJzaW9uIjogInYxIiwKICAgICJjaGFsbGVuZ2VJZCI6ICIwMTk4ZjNhMS03ZjAwLTdkNDAtYmU1MS0zZjRhNWI2YzdkODMiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiY2hhbGxlbmdlTm9uY2UiOiAiVFZ4NGpLYVBnV1BsZFRTbTdfU3pLUGxINUhmaGtwcXFyOWJ5ZUNqWGRIVSIsCiAgICAiY2hhbGxlbmdlUHJvb2YiOiAiamlEVjRXeTgxV3dxUXdseFZxRjBlRlRoOWpFTW5EM21rVVdCNVhxR21wa1N3MFduVGRBanVWSDF3cVJNZVVFWWtaTERUdTdmQjVZSlJ2Qk4yd3JVdEEiLAogICAgImRldmljZUtleUlkIjogIjM5Y2EyNGM4YjAyYTU1OWZkOWJlYjJiMWY1ZDE4Y2VkMjBjNGJiMjQ2NTc3YjkyOTE0YWU2ODE0YzNmNzBhY2YiLAogICAgImRldmljZVB1YmxpY0tleSI6ICJCT1R1eUp6RkhqVzQyaFJsckNZM2JtTUJLMWdmdFQzYU5MaVRnQ3NKMV9lTlNkcjFHTTlhWTh3Mzlwb3BWTG9lZHdvZUFHQUJncDYxaEVhd2tFS3FJUDAiLAogICAgImRldmljZU5vbmNlIjogIlAxVWdKaUZrbXpKd1VRMzhfMmJKR2pkbC02Ri1UQ1F4akUzZzNqRDNVZzAiLAogICAgInByb2R1Y2VkQXQiOiAiMjAyNi0wOC0yMFQxMjowMDowMFoiCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "XYPw4wwC36mBORLmzLRjRJzaY4gU7KaCJDcYY0MC6klPPX9ackSYDFEjRYo1I-qsYHAmCu3iTdbxul6CZ-CwQQ"
}
},
"expected": {
"signatureVerifies": true,
"accepted": false,
"reason": "UNSUPPORTED_FORMAT"
}
},
{
"name": "response declaring an unsupported protocol major version",
"artifact": "response",
"answersChallenge": "challenge signed by a chained signing key under the pinned root",
"signerKey": "device",
"evaluationTime": "2026-08-20T12:00:00Z",
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50UmVzcG9uc2UvMSIsCiAgICAicHJvdG9jb2xWZXJzaW9uIjogInYyIiwKICAgICJjaGFsbGVuZ2VJZCI6ICIwMTk4ZjNhMS03ZjAwLTdkNDAtYmU1MS0zZjRhNWI2YzdkODMiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiY2hhbGxlbmdlTm9uY2UiOiAiVFZ4NGpLYVBnV1BsZFRTbTdfU3pLUGxINUhmaGtwcXFyOWJ5ZUNqWGRIVSIsCiAgICAiY2hhbGxlbmdlUHJvb2YiOiAiamlEVjRXeTgxV3dxUXdseFZxRjBlRlRoOWpFTW5EM21rVVdCNVhxR21wa1N3MFduVGRBanVWSDF3cVJNZVVFWWtaTERUdTdmQjVZSlJ2Qk4yd3JVdEEiLAogICAgImRldmljZUtleUlkIjogIjM5Y2EyNGM4YjAyYTU1OWZkOWJlYjJiMWY1ZDE4Y2VkMjBjNGJiMjQ2NTc3YjkyOTE0YWU2ODE0YzNmNzBhY2YiLAogICAgImRldmljZVB1YmxpY0tleSI6ICJCT1R1eUp6RkhqVzQyaFJsckNZM2JtTUJLMWdmdFQzYU5MaVRnQ3NKMV9lTlNkcjFHTTlhWTh3Mzlwb3BWTG9lZHdvZUFHQUJncDYxaEVhd2tFS3FJUDAiLAogICAgImRldmljZU5vbmNlIjogIm85U0RpUzk0YjFBdkFCNE1waTB2VnBPQnVkd054VlZlT3NBMGZCai1LdUUiLAogICAgInByb2R1Y2VkQXQiOiAiMjAyNi0wOC0yMFQxMjowMDowMFoiCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "ypN6eGOCWi0efKh-hkv_LmwX1pU8WNG0AG8PbZFlqvhGBPO2JFxhvyLz2BlI3rH7U7FBvbebjnlNOanf1blAzQ"
}
},
"expected": {
"signatureVerifies": true,
"accepted": false,
"reason": "UNSUPPORTED_PROTOCOL"
}
},
{
"name": "a byte identical replay of an accepted response",
"artifact": "response",
"answersChallenge": "challenge signed by a chained signing key under the pinned root",
"signerKey": "device",
"evaluationTime": "2026-08-20T12:00:01Z",
"challengeAlreadyConsumed": true,
"document": {
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS5lbnJvbGxtZW50UmVzcG9uc2UvMSIsCiAgICAicHJvdG9jb2xWZXJzaW9uIjogInYxIiwKICAgICJjaGFsbGVuZ2VJZCI6ICIwMTk4ZjNhMS03ZjAwLTdkNDAtYmU1MS0zZjRhNWI2YzdkODMiLAogICAgIm9yZ2FuaXphdGlvbk5hbWUiOiAib3JnYW5pemF0aW9ucy8wMTk4ZjNhMS00YzAwLTdhMTAtOGIyMS0wYzFkMmUzZjRhNTAiLAogICAgImNsdXN0ZXJOYW1lIjogIm9yZ2FuaXphdGlvbnMvMDE5OGYzYTEtNGMwMC03YTEwLThiMjEtMGMxZDJlM2Y0YTUwL2NsdXN0ZXJzLzAxOThmM2ExLTVkMDAtN2IyMC05YzMxLTFkMmUzZjRhNWI2MSIsCiAgICAiY2hhbGxlbmdlTm9uY2UiOiAiVFZ4NGpLYVBnV1BsZFRTbTdfU3pLUGxINUhmaGtwcXFyOWJ5ZUNqWGRIVSIsCiAgICAiY2hhbGxlbmdlUHJvb2YiOiAiamlEVjRXeTgxV3dxUXdseFZxRjBlRlRoOWpFTW5EM21rVVdCNVhxR21wa1N3MFduVGRBanVWSDF3cVJNZVVFWWtaTERUdTdmQjVZSlJ2Qk4yd3JVdEEiLAogICAgImRldmljZUtleUlkIjogIjM5Y2EyNGM4YjAyYTU1OWZkOWJlYjJiMWY1ZDE4Y2VkMjBjNGJiMjQ2NTc3YjkyOTE0YWU2ODE0YzNmNzBhY2YiLAogICAgImRldmljZVB1YmxpY0tleSI6ICJCT1R1eUp6RkhqVzQyaFJsckNZM2JtTUJLMWdmdFQzYU5MaVRnQ3NKMV9lTlNkcjFHTTlhWTh3Mzlwb3BWTG9lZHdvZUFHQUJncDYxaEVhd2tFS3FJUDAiLAogICAgImRldmljZU5vbmNlIjogInB3bUwxVW9jSG5NYVUwa2w2ZVc0M3BfcllOakxBOGtJaUN2TDlibktwUGMiLAogICAgInByb2R1Y2VkQXQiOiAiMjAyNi0wOC0yMFQxMjowMDowMFoiCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"value": "n4joB8c1KbYvw7MSjeGs1BEYeYe8dFpy47Me_iD7MO1gUSKDpGl6MyCxuZ8KWmjzNMUWz1sgREEW8HsgpNlsIQ"
}
},
"expected": {
"signatureVerifies": true,
"accepted": false,
"reason": "ENROLLMENT_REPLAYED"
}
}
]
}
@@ -0,0 +1,125 @@
{
"protocolVersion": "v1",
"fixtureSet": "offline-enrollment",
"fixture": "trust-chain",
"description": "The golden trust chain. bytes fields are standard padded base64 (RFC 4648 section 4) of the exact raw octets of the signed document; signature.value is unpadded base64url (RFC 4648 section 5) of the 64 octet r||s. No private key appears here or anywhere else in this repository: the vectors are for verification conformance, and a producer proves itself with the encoding rules rather than by reproducing these bytes.",
"pinnedRoot": {
"keyId": "df22e2806112debbe953672aafa186d699af0e97dd3fd2b09fa8359005fe348f",
"publicKey": "BFfx-K-FfEA5nK_Rz3IHacvRCkJyQ7JOd1geLyU6HKRZDgNezmVuKhvJ22VhemyjV__Gshk8JGGqOBzYPMD0p6s",
"source": "compiled into official RustFS builds"
},
"chain": [
{
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS50cnVzdExpbmsvMSIsCiAgICAicHJvdG9jb2xWZXJzaW9uIjogInYxIiwKICAgICJzZXJpYWwiOiAiYzI1OGE2ZGRjOThiYjVhNWU5NDRmNjJlMDY3OWE3NGUiLAogICAgInJvbGUiOiAiaW50ZXJtZWRpYXRlIiwKICAgICJpc3N1ZXJLZXlJZCI6ICJkZjIyZTI4MDYxMTJkZWJiZTk1MzY3MmFhZmExODZkNjk5YWYwZTk3ZGQzZmQyYjA5ZmE4MzU5MDA1ZmUzNDhmIiwKICAgICJzdWJqZWN0S2V5SWQiOiAiMDRlYmM3NGQzMDAyMGY3Mjc5ODhjOTZjMmZmYjIyODIxODgzZWU2YjJlNGMxMTdmNmU5NTlkYmQ2M2YzM2Q4NyIsCiAgICAic3ViamVjdFB1YmxpY0tleSI6ICJCRHg1VnNXSVpKV3pDS2FUMDhfeFNIaGdhLWlzOTVhWU9oMG1kQTU0N2YyQWxGVExmeWVaajAxemVOaWtNdWRjQWZMVHg0REhYRGFHc3FQRFRZdFZ6RG8iLAogICAgIm5vdEJlZm9yZSI6ICIyMDI2LTAxLTAxVDAwOjAwOjAwWiIsCiAgICAibm90QWZ0ZXIiOiAiMjAyNy0wMS0wMVQwMDowMDowMFoiCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "df22e2806112debbe953672aafa186d699af0e97dd3fd2b09fa8359005fe348f",
"value": "lXIxDmkvRX2CKL_NSMJ_ym-HJY48qoUz4h3bPPx33IFEv0E1gnSOWd1M2XprmIVRCvx8xN5CfYJ0L7wzsDA_FQ"
}
},
{
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS50cnVzdExpbmsvMSIsCiAgICAicHJvdG9jb2xWZXJzaW9uIjogInYxIiwKICAgICJzZXJpYWwiOiAiN2JmZmNmMDc4ZDc2NDg5OWE3MGZkYjFhZjliYmNkNjIiLAogICAgInJvbGUiOiAic2lnbmluZyIsCiAgICAiaXNzdWVyS2V5SWQiOiAiMDRlYmM3NGQzMDAyMGY3Mjc5ODhjOTZjMmZmYjIyODIxODgzZWU2YjJlNGMxMTdmNmU5NTlkYmQ2M2YzM2Q4NyIsCiAgICAic3ViamVjdEtleUlkIjogIjA4ZTcyOTVjOGY5ZDA0M2UyMmIyYjgwZmRiMTQ4MGIwYmVjMDYwZGFjYmNlN2RlOWRkMmUzZDU4M2Y5M2Q3ZTgiLAogICAgInN1YmplY3RQdWJsaWNLZXkiOiAiQkdWSVU3eTVSaWgyaEk4LVBfaWwtR3VIVnRkUUxGeTJEaFFlRlU3cWh5YmJ0MjkxelNCa191eFlKSk5hQkRkZDl3TUM0RGZsWTVRQlBUOG1SNjdFZ2NBIiwKICAgICJub3RCZWZvcmUiOiAiMjAyNi0wOC0wMVQwMDowMDowMFoiLAogICAgIm5vdEFmdGVyIjogIjIwMjYtMDgtMzFUMDA6MDA6MDBaIgp9Cg==",
"signature": {
"algorithm": "ES256",
"keyId": "04ebc74d30020f727988c96c2ffb22821883ee6b2e4c117f6e959dbd63f33d87",
"value": "jhkdZ3yjn8-NwBqZ6cEKYVSla_UnUfTRVVcqk6IJwTMlwKVrYO1XFRU05m0VsEM18X3Zi8OzllhIP2R9qYC0kA"
}
}
],
"keys": [
{
"role": "enrollment-root",
"name": "root",
"keyId": "df22e2806112debbe953672aafa186d699af0e97dd3fd2b09fa8359005fe348f",
"publicKey": "BFfx-K-FfEA5nK_Rz3IHacvRCkJyQ7JOd1geLyU6HKRZDgNezmVuKhvJ22VhemyjV__Gshk8JGGqOBzYPMD0p6s"
},
{
"role": "intermediate",
"name": "intermediate",
"keyId": "04ebc74d30020f727988c96c2ffb22821883ee6b2e4c117f6e959dbd63f33d87",
"publicKey": "BDx5VsWIZJWzCKaT08_xSHhga-is95aYOh0mdA547f2AlFTLfyeZj01zeNikMudcAfLTx4DHXDaGsqPDTYtVzDo"
},
{
"role": "signing",
"name": "signing",
"keyId": "08e7295c8f9d043e22b2b80fdb1480b0bec060dacbce7de9dd2e3d583f93d7e8",
"publicKey": "BGVIU7y5Rih2hI8-P_il-GuHVtdQLFy2DhQeFU7qhybbt291zSBk_uxYJJNaBDdd9wMC4DflY5QBPT8mR67EgcA"
},
{
"role": "unchained",
"name": "stray",
"keyId": "f6fbe050defded18b50477ace38c9515fb61b8157e57b2f0e7e8ca69c862b6ca",
"publicKey": "BMDdRSCtFB2w1c3buqktv-eGgMJeck5-rYvnTlvvfTuqg2NybM5gZLrnJCasieiR48JF-Sik-4UI_HCQM12ogsA"
},
{
"role": "unpinned-root",
"name": "rogueRoot",
"keyId": "5ff37910aa4d69949e2c488f98d6072f10a3c3e73d776698963872582644f731",
"publicKey": "BB4ldUQqSkfBQhYa10Otr2q43Yaka53dNLVD8nDThP_fVxFH_s04p_gIds6MDef11ukjZAhdgqQu_A8JLW3SzVk"
},
{
"role": "unpinned-signing",
"name": "rogueSigning",
"keyId": "90f38a0dc2c5f948f47887a010ea3cb9e425d0409edb2a4014c7f7539bc22471",
"publicKey": "BIGNnsOqMBI4LPD8JySg77E3vChI78R4AHgolxFcJNt9Z9GsMoVKIe9ZOdJOE38sFZt3oF5crU0HqdziGfiEp9U"
},
{
"role": "device",
"name": "device",
"keyId": "39ca24c8b02a559fd9beb2b1f5d18ced20c4bb246577b92914ae6814c3f70acf",
"publicKey": "BOTuyJzFHjW42hRlrCY3bmMBK1gftT3aNLiTgCsJ1_eNSdr1GM9aY8w39popVLoedwoeAGABgp61hEawkEKqIP0"
},
{
"role": "device",
"name": "foreignDevice",
"keyId": "63d8184f2b2ec6895bb71b999c90192d7718694ba042bad86364b27628f9cb50",
"publicKey": "BMwdwp6a50zSVsIa3NluDPaszxyIm2EeIbdukH38O3etEhoAlHXJrRblAltumHYlku6EBa_S3IBen8T8JOrr6Nk"
},
{
"role": "device",
"name": "revokedDevice",
"keyId": "1f6a0c684716862c2507b48e72de25fbcb7db5c7aa629ff0c17e20b8a387d7ad",
"publicKey": "BA9MuDkIi4fZSw98wCnbuEZUxqheO5Uks4rVTZa477kg30lb-aLAk5b8xM3kgnEq6PNCkSc28x_JaBPwPkBmwYk"
}
],
"alternateChains": [
{
"name": "chain rooted at an unpinned key",
"chain": [
{
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS50cnVzdExpbmsvMSIsCiAgICAicHJvdG9jb2xWZXJzaW9uIjogInYxIiwKICAgICJzZXJpYWwiOiAiMTNjZGI3NDhiMThjMDNiMjA0NzBjZjY2YTJkYjZhZjgiLAogICAgInJvbGUiOiAic2lnbmluZyIsCiAgICAiaXNzdWVyS2V5SWQiOiAiNWZmMzc5MTBhYTRkNjk5NDllMmM0ODhmOThkNjA3MmYxMGEzYzNlNzNkNzc2Njk4OTYzODcyNTgyNjQ0ZjczMSIsCiAgICAic3ViamVjdEtleUlkIjogIjkwZjM4YTBkYzJjNWY5NDhmNDc4ODdhMDEwZWEzY2I5ZTQyNWQwNDA5ZWRiMmE0MDE0YzdmNzUzOWJjMjI0NzEiLAogICAgInN1YmplY3RQdWJsaWNLZXkiOiAiQklHTm5zT3FNQkk0TFBEOEp5U2c3N0UzdkNoSTc4UjRBSGdvbHhGY0pOdDlaOUdzTW9WS0llOVpPZEpPRTM4c0ZadDNvRjVjclUwSHFkemlHZmlFcDlVIiwKICAgICJub3RCZWZvcmUiOiAiMjAyNi0wOC0wMVQwMDowMDowMFoiLAogICAgIm5vdEFmdGVyIjogIjIwMjYtMDgtMzFUMDA6MDA6MDBaIgp9Cg==",
"signature": {
"algorithm": "ES256",
"keyId": "5ff37910aa4d69949e2c488f98d6072f10a3c3e73d776698963872582644f731",
"value": "suxUbZ9Is1liQlhjfwXQoTeze4me9f7Qddr3299lmzpn-8VzH2kgTA5YvM9Bi-b4ATeHNLPBmkFDhaVoxX64Lw"
}
}
],
"reason": "ENROLLMENT_ROOT_UNKNOWN",
"note": "Internally consistent and correctly signed. It fails only because its root is not pinned, which is exactly what trust on first use would have accepted."
},
{
"name": "signing link already expired when the challenge was issued",
"chain": [
{
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS50cnVzdExpbmsvMSIsCiAgICAicHJvdG9jb2xWZXJzaW9uIjogInYxIiwKICAgICJzZXJpYWwiOiAiYzI1OGE2ZGRjOThiYjVhNWU5NDRmNjJlMDY3OWE3NGUiLAogICAgInJvbGUiOiAiaW50ZXJtZWRpYXRlIiwKICAgICJpc3N1ZXJLZXlJZCI6ICJkZjIyZTI4MDYxMTJkZWJiZTk1MzY3MmFhZmExODZkNjk5YWYwZTk3ZGQzZmQyYjA5ZmE4MzU5MDA1ZmUzNDhmIiwKICAgICJzdWJqZWN0S2V5SWQiOiAiMDRlYmM3NGQzMDAyMGY3Mjc5ODhjOTZjMmZmYjIyODIxODgzZWU2YjJlNGMxMTdmNmU5NTlkYmQ2M2YzM2Q4NyIsCiAgICAic3ViamVjdFB1YmxpY0tleSI6ICJCRHg1VnNXSVpKV3pDS2FUMDhfeFNIaGdhLWlzOTVhWU9oMG1kQTU0N2YyQWxGVExmeWVaajAxemVOaWtNdWRjQWZMVHg0REhYRGFHc3FQRFRZdFZ6RG8iLAogICAgIm5vdEJlZm9yZSI6ICIyMDI2LTAxLTAxVDAwOjAwOjAwWiIsCiAgICAibm90QWZ0ZXIiOiAiMjAyNy0wMS0wMVQwMDowMDowMFoiCn0K",
"signature": {
"algorithm": "ES256",
"keyId": "df22e2806112debbe953672aafa186d699af0e97dd3fd2b09fa8359005fe348f",
"value": "lXIxDmkvRX2CKL_NSMJ_ym-HJY48qoUz4h3bPPx33IFEv0E1gnSOWd1M2XprmIVRCvx8xN5CfYJ0L7wzsDA_FQ"
}
},
{
"bytes": "ewogICAgImZvcm1hdFZlcnNpb24iOiAicnVzdGZzLmNvbm5lY3Qub2ZmbGluZS50cnVzdExpbmsvMSIsCiAgICAicHJvdG9jb2xWZXJzaW9uIjogInYxIiwKICAgICJzZXJpYWwiOiAiY2EwOWYzZTVhZDkxOTEwNmQxMjZjODRlZGRjNDViZTgiLAogICAgInJvbGUiOiAic2lnbmluZyIsCiAgICAiaXNzdWVyS2V5SWQiOiAiMDRlYmM3NGQzMDAyMGY3Mjc5ODhjOTZjMmZmYjIyODIxODgzZWU2YjJlNGMxMTdmNmU5NTlkYmQ2M2YzM2Q4NyIsCiAgICAic3ViamVjdEtleUlkIjogIjA4ZTcyOTVjOGY5ZDA0M2UyMmIyYjgwZmRiMTQ4MGIwYmVjMDYwZGFjYmNlN2RlOWRkMmUzZDU4M2Y5M2Q3ZTgiLAogICAgInN1YmplY3RQdWJsaWNLZXkiOiAiQkdWSVU3eTVSaWgyaEk4LVBfaWwtR3VIVnRkUUxGeTJEaFFlRlU3cWh5YmJ0MjkxelNCa191eFlKSk5hQkRkZDl3TUM0RGZsWTVRQlBUOG1SNjdFZ2NBIiwKICAgICJub3RCZWZvcmUiOiAiMjAyNi0wNi0wMVQwMDowMDowMFoiLAogICAgIm5vdEFmdGVyIjogIjIwMjYtMDctMDFUMDA6MDA6MDBaIgp9Cg==",
"signature": {
"algorithm": "ES256",
"keyId": "04ebc74d30020f727988c96c2ffb22821883ee6b2e4c117f6e959dbd63f33d87",
"value": "SX75WdOVLvphRMcce0xq7W5-Er0HSVhBcsUng1Uv8RAPR23prCH5oJOF06A_h7xHAj9XL7YeuxRStPs_pkTNrA"
}
}
],
"reason": "TRUST_CHAIN_INVALID",
"note": "notAfter is 2026-07-01T00:00:00Z and the challenge issuedAt is 2026-08-17T00:00:00Z."
}
]
}
@@ -0,0 +1,299 @@
{
"protocolVersion": "v1",
"fixtureSet": "offline-enrollment",
"fixture": "trust-model",
"description": "The frozen offline trust model: how an air-gapped device and Connect authenticate signed artifacts to each other without a network, a certificate, or trust on first use. R05 (the RustFS CLI) and R07 (the bundle writer) implement against this file; api/tests/Feature/Diagnostics/OfflineTrustFixtureTest.php replays it.",
"signature": {
"signatureAlgorithm": "ES256",
"curve": "P-256",
"hash": "SHA-256",
"signatureEncoding": "fixed-width-r-s",
"signatureLengthBytes": 64,
"signatureTransferEncoding": "base64url-unpadded",
"signatureValuePattern": "^[A-Za-z0-9_-]{86}$",
"lowSRequired": true,
"groupOrder": "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551",
"maxS": "7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a8",
"publicKeyEncoding": "sec1-uncompressed",
"publicKeyLengthBytes": 65,
"publicKeyTransferEncoding": "base64url-unpadded",
"subjectPublicKeyInfoDerPrefix": "3059301306072a8648ce3d020106082a8648ce3d030107034200",
"keyIdAlgorithm": "SHA-256",
"keyIdOver": "DER SubjectPublicKeyInfo",
"keyIdEncoding": "lowercase-hex",
"keyIdPattern": "^[0-9a-f]{64}$",
"documentTransferEncoding": "base64-padded"
},
"domainSeparation": {
"rule": "signatureInput = domainSeparationTag || 0x00 || the exact raw octets of the signed document as transmitted",
"separatorByte": "0x00",
"tagEncoding": "US-ASCII, no terminator beyond the single 0x00 separator",
"reserialisationPermitted": false,
"canonicalisationPermitted": false,
"note": "A verifier never parses, re-encodes, re-indents, reorders, or normalises a document before verifying it. It hashes the bytes it received. Parsing happens only after the signature over those exact bytes has verified.",
"tags": {
"trustLink": "rustfs-offline-trust-link-v1",
"enrollmentChallenge": "rustfs-offline-enrollment-challenge-v1",
"enrollmentResponse": "rustfs-offline-enrollment-response-v1",
"supportBundleManifest": "rustfs-support-bundle-v1"
}
},
"verifierMustReject": [
"A signature that is not exactly 64 octets of fixed-width r||s.",
"A DER or any other ASN.1 encoded signature, even when it decodes to the same r and s.",
"A signature encoded with the standard base64 alphabet or with = padding.",
"A signature whose r or s is zero, or is greater than or equal to the group order.",
"A signature whose s is greater than half the group order, even though such a signature verifies mathematically. ECDSA is malleable and only the low-S form is a canonical artifact identity.",
"An algorithm value other than ES256, including a downgrade to a hash other than SHA-256.",
"A public key that is not a 65 octet uncompressed SEC1 point on P-256, and any compressed or hybrid point form.",
"A keyId that is not the lowercase SHA-256 hex of the DER SubjectPublicKeyInfo built from the accompanying public key.",
"A signature checked against re-serialised, re-indented, key-reordered, or otherwise regenerated document bytes rather than the received octets.",
"A document that verifies under one domain separation tag being accepted for another artifact type."
],
"rejectedSignatureEncodings": [
{
"name": "high-S signature over an otherwise valid challenge",
"value": "jiDV4Wy81WwqQwlxVqF0eFTh9jEMnD3mkUWB5XqGmpntPLpXsi_cR64KPVuzhr7nK1Q3Xrg4lu7qctp1IVhQnQ",
"acceptedByALenientVerifier": true,
"reason": "SIGNATURE_NOT_CANONICAL",
"note": "The malleated pair (r, n - s) of a valid signature. Every ECDSA library accepts it, which is exactly why the encoding rule and not the library has to reject it."
},
{
"name": "DER encoded signature",
"value": "MEUCIQCOINXhbLzVbCpDCXFWoXR4VOH2MQycPeaRRYHleoaamQIgEsNFp03QI7lR9cKkTHlBGJGSw07u3weWCUbwTdsK1LQ",
"acceptedByALenientVerifier": true,
"reason": "SIGNATURE_MALFORMED",
"note": "The same r and s in ASN.1. A verifier that hands whatever it decoded to its library accepts it; this surface has exactly one signature encoding."
},
{
"name": "padded base64url signature",
"value": "jiDV4Wy81WwqQwlxVqF0eFTh9jEMnD3mkUWB5XqGmpkSw0WnTdAjuVH1wqRMeUEYkZLDTu7fB5YJRvBN2wrUtA==",
"acceptedByALenientVerifier": true,
"reason": "SIGNATURE_MALFORMED",
"note": "The same 64 octets with = padding. Two spellings of one signature would make the signature useless as an artifact identity."
},
{
"name": "truncated signature",
"value": "jiDV4Wy81WwqQwlxVqF0eFTh9jEMnD3mkUWB5XqGmpkSw0WnTdAjuVH1wqRMeUEYkZLDTu7fB5YJRvBN",
"acceptedByALenientVerifier": false,
"reason": "SIGNATURE_MALFORMED",
"note": "Sixty octets. Left-padding it back to 64 would change r, so a verifier must reject rather than repair."
},
{
"name": "zero r and zero s",
"value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"acceptedByALenientVerifier": false,
"reason": "SIGNATURE_MALFORMED",
"note": "Well formed in length and alphabet, and out of range in value."
}
],
"verificationOrder": {
"principle": "Parse as late as the verification key allows, and treat anything read before the signature verified as untrusted routing information rather than as a fact.",
"enrollmentChallenge": {
"note": "A challenge carries its own chain, so the CLI must read structure before it can verify anything. The pre-parse yields only trustChain, connectKeyId, and issuedAt, and none of them is believed: the chain has to close on a pinned root, and the challenge signature has to verify, before any other field is used.",
"steps": [
"check the signature encoding",
"pre-parse the untrusted document for trustChain, connectKeyId, and issuedAt",
"reject unless trustChain[0].issuerKeyId is a pinned root",
"verify every trust link against its issuer and its validity at issuedAt",
"reject unless connectKeyId is the subject of the last link",
"verify the challenge signature over the received octets",
"only now read protocolVersion, then formatVersion",
"check the freshness window"
]
},
"enrollmentResponse": {
"note": "A response presents the device key it is enrolling, so Connect necessarily reads that key from the document. Proof of possession is what makes it safe: the presented key must be the key that signed the presenting document.",
"steps": [
"check the signature encoding",
"reject unless deviceKeyId is the fingerprint of devicePublicKey and the signature verifies under devicePublicKey",
"only now read protocolVersion, then formatVersion",
"compare organization, then cluster, against the stored challenge",
"compare challengeId, challengeNonce, and challengeProof against the stored challenge",
"check the freshness window against producedAt, then against the receive time",
"reject a challenge that was already consumed"
]
},
"supportBundleManifest": {
"note": "Connect already knows which device key is effective for a bundle, so nothing has to be parsed to find the verification key. Verification comes first and the manifest is not parsed at all until it has.",
"steps": [
"check the signature encoding",
"resolve the detached signature keyId against the enrolled keys of the named bundle device",
"verify the manifest signature over the raw manifest octets",
"only now parse the manifest, and read protocolVersion, then formatVersion",
"reject unless the manifest deviceKeyId is the key that signed it",
"compare organization, cluster, and device against the authorised bundle",
"check redactionVersion, then every entry type and classification",
"check the freshness window",
"reject a replayed nonce"
]
}
},
"trustAnchor": {
"trustOnFirstUse": false,
"rootLearnedFromArtifact": false,
"rootShippedWithArtifact": false,
"distribution": "The hosted RustFS enrollment root public key fingerprint is compiled into official RustFS builds. It is never read from a challenge, a bundle, a configuration file, or an operator prompt.",
"pinnedRootKeyIds": [
"df22e2806112debbe953672aafa186d699af0e97dd3fd2b09fa8359005fe348f"
],
"pinnedRootPublicKeys": [
{
"keyId": "df22e2806112debbe953672aafa186d699af0e97dd3fd2b09fa8359005fe348f",
"publicKey": "BFfx-K-FfEA5nK_Rz3IHacvRCkJyQ7JOd1geLyU6HKRZDgNezmVuKhvJ22VhemyjV__Gshk8JGGqOBzYPMD0p6s"
}
],
"chainLinkCount": 2,
"maxChainLinkCount": 2,
"chainOrder": "index 0 is issued by a pinned root, index 1 is issued by the subject of index 0",
"note": "Because no root is ever learned at runtime, an operator cannot be socially engineered into accepting an attacker root, and a stolen intermediate cannot mint its own root. The cost is that a root rollover requires redistributing the RustFS build, which is stated in rollover.root."
},
"keyHierarchy": [
{
"role": "enrollment-root",
"holder": "RustFS",
"algorithm": "ES256",
"signs": [
"intermediate trust links"
],
"maxValiditySeconds": null,
"distribution": "pinned in official RustFS builds"
},
{
"role": "intermediate",
"holder": "RustFS Connect",
"algorithm": "ES256",
"signs": [
"signing trust links"
],
"maxValiditySeconds": 31536000,
"distribution": "carried inside every challenge as a signed trust link"
},
{
"role": "signing",
"holder": "RustFS Connect",
"algorithm": "ES256",
"signs": [
"enrollment challenges"
],
"maxValiditySeconds": 2678400,
"distribution": "carried inside every challenge as a signed trust link"
},
{
"role": "device",
"holder": "the air-gapped cluster device",
"algorithm": "ES256",
"signs": [
"enrollment responses",
"support bundle manifests"
],
"maxValiditySeconds": null,
"distribution": "generated on the device, never transmitted; only the public point leaves it"
}
],
"rollover": {
"root": {
"mechanism": "A new root is pinned by shipping a new official RustFS build. Both the outgoing and the incoming root stay pinned for the overlap window so a device running either build can still enroll.",
"maxOverlapSeconds": 31536000,
"learnedAtRuntime": false,
"consequence": "A device that never takes a new build eventually cannot enroll. That is the accepted cost of refusing trust on first use."
},
"intermediate": {
"mechanism": "Overlapping links. A challenge carries exactly the chain that validated it when it was issued, so a rolled intermediate does not invalidate challenges already in the field.",
"maxValiditySeconds": 31536000,
"validityEvaluatedAgainst": "the issuedAt of the challenge that carries the link, with no skew tolerance"
},
"signing": {
"mechanism": "Overlapping links, rotated at least monthly.",
"maxValiditySeconds": 2678400,
"validityEvaluatedAgainst": "the issuedAt of the challenge that carries the link, with no skew tolerance"
},
"device": {
"mechanism": "A device key is durable. Replacing it is a new enrollment: a fresh challenge, a fresh response, and a fresh device public key. There is no in-band device key rotation message.",
"maxOverlapSeconds": 604800,
"consequence": "The outgoing device key is revoked when the incoming one becomes effective, so a device never has more than one effective offline key."
}
},
"revocation": {
"device": {
"effect": "immediate",
"authority": "Connect, which holds the device key state and evaluates every artifact it receives",
"retroactive": true,
"note": "An artifact signed before revocation but received after it is still rejected. Revocation is not a validity window and past signatures are not grandfathered.",
"reason": "DEVICE_KEY_REVOKED"
},
"signing": {
"effect": "bounded by link validity",
"authority": "RustFS Connect",
"mechanism": "No CRL and no OCSP: an air-gapped device cannot fetch either, and a revocation list carried inside the artifact would simply be omitted by an attacker. Exposure is bounded by the 31 day signing link validity, and official RustFS builds additionally carry a denylist of revoked keyIds updated with each release."
},
"intermediate": {
"effect": "bounded by link validity",
"authority": "RustFS",
"mechanism": "Same as signing, bounded by the 365 day intermediate link validity plus the build denylist."
},
"root": {
"effect": "requires redistributing official RustFS builds",
"authority": "RustFS",
"mechanism": "There is nothing above the root to revoke it. This asymmetry is deliberate and is the reason the root signs nothing except intermediate links."
},
"asymmetry": "Connect can revoke a device key instantly because Connect holds that state and sees every artifact. A device cannot learn about a revoked Connect key promptly, because it has no network. Every offline-facing key therefore has a short validity instead of a revocation channel."
},
"clockSkew": {
"toleranceSeconds": 300,
"deviceClockAuthority": "advisory",
"challengeWindow": "accepted while verifierNow is within [issuedAt - 300, expiresAt + 300]",
"chainLinkWindow": "each link must satisfy notBefore <= challenge.issuedAt <= notAfter, evaluated with no tolerance because the issuer controls both values",
"maxChallengeLifetimeSeconds": 604800,
"maxManifestAgeSeconds": 2592000,
"maxManifestFutureSkewSeconds": 300,
"responseWindow": "producedAt must fall within [challenge.issuedAt - 300, challenge.expiresAt + 300]",
"note": "ADR 0003 already treats client clocks as advisory for heartbeat freshness. An air-gapped device is worse: it may have no synchronised clock at all. Every window is therefore evaluated against the Connect clock for artifacts Connect receives, and against the issuer-supplied issuedAt for the chain a device validates locally."
},
"replay": {
"challengeIdSingleUse": true,
"consumedChallengeRetention": "until expiresAt + 300 seconds, so a late replay still meets a stored record rather than an empty table",
"nonceLengthBytes": 32,
"nonceEncoding": "base64url-unpadded",
"noncePattern": "^[A-Za-z0-9_-]{43}$",
"manifestNonceUniqueness": "unique per organization, cluster, and device for at least maxManifestAgeSeconds",
"signatureCanonicality": "Low-S normalisation makes the 64 octet signature a canonical identity for the artifact, so a malleated copy is not a second distinct artifact and cannot slip past deduplication.",
"reasons": [
"ENROLLMENT_REPLAYED",
"BUNDLE_REPLAYED"
]
},
"versioning": {
"protocolVersionRule": "Identical to protocol/agent/v1/authentication.md: protocolVersion is v<major> matching ^v[1-9][0-9]{0,3}$, Connect supports major 1, and anything else fails closed with UNSUPPORTED_PROTOCOL and HTTP 400. Nothing is partially processed.",
"supportedMajorVersions": [
1
],
"protocolVersionPattern": "^v[1-9][0-9]{0,3}$",
"formatVersionRule": "formatVersion is matched exactly against the closed list below. An unknown value fails closed with UNSUPPORTED_FORMAT and is never guessed at, prefix-matched, or downgraded.",
"supportedFormatVersions": [
"rustfs.connect.offline.trustLink/1",
"rustfs.connect.offline.enrollmentChallenge/1",
"rustfs.connect.offline.enrollmentResponse/1",
"rustfs.connect.support.bundleManifest/1"
],
"additive": {
"unknownOptionalFieldPolicy": "accept-and-discard",
"unknownOptionalEntryFieldPolicy": "accept-and-discard",
"echoedBack": false,
"stored": false,
"absentOptionalFieldPolicy": "take the documented default",
"requiredFieldsMayBeAdded": false,
"existingFieldsMayChangeTypeOrMeaning": false,
"signatureImpact": "None. An unknown field is inside the signed octets and is therefore authentic; discarding it after verification cannot change the signature input, because the input is the received bytes and not a projection of the parsed document."
},
"closedEnumerations": {
"note": "Enumerated values are closed and are NOT additive. Only optional fields are additive. An unrecognised enumerated value is a rejection, never a discard, because silently ignoring an unknown classification or entry type would let a producer widen what it collects.",
"enumerations": [
"signature.algorithm",
"trustLink.role",
"manifest.entries[].type",
"manifest.entries[].classification"
]
}
}
}
@@ -0,0 +1,4 @@
38e793226476bdb5f74c704c23ccc0e9ec09d51be3b51791e8b2cdfbf27a5c02 allowed-vectors.json
014b06540e664f6e38f0174746a418069d2677a2a1a4ef96c17f16dec7e47886 rejection-vectors.json
fdf1d8f4c7ed6f96026e86c7d89f56e5e08269c0b7a49a1e3c3880e5d023c600 ruleset.json
5e349d5121037a09a9b1009b08feac9f5fb4b14cd6548419b88bd9f032929b55 secret-vectors.json
@@ -0,0 +1,104 @@
{
"protocolVersion": "v1",
"fixtureSet": "redaction",
"fixture": "allowed-vectors",
"description": "Ordinary collected documents. Every vector must survive with redactedCount 0: versions, counts, capacities, flags, and single-case digests are exactly what the L0 and L1 registry exists to collect, and over-redaction would make a support bundle useless.",
"vectors": [
{
"name": "a complete heartbeat payload",
"source": "heartbeat",
"document": {
"protocolVersion": 1,
"agentVersion": "rustfs-agent/1.19.4",
"capabilities": [
"inventory",
"events",
"jobs"
],
"sequence": 8421,
"clientTime": "2026-08-17T04:05:06Z",
"coarseNodeSummary": {
"total": 8,
"healthy": 7,
"degraded": 1
}
},
"expectedCanonicalJson": "{\"agentVersion\":\"rustfs-agent/1.19.4\",\"capabilities\":[\"inventory\",\"events\",\"jobs\"],\"clientTime\":\"2026-08-17T04:05:06Z\",\"coarseNodeSummary\":{\"degraded\":1,\"healthy\":7,\"total\":8},\"protocolVersion\":1,\"sequence\":8421}"
},
{
"name": "a complete inventory snapshot",
"source": "inventory",
"document": {
"rustfsVersion": "1.19.4",
"osVersion": "Ubuntu 22.04.5 LTS",
"nodeCount": 8,
"driveCount": 96,
"capacityUsedBytes": 412316860416,
"capacityTotalBytes": 1099511627776,
"coarseFlags": {
"degraded": false,
"readOnly": false,
"rebalancing": true
}
},
"expectedCanonicalJson": "{\"capacityTotalBytes\":1099511627776,\"capacityUsedBytes\":412316860416,\"coarseFlags\":{\"degraded\":false,\"readOnly\":false,\"rebalancing\":true},\"driveCount\":96,\"nodeCount\":8,\"osVersion\":\"Ubuntu 22.04.5 LTS\",\"rustfsVersion\":\"1.19.4\"}"
},
{
"name": "a complete offline diagnostic covering L0 and L1",
"source": "offline-diagnostic",
"document": {
"rustfsVersion": "1.19.4",
"nodeCount": 8,
"driveCount": 96,
"capacityUsedBytes": 412316860416,
"capacityTotalBytes": 1099511627776,
"coarseHealthFlags": {
"degraded": false
},
"osSummary": "Ubuntu 22.04.5 LTS",
"kernelSummary": "6.8.0-51-generic",
"cpuSummary": {
"architecture": "aarch64",
"cores": 64
},
"memorySummary": {
"totalBytes": 274877906944,
"underPressure": false
},
"filesystemSummary": [
"xfs",
"ext4"
],
"networkSummary": {
"interfaceCount": 4,
"bondCount": 2
}
},
"expectedCanonicalJson": "{\"capacityTotalBytes\":1099511627776,\"capacityUsedBytes\":412316860416,\"coarseHealthFlags\":{\"degraded\":false},\"cpuSummary\":{\"architecture\":\"aarch64\",\"cores\":64},\"driveCount\":96,\"filesystemSummary\":[\"xfs\",\"ext4\"],\"kernelSummary\":\"6.8.0-51-generic\",\"memorySummary\":{\"totalBytes\":274877906944,\"underPressure\":false},\"networkSummary\":{\"bondCount\":2,\"interfaceCount\":4},\"nodeCount\":8,\"osSummary\":\"Ubuntu 22.04.5 LTS\",\"rustfsVersion\":\"1.19.4\"}"
},
{
"name": "single-case digests and long identifiers are not mistaken for key material",
"source": "inventory",
"document": {
"rustfsVersion": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
"osVersion": "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"
},
"expectedCanonicalJson": "{\"osVersion\":\"E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855\",\"rustfsVersion\":\"da39a3ee5e6b4b0d3255bfef95601890afd80709\"}"
},
{
"name": "extreme but ordinary capacity and count values survive unchanged",
"source": "inventory",
"document": {
"nodeCount": 0,
"driveCount": 1024,
"capacityUsedBytes": 0,
"capacityTotalBytes": 9223372036854775807,
"coarseFlags": {
"degraded": null,
"utilisation": 0.9375
}
},
"expectedCanonicalJson": "{\"capacityTotalBytes\":9223372036854775807,\"capacityUsedBytes\":0,\"coarseFlags\":{\"degraded\":null,\"utilisation\":0.9375},\"driveCount\":1024,\"nodeCount\":0}"
}
]
}
@@ -0,0 +1,107 @@
{
"protocolVersion": "v1",
"fixtureSet": "redaction",
"fixture": "rejection-vectors",
"description": "The input budget. A document the engine cannot scan inside its budget is refused whole rather than partially redacted, and every refusal message is built from a literal and an integer so no part of the input reaches the message or the stack trace.",
"builders": {
"literal": "Use document as it stands.",
"nestedDepth": "{field: {nested: {... depth times ...: {leaf: 1}}}}.",
"listNodes": "{field: [1 repeated count times]}.",
"bulkStrings": "{field: {f0..f(entries-1): 'a' repeated valueBytes times}}.",
"unrepresentable": "{field: NaN}, a float no JSON encoder can represent."
},
"vectors": [
{
"name": "a surface that is not a registered collection surface",
"source": "support-bundle",
"build": {
"kind": "literal",
"document": {
"rustfsVersion": "1.19.4"
}
},
"expected": {
"refused": true,
"message": "Redaction refused the document: it names no registered collection surface."
}
},
{
"name": "a document larger than the input budget",
"source": "inventory",
"build": {
"kind": "bulkStrings",
"field": "coarseFlags",
"entries": 100,
"valueBytes": 4000
},
"expected": {
"refused": true,
"message": "Redaction refused the document: its size in bytes exceeds the frozen budget of 262144."
}
},
{
"name": "a document nested past the depth budget",
"source": "heartbeat",
"build": {
"kind": "nestedDepth",
"field": "coarseNodeSummary",
"depth": 8
},
"expected": {
"refused": true,
"message": "Redaction refused the document: its nesting depth exceeds the frozen budget of 8."
}
},
{
"name": "a document with more nodes than the node budget",
"source": "heartbeat",
"build": {
"kind": "listNodes",
"field": "capabilities",
"count": 4096
},
"expected": {
"refused": true,
"message": "Redaction refused the document: its node count exceeds the frozen budget of 4096."
}
},
{
"name": "a value no JSON encoder can represent",
"source": "inventory",
"build": {
"kind": "unrepresentable",
"field": "capacityUsedBytes"
},
"expected": {
"refused": true,
"message": "Redaction refused the document: it is not representable as JSON."
}
},
{
"name": "the deepest document the depth budget still accepts",
"source": "heartbeat",
"build": {
"kind": "nestedDepth",
"field": "coarseNodeSummary",
"depth": 7
},
"expected": {
"refused": false,
"redactedCount": 0
}
},
{
"name": "the largest node count the node budget still accepts",
"source": "heartbeat",
"build": {
"kind": "listNodes",
"field": "capabilities",
"count": 4095
},
"expected": {
"refused": false,
"redactedCount": 0
}
}
]
}
@@ -0,0 +1,116 @@
{
"protocolVersion": "v1",
"fixtureSet": "redaction",
"fixture": "ruleset",
"description": "The frozen deterministic redaction contract. A support bundle manifest stores redactionVersion and rulesetHash so a reader can prove which rules produced a redacted document, and a RustFS agent reproduces the same decisions from canonicalForm alone.",
"redactionVersion": "rustfs.connect.redaction.v1",
"redactionVersionFormat": "^rustfs\\.connect\\.redaction\\.v[1-9][0-9]*$",
"redactionVersionNotes": [
"An opaque stable identifier, not a semantic version: compare it for equality, never order it.",
"A change to any line of canonicalForm changes rulesetHash and requires a new major."
],
"rulesetHash": "b37436d8e72515394a122d633865b1dc028d4ece349352a0a3a23f52ca4285f3",
"rulesetHashAlgorithm": "sha256",
"rulesetHashInput": "The canonicalForm lines below joined with U+000A and terminated with a final U+000A, encoded as UTF-8.",
"collectionDecision": {
"registry": "protocol/data-collection-fields.json",
"rule": "Stage one is an allow-list. A field id absent from the registry is removed before its value is read, so an unknown, newly invented, or L2/L3 field can never be collected no matter what it contains. The value rules below are stage two and never grant collection."
},
"placeholder": {
"token": "[REDACTED]",
"rule": "One constant token for every redaction, carrying no rule name, no offset, no length, and no digest of the removed value. Redaction is not reversible and the result records no hash of anything it removed."
},
"output": {
"canonicalJson": "Object keys are emitted in ascending byte order and an object that loses every entry is removed from its parent, so the same input and version always produce the same bytes.",
"counts": [
"droppedField: a field the allow-list refused, a key that is not a plain ASCII identifier, or an object left with no entries.",
"redactedValue: a value replaced by the placeholder.",
"redactedOversizeValue: a value replaced because it is longer than maxValueBytes and cannot be scanned within budget."
]
},
"coverage": {
"AWS_ACCESS_KEY_ID": "S3 and AWS access key ids.",
"AWS_SECRET_ACCESS_KEY": "A standalone 40-character S3 secret access key. Mixed case plus a digit is required so a single-case hex digest of the same length is not redacted.",
"BEARER_TOKEN": "HTTP bearer credentials, including a captured Authorization header.",
"CREDENTIAL_ASSIGNMENT": "API keys, registration tokens, session tokens, passphrases, and KMS secrets written as an assignment in a connection string, environment dump, or configuration snippet.",
"JWT": "JSON web tokens presented on their own.",
"PASSWORD_ASSIGNMENT": "A password written as an assignment, including inside a DSN.",
"PEM_PRIVATE_KEY": "Any PEM private key or private key block header, which covers device keys and KMS private material.",
"SESSION_ID_ASSIGNMENT": "Session and CSRF identifiers written as an assignment.",
"URL_CREDENTIALS": "Credentials in a URL or DSN authority. The whole value is replaced, so the host the credential belonged to is not published either."
},
"keyRuleNormalisation": "ASCII-lowercase the key and remove '_', '-', and '.', so secret_access_key, Secret-Access-Key, and secretAccessKey are the same key. A key that is not a plain ASCII identifier is dropped rather than normalised.",
"canonicalForm": [
"version\trustfs.connect.redaction.v1",
"placeholder\t[REDACTED]",
"budget\tmaxInputBytes\t262144",
"budget\tmaxDepth\t8",
"budget\tmaxNodes\t4096",
"budget\tmaxValueBytes\t4096",
"keyPattern\t/^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/D",
"field\theartbeat.agentVersion\tL0",
"field\theartbeat.capabilities\tL0",
"field\theartbeat.clientTime\tL0",
"field\theartbeat.coarseNodeSummary\tL0",
"field\theartbeat.protocolVersion\tL0",
"field\theartbeat.sequence\tL0",
"field\tinventory.capacityTotalBytes\tL0",
"field\tinventory.capacityUsedBytes\tL0",
"field\tinventory.coarseFlags\tL0",
"field\tinventory.driveCount\tL0",
"field\tinventory.nodeCount\tL0",
"field\tinventory.osVersion\tL0",
"field\tinventory.rustfsVersion\tL0",
"field\toffline.capacityTotalBytes\tL0",
"field\toffline.capacityUsedBytes\tL0",
"field\toffline.coarseHealthFlags\tL0",
"field\toffline.cpuSummary\tL1",
"field\toffline.driveCount\tL0",
"field\toffline.filesystemSummary\tL1",
"field\toffline.kernelSummary\tL1",
"field\toffline.memorySummary\tL1",
"field\toffline.networkSummary\tL1",
"field\toffline.nodeCount\tL0",
"field\toffline.osSummary\tL1",
"field\toffline.rustfsVersion\tL0",
"keyRule\taccesskey",
"keyRule\taccesskeyid",
"keyRule\tapikey",
"keyRule\tapitoken",
"keyRule\tauthorization",
"keyRule\tbearertoken",
"keyRule\tcookie",
"keyRule\tcredential",
"keyRule\tcredentials",
"keyRule\tcsrftoken",
"keyRule\tkmskey",
"keyRule\tkmskeyid",
"keyRule\tkmsmasterkey",
"keyRule\tkmssecret",
"keyRule\tpassphrase",
"keyRule\tpasswd",
"keyRule\tpassword",
"keyRule\tprivatekey",
"keyRule\tpwd",
"keyRule\trefreshtoken",
"keyRule\tregistrationtoken",
"keyRule\tsecret",
"keyRule\tsecretaccesskey",
"keyRule\tsecretkey",
"keyRule\tsessioncookie",
"keyRule\tsessionid",
"keyRule\tsessiontoken",
"keyRule\tsigningkey",
"keyRule\ttoken",
"keyRule\txsrftoken",
"valueRule\tAWS_ACCESS_KEY_ID\t/\\b(?:A3T[A-Z0-9]{2}|ABIA|ACCA|AKIA|ASIA)[A-Z0-9]{16}\\b/",
"valueRule\tAWS_SECRET_ACCESS_KEY\t/(?<![A-Za-z0-9+\\/])(?=[A-Za-z0-9+\\/]{0,39}[a-z])(?=[A-Za-z0-9+\\/]{0,39}[A-Z])(?=[A-Za-z0-9+\\/]{0,39}[0-9])[A-Za-z0-9+\\/]{40}(?![A-Za-z0-9+\\/=])/",
"valueRule\tBEARER_TOKEN\t/(?i)\\bbearer\\s{1,8}[A-Za-z0-9\\-._~+\\/]{8,4096}={0,2}/",
"valueRule\tCREDENTIAL_ASSIGNMENT\t/(?i)\\b[a-z0-9_.-]{0,24}(?:access[_.-]?key(?:[_.-]?id)?|api[_.-]?key|credentials?|passphrase|secret(?:[_.-]?key)?|token)\\b\\s{0,8}[:=]\\s{0,8}[\"\\x27]?[A-Za-z0-9\\-._~+\\/=]{8,4096}/",
"valueRule\tJWT\t/\\beyJ[A-Za-z0-9_-]{4,4096}\\.[A-Za-z0-9_-]{4,4096}\\.[A-Za-z0-9_-]{4,4096}/",
"valueRule\tPASSWORD_ASSIGNMENT\t/(?i)\\b(?:passwd|password|pwd)\\b\\s{0,8}[:=]\\s{0,8}\\S/",
"valueRule\tPEM_PRIVATE_KEY\t/-----BEGIN [A-Z0-9 ]{0,32}PRIVATE KEY(?: BLOCK)?-----/",
"valueRule\tSESSION_ID_ASSIGNMENT\t/(?i)\\b(?:csrf[_.-]?token|jsessionid|phpsessid|sess|session|sid|xsrf[_.-]?token)(?:[_.-]?id)?\\b\\s{0,8}[:=]\\s{0,8}[\"\\x27]?[A-Za-z0-9%\\-._~+\\/]{12,4096}/",
"valueRule\tURL_CREDENTIALS\t/\\b[a-zA-Z][a-zA-Z0-9+.\\-]{0,31}:\\/\\/[^\\s\\/@:]{1,256}(?::[^\\s\\/@]{0,256})?@/"
]
}
@@ -0,0 +1,342 @@
{
"protocolVersion": "v1",
"fixtureSet": "redaction",
"fixture": "secret-vectors",
"description": "Realistic secret material presented to the engine. Every literal in secretLiterals must be absent from the redacted output, from any log, and from any exception. The example values are synthetic: the PEM body is not a usable key and the AWS values are the documented example credentials.",
"expectations": [
"expectedCanonicalJson is the exact output bytes for this document at rustfs.connect.redaction.v1.",
"valueRule names the rule that fires for ruleSubject; a null valueRule means the removal comes from the allow-list, a key rule, or the ASCII key rule instead."
],
"vectors": [
{
"name": "an S3 access key id smuggled into the version field",
"source": "inventory",
"valueRule": "AWS_ACCESS_KEY_ID",
"ruleSubject": "AKIAIOSFODNN7EXAMPLE",
"document": {
"rustfsVersion": "AKIAIOSFODNN7EXAMPLE",
"nodeCount": 4
},
"secretLiterals": [
"AKIAIOSFODNN7EXAMPLE"
],
"expectedCanonicalJson": "{\"nodeCount\":4,\"rustfsVersion\":\"[REDACTED]\"}",
"expectedCounts": {
"droppedField": 0,
"redactedValue": 1,
"redactedOversizeValue": 0
},
"expectedRedactedCount": 1
},
{
"name": "an S3 secret access key standing alone in an offline OS summary",
"source": "offline-diagnostic",
"valueRule": "AWS_SECRET_ACCESS_KEY",
"ruleSubject": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"document": {
"osSummary": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"rustfsVersion": "1.19.4"
},
"secretLiterals": [
"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
],
"expectedCanonicalJson": "{\"osSummary\":\"[REDACTED]\",\"rustfsVersion\":\"1.19.4\"}",
"expectedCounts": {
"droppedField": 0,
"redactedValue": 1,
"redactedOversizeValue": 0
},
"expectedRedactedCount": 1
},
{
"name": "a captured bearer authorization header in a kernel summary",
"source": "offline-diagnostic",
"valueRule": "BEARER_TOKEN",
"ruleSubject": "Linux 6.8.0; upstream call used Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJydXN0ZnMtZGV2aWNlIn0.dBjftJeZ4CVPmB92K27uhbUJU1p1r_wW1gFWFOEjXk",
"document": {
"kernelSummary": "Linux 6.8.0; upstream call used Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJydXN0ZnMtZGV2aWNlIn0.dBjftJeZ4CVPmB92K27uhbUJU1p1r_wW1gFWFOEjXk"
},
"secretLiterals": [
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJydXN0ZnMtZGV2aWNlIn0.dBjftJeZ4CVPmB92K27uhbUJU1p1r_wW1gFWFOEjXk"
],
"expectedCanonicalJson": "{\"kernelSummary\":\"[REDACTED]\"}",
"expectedCounts": {
"droppedField": 0,
"redactedValue": 1,
"redactedOversizeValue": 0
},
"expectedRedactedCount": 1
},
{
"name": "a JSON web token inside a heartbeat capability list",
"source": "heartbeat",
"valueRule": "JWT",
"ruleSubject": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJydXN0ZnMtZGV2aWNlIn0.dBjftJeZ4CVPmB92K27uhbUJU1p1r_wW1gFWFOEjXk",
"document": {
"capabilities": [
"inventory",
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJydXN0ZnMtZGV2aWNlIn0.dBjftJeZ4CVPmB92K27uhbUJU1p1r_wW1gFWFOEjXk",
"jobs"
],
"sequence": 12
},
"secretLiterals": [
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJydXN0ZnMtZGV2aWNlIn0.dBjftJeZ4CVPmB92K27uhbUJU1p1r_wW1gFWFOEjXk"
],
"expectedCanonicalJson": "{\"capabilities\":[\"inventory\",\"[REDACTED]\",\"jobs\"],\"sequence\":12}",
"expectedCounts": {
"droppedField": 0,
"redactedValue": 1,
"redactedOversizeValue": 0
},
"expectedRedactedCount": 1
},
{
"name": "a connection string carrying an embedded password",
"source": "offline-diagnostic",
"valueRule": "URL_CREDENTIALS",
"ruleSubject": "postgresql://rustfs:s3cr3t-p4ss@db.internal:5432/connect",
"document": {
"filesystemSummary": "postgresql://rustfs:s3cr3t-p4ss@db.internal:5432/connect"
},
"secretLiterals": [
"s3cr3t-p4ss",
"db.internal"
],
"expectedCanonicalJson": "{\"filesystemSummary\":\"[REDACTED]\"}",
"expectedCounts": {
"droppedField": 0,
"redactedValue": 1,
"redactedOversizeValue": 0
},
"expectedRedactedCount": 1
},
{
"name": "a PEM private key nested inside a coarse node summary",
"source": "heartbeat",
"valueRule": "PEM_PRIVATE_KEY",
"ruleSubject": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAxEXAMPLEKEYBODYnotarealkey0000000000000000000000\nEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLE=\n-----END RSA PRIVATE KEY-----",
"document": {
"coarseNodeSummary": {
"healthy": 4,
"note": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAxEXAMPLEKEYBODYnotarealkey0000000000000000000000\nEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLE=\n-----END RSA PRIVATE KEY-----"
}
},
"secretLiterals": [
"-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAxEXAMPLEKEYBODYnotarealkey0000000000000000000000\nEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLE=\n-----END RSA PRIVATE KEY-----",
"EXAMPLEKEYBODY"
],
"expectedCanonicalJson": "{\"coarseNodeSummary\":{\"healthy\":4,\"note\":\"[REDACTED]\"}}",
"expectedCounts": {
"droppedField": 0,
"redactedValue": 1,
"redactedOversizeValue": 0
},
"expectedRedactedCount": 1
},
{
"name": "a DSN password assignment in a CPU summary",
"source": "offline-diagnostic",
"valueRule": "PASSWORD_ASSIGNMENT",
"ruleSubject": "Server=db;User Id=rustfs;Password=hunter2xyz;",
"document": {
"cpuSummary": "Server=db;User Id=rustfs;Password=hunter2xyz;"
},
"secretLiterals": [
"hunter2xyz"
],
"expectedCanonicalJson": "{\"cpuSummary\":\"[REDACTED]\"}",
"expectedCounts": {
"droppedField": 0,
"redactedValue": 1,
"redactedOversizeValue": 0
},
"expectedRedactedCount": 1
},
{
"name": "a session identifier assignment appended to an OS version",
"source": "inventory",
"valueRule": "SESSION_ID_ASSIGNMENT",
"ruleSubject": "Ubuntu 22.04.5 LTS; PHPSESSID=9f8a7b6c5d4e3f2a1b0c",
"document": {
"osVersion": "Ubuntu 22.04.5 LTS; PHPSESSID=9f8a7b6c5d4e3f2a1b0c"
},
"secretLiterals": [
"9f8a7b6c5d4e3f2a1b0c"
],
"expectedCanonicalJson": "{\"osVersion\":\"[REDACTED]\"}",
"expectedCounts": {
"droppedField": 0,
"redactedValue": 1,
"redactedOversizeValue": 0
},
"expectedRedactedCount": 1
},
{
"name": "a registration token assignment in an agent version string",
"source": "heartbeat",
"valueRule": "CREDENTIAL_ASSIGNMENT",
"ruleSubject": "rustfs-agent/1.19.4 registration_token=rft-9f8a7b6c5d4e3f2a1b0c",
"document": {
"agentVersion": "rustfs-agent/1.19.4 registration_token=rft-9f8a7b6c5d4e3f2a1b0c"
},
"secretLiterals": [
"rft-9f8a7b6c5d4e3f2a1b0c"
],
"expectedCanonicalJson": "{\"agentVersion\":\"[REDACTED]\"}",
"expectedCounts": {
"droppedField": 0,
"redactedValue": 1,
"redactedOversizeValue": 0
},
"expectedRedactedCount": 1
},
{
"name": "KMS key material recognised by its key name alone",
"source": "inventory",
"valueRule": null,
"ruleSubject": null,
"document": {
"coarseFlags": {
"degraded": false,
"kms_master_key": "AQIDAHhEXAMPLEkmsDataKeyCiphertext"
}
},
"secretLiterals": [
"AQIDAHhEXAMPLEkmsDataKeyCiphertext"
],
"expectedCanonicalJson": "{\"coarseFlags\":{\"degraded\":false,\"kms_master_key\":\"[REDACTED]\"}}",
"expectedCounts": {
"droppedField": 0,
"redactedValue": 1,
"redactedOversizeValue": 0
},
"expectedRedactedCount": 1
},
{
"name": "a secret access key under a separated and mixed-case key name",
"source": "inventory",
"valueRule": null,
"ruleSubject": null,
"document": {
"coarseFlags": {
"Secret-Access-Key": "zz9EXAMPLEkey",
"driveCount": 24
}
},
"secretLiterals": [
"zz9EXAMPLEkey"
],
"expectedCanonicalJson": "{\"coarseFlags\":{\"Secret-Access-Key\":\"[REDACTED]\",\"driveCount\":24}}",
"expectedCounts": {
"droppedField": 0,
"redactedValue": 1,
"redactedOversizeValue": 0
},
"expectedRedactedCount": 1
},
{
"name": "a brand new secret-bearing field that matches no value rule at all",
"source": "inventory",
"valueRule": null,
"ruleSubject": null,
"document": {
"rustfsVersion": "1.19.4",
"vendorSupportCredential": "ordinary-looking-handle-42",
"objectStoreRootPassphrase": "correct horse battery staple"
},
"secretLiterals": [
"ordinary-looking-handle-42",
"correct horse battery staple",
"vendorSupportCredential",
"objectStoreRootPassphrase"
],
"expectedCanonicalJson": "{\"rustfsVersion\":\"1.19.4\"}",
"expectedCounts": {
"droppedField": 2,
"redactedValue": 0,
"redactedOversizeValue": 0
},
"expectedRedactedCount": 2
},
{
"name": "deferred L2 and L3 collection is refused by the allow-list",
"source": "offline-diagnostic",
"valueRule": null,
"ruleSubject": null,
"document": {
"rustfsVersion": "1.19.4",
"configuration": {
"endpoint": "https://s3.example.internal"
},
"logs": [
"level=error msg=\"auth failed for user rustfs\""
],
"traces": "span 8f2c1a",
"profiles": "cpu.pprof"
},
"secretLiterals": [
"s3.example.internal",
"auth failed for user rustfs",
"span 8f2c1a",
"cpu.pprof"
],
"expectedCanonicalJson": "{\"rustfsVersion\":\"1.19.4\"}",
"expectedCounts": {
"droppedField": 4,
"redactedValue": 0,
"redactedOversizeValue": 0
},
"expectedRedactedCount": 4
},
{
"name": "a confusable non-ASCII key inside an allow-listed object is dropped",
"source": "inventory",
"valueRule": null,
"ruleSubject": null,
"document": {
"coarseFlags": {
"ecretAccessKey": "AKIAIOSFODNN7EXAMPLE",
"degraded": true
}
},
"secretLiterals": [
"AKIAIOSFODNN7EXAMPLE",
"ecretAccessKey"
],
"expectedCanonicalJson": "{\"coarseFlags\":{\"degraded\":true}}",
"expectedCounts": {
"droppedField": 1,
"redactedValue": 0,
"redactedOversizeValue": 0
},
"expectedRedactedCount": 1
},
{
"name": "an allow-listed object that loses every entry is removed entirely",
"source": "heartbeat",
"valueRule": null,
"ruleSubject": null,
"document": {
"coarseNodeSummary": {
"perNode": {
"ostname": "node-01.rustfs.internal"
}
},
"sequence": 7
},
"secretLiterals": [
"node-01.rustfs.internal",
"perNode",
"coarseNodeSummary"
],
"expectedCanonicalJson": "{\"sequence\":7}",
"expectedCounts": {
"droppedField": 3,
"redactedValue": 0,
"redactedOversizeValue": 0
},
"expectedRedactedCount": 3
}
]
}
@@ -0,0 +1,4 @@
812b0ba479a4c8d8eb9776e7bcb8d4c4d929bb83f372c03bec064472bca6155a accept-vectors.json
eb197077a2db61ae3114fa52cdeb32715f8060f6ce5f2b9bae7fe7e7f78b4981 error-codes.json
3940cc260b21a8655e5ebbdbeccd06a273d2299ce783eef92b22cacdaebc80e1 reject-vectors.json
58a7126cef796dd0631b2de8d31528267e6281566646a5662dd3ad555a530008 transcript.json
@@ -0,0 +1,101 @@
{
"protocolVersion": "v1",
"fixtureSet": "registration",
"fixture": "accept-vectors",
"description": "Registration proof-of-possession exchanges that verify. Each vector carries the token row Connect rebuilds the transcript from, the request body the device sent, and the exact transcript octets that rebuild produces. evaluatedAt is the Connect clock the vector is evaluated at; the bytes are frozen, so a window is a property of the evaluation and not of them. No vector carries a registration token secret or any private key.",
"vectors": [
{
"name": "device proof over the frozen transcript",
"stage": "proof",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "The reference exchange. Every reject vector below is this one with exactly one input substituted.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwpptE_9atQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "2552ab455a86703b75321cf70c7ee48034f0ad8a56dae0dbdc462c7fbe762477",
"expected": {
"accepted": true,
"reason": null,
"verifiesMathematically": true
}
},
{
"name": "the same certificate request under a second requestId carries its own proof",
"stage": "proof",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "requestId is inside the transcript, so a device that starts a second attempt signs again. It cannot move a proof it already produced onto a new idempotency key.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "7c4d2e10-9f83-4a5b-b6c7-d8e9f0a1b2c3",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "f0YRwKo8HTMuvCBlh0v3OjqCVplGPVfXqfTgxO4slepy1XdL6bYROjXYwKfn7ZZtkXH5PdOfrC37qITLT4cirQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:7c4d2e10-9f83-4a5b-b6c7-d8e9f0a1b2c3\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "561bda4be56f6334379c9832b73c74eca7d01a7cef95ef6ab0c5c13499bbb90c",
"expected": {
"accepted": true,
"reason": null,
"verifiesMathematically": true
}
},
{
"name": "a second device registering against a second token of the same cluster",
"stage": "proof",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "Nothing in the transcript is global: a second key, a second token, and a second nonce produce an unrelated transcript under the same cluster.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-7a00-7c70-8381-8e9fa0b1c2d6",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "5c7e9a0b1d2f3041526374859607b8c9dae0f1023456789abcdef0123456789a",
"expiresUnix": 1787229000,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "5e1b7a36-2c4d-48ef-90ab-1c2d3e4f5061",
"registrationTokenUid": "0198f4b0-7a00-7c70-8381-8e9fa0b1c2d6",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATSvBqHsbWVSUjYckACjTRW7wm+lGHKffqZazn/RBmkdkcSeuERS/160K4N2EdiwQWNU/g0LD+9kzrhI6M7TXrgoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiEAxZXtloz/p6atQT/sqMxjlcHHN7sq/2f2YeND2oGJCYUCIBH+jf269LueLR4dAZIO7AygdPBf3NxW1PgBu3U4UElH",
"proof": {
"algorithm": "ES256",
"value": "gPExu0WlRxfLeDC388xb15VL8yuMEGnbdkz-CapNBscx4JX8bP0JVbkumMQyddX4BpRAldeeQZ_GJjuxrUVn8w"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-7a00-7c70-8381-8e9fa0b1c2d6\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:5e1b7a36-2c4d-48ef-90ab-1c2d3e4f5061\n64:5c7e9a0b1d2f3041526374859607b8c9dae0f1023456789abcdef0123456789a\n10:1787229000\n43:srwagTJq3yDJm_u_cO-u9KLQWyLeJAouFWiFuNLNSMQ\n",
"serverTranscriptSha256": "6873d380ebf6634c3e36b402ab74f3c46471d7f725b747dda1843d14a3a7b655",
"expected": {
"accepted": true,
"reason": null,
"verifiesMathematically": true
}
}
]
}
@@ -0,0 +1,80 @@
{
"protocolVersion": "v1",
"fixtureSet": "registration",
"fixture": "error-codes",
"description": "Frozen ErrorInfo reasons for the registration token exchange and its proof of possession. Clients branch on status and reason, never on message. Two of the reasons a rejected exchange can carry are defined elsewhere and are cited here rather than restated, so this surface can never come to mean something different by them.",
"domain": "rustfs.connect",
"detailType": "type.googleapis.com/google.rpc.ErrorInfo",
"disclosureRules": [
"A rejection never says which of the seven transcript bindings disagreed. Every binding failure is REGISTRATION_PROOF_INVALID.",
"A rejection never says whether a registration token uid exists, whether the presented secret was right, whether the token was already spent, expired, or revoked, or whether another request holds its reservation. Every one of those is REGISTRATION_TOKEN_UNUSABLE.",
"A rejection never contains a registration token secret, a challenge nonce, certificate request octets, or key material.",
"A rejection never reveals the organization or cluster a token belongs to."
],
"reasons": [
{
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"meaning": "The requested protocol major version is missing, malformed, or not supported.",
"definedBy": "protocol/agent/v1/authentication.md",
"note": "Cited, not redefined. The exchange applies the rule already frozen for every agent operation: nothing is partially processed, stored, or echoed."
},
{
"reason": "UNSUPPORTED_ALGORITHM",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"meaning": "proof.algorithm is a value other than ES256.",
"definedBy": "protocol/agent/v1/registration-proof.md",
"note": "The enumeration is closed. An unrecognised algorithm is refused rather than discarded, because a discarded algorithm would leave the proof to be interpreted by whatever its bytes happen to look like."
},
{
"reason": "SIGNATURE_MALFORMED",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"meaning": "proof.value is not 86 unpadded base64url characters decoding to 64 octets whose r and s both lie in [1, n).",
"definedBy": "protocol/agent/v1/registration-proof.md",
"note": "DER, padded base64url, the standard base64 alphabet, a truncated value, and an out-of-range value all land here. The encoding is checked before any key is loaded."
},
{
"reason": "SIGNATURE_NOT_CANONICAL",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"meaning": "proof.value is well formed but its s exceeds half the group order.",
"definedBy": "protocol/agent/v1/registration-proof.md",
"note": "Such a proof verifies mathematically. Only the encoding rule refuses it, which is what makes the 64 octet value a canonical identity for one exchange rather than one of two equally valid spellings."
},
{
"reason": "CERTIFICATE_REQUEST_MALFORMED",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"meaning": "certificateRequest is not exactly one well-formed PKCS#10 DER structure, or its ES256 self-signature does not verify under the key it presents.",
"definedBy": "protocol/agent/v1/registration-proof.md",
"note": "Trailing octets after the outer SEQUENCE are malformed, not ignored: two readers that disagree about where a certificate request ends would disagree about its digest."
},
{
"reason": "DEVICE_KEY_UNSUPPORTED",
"httpStatus": 400,
"status": "INVALID_ARGUMENT",
"meaning": "The SubjectPublicKeyInfo of the certificate request is not an ECDSA key on NIST P-256.",
"definedBy": "protocol/agent/v1/registration-proof.md",
"note": "Separate from CERTIFICATE_REQUEST_MALFORMED because the request is structurally fine and the refusal is a policy one: ADR 0008 fixes the device key and this surface may not widen it."
},
{
"reason": "REGISTRATION_TOKEN_UNUSABLE",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "No usable registration token matches the presented uid and secret at this instant.",
"definedBy": "App\\Modules\\Clusters\\Application\\Contracts\\RegistrationTokenPort",
"note": "Cited, not redefined. The port already answers conditionally for every reason a caller does not own an exchange, and this single reason is the whole of what the surface may say about why."
},
{
"reason": "REGISTRATION_PROOF_INVALID",
"httpStatus": 401,
"status": "UNAUTHENTICATED",
"meaning": "The proof does not verify over the transcript Connect rebuilt, under the key inside the presented certificate request.",
"definedBy": "protocol/agent/v1/registration-proof.md",
"note": "One reason for every substitution: another token, another cluster, another organization, another requestId, another nonce, a changed expiry, a substituted certificate request, a variant transcript encoding, or a proof by another key. Naming which one failed would turn the exchange into an oracle for the contents of a token row."
}
]
}
@@ -0,0 +1,814 @@
{
"protocolVersion": "v1",
"fixtureSet": "registration",
"fixture": "reject-vectors",
"description": "Every rejection the registration transcript exists to produce. Except where a vector says otherwise, each one is the first accept vector with exactly one input substituted, so the field that changed is the field that caused the rejection. stage names the frozen check that refuses it. verifiesMathematically is what a lenient verifier gets: one that accepts either base64 alphabet with or without padding, takes DER or fixed-width r||s, and asks its ECDSA library whether the numbers work out over serverTranscript under the key inside the presented certificate request. A vector that is rejected while verifiesMathematically is true is a vector the library alone would have accepted, and is the reason the rule and not the library decides. null means there was no usable key to ask with.",
"vectors": [
{
"name": "protocol major version the exchange does not support",
"stage": "protocolVersion",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "The bytes are the accepted exchange untouched. protocol/agent/v1/authentication.md already owns this rejection and it happens before anything here is parsed, which is why the proof still verifies.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v2",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwpptE_9atQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "2552ab455a86703b75321cf70c7ee48034f0ad8a56dae0dbdc462c7fbe762477",
"expected": {
"accepted": false,
"reason": "UNSUPPORTED_PROTOCOL",
"verifiesMathematically": true
}
},
{
"name": "malleated high-S proof over the accepted transcript",
"stage": "encoding",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "The pair (r, n - s) of the accepted proof. Every ECDSA library verifies it, so the encoding rule and not the library has to refuse it.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-vCuYrhTSEzwQbRldM3mXMgun6ghSOPBl1P9zBV6GPKnA"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "2552ab455a86703b75321cf70c7ee48034f0ad8a56dae0dbdc462c7fbe762477",
"expected": {
"accepted": false,
"reason": "SIGNATURE_NOT_CANONICAL",
"verifiesMathematically": true
}
},
{
"name": "DER encoded proof",
"stage": "encoding",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "The same r and s in ASN.1. This surface has exactly one signature encoding.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "MEUCIQCJQtZ-rcHMlDaYioWRmiY8cBdqFaNtQ_URM7woEhGv6wIgPUZ1HbLezD_5LmosyGaM3wJoWiiDiJgno8KabRP_WrU"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "2552ab455a86703b75321cf70c7ee48034f0ad8a56dae0dbdc462c7fbe762477",
"expected": {
"accepted": false,
"reason": "SIGNATURE_MALFORMED",
"verifiesMathematically": true
}
},
{
"name": "padded base64url proof",
"stage": "encoding",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "The same 64 octets with = padding. Two spellings of one proof would make the proof useless as an exchange identity.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwpptE_9atQ=="
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "2552ab455a86703b75321cf70c7ee48034f0ad8a56dae0dbdc462c7fbe762477",
"expected": {
"accepted": false,
"reason": "SIGNATURE_MALFORMED",
"verifiesMathematically": true
}
},
{
"name": "standard base64 alphabet proof",
"stage": "encoding",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "Unpadded, right length, wrong alphabet. Whether it is even distinguishable from base64url depends on the bytes, which is exactly why the alphabet is fixed rather than sniffed.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr+s9RnUdst7MP/kuaizIZozfAmhaKIOImCejwpptE/9atQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "2552ab455a86703b75321cf70c7ee48034f0ad8a56dae0dbdc462c7fbe762477",
"expected": {
"accepted": false,
"reason": "SIGNATURE_MALFORMED",
"verifiesMathematically": true
}
},
{
"name": "truncated proof",
"stage": "encoding",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "Sixty octets. Left-padding it back to 64 would change r, so a verifier rejects rather than repairs.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwppt"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "2552ab455a86703b75321cf70c7ee48034f0ad8a56dae0dbdc462c7fbe762477",
"expected": {
"accepted": false,
"reason": "SIGNATURE_MALFORMED",
"verifiesMathematically": false
}
},
{
"name": "proof whose r and s are both zero",
"stage": "encoding",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "Well formed in length and alphabet, out of range in value.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "2552ab455a86703b75321cf70c7ee48034f0ad8a56dae0dbdc462c7fbe762477",
"expected": {
"accepted": false,
"reason": "SIGNATURE_MALFORMED",
"verifiesMathematically": false
}
},
{
"name": "proof declaring ES384",
"stage": "encoding",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "The value is the accepted proof. algorithm is a closed enumeration, so an unrecognised value is refused rather than ignored in favour of what the bytes look like.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES384",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwpptE_9atQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "2552ab455a86703b75321cf70c7ee48034f0ad8a56dae0dbdc462c7fbe762477",
"expected": {
"accepted": false,
"reason": "UNSUPPORTED_ALGORITHM",
"verifiesMathematically": true
}
},
{
"name": "proof declaring none",
"stage": "encoding",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "The JWS \"none\" downgrade, refused before any key is loaded.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "none",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwpptE_9atQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "2552ab455a86703b75321cf70c7ee48034f0ad8a56dae0dbdc462c7fbe762477",
"expected": {
"accepted": false,
"reason": "UNSUPPORTED_ALGORITHM",
"verifiesMathematically": true
}
},
{
"name": "certificate request carrying a P-384 key",
"stage": "certificateRequest",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "A structurally valid, correctly self-signed PKCS#10 whose key is on the wrong curve. ADR 0008 fixes P-256 for the device identity and this surface may not widen it.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBhDCCAQoCAQAwLzEtMCsGA1UEAwwkMDE5OGY0YjAtOGIwMC03ZDgwLTk0OTEtOWZhMGIxYzJkM2U3MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEK8Kel2qfLdRNZi5vrCirhsFMQXe89k2TE4gwd4oQ0EOdGEQNZTbesxTdxDmNJ47urHFS9+uw4WwGfgV5yn8jtSX2ciwSpOVW7KlZryHkvsDG5yjk81iUuBasjQlirXxsoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNoADBlAjEA8xjrA4XsOUjpmohfjygdkYBo6pqFQGDYGc9h3RBGRwgGiDkECS1B/WYEAZ43GOheAjB4YnNDjfRtDyRGG2xOnXvFO+KmNWeA97lO7mr3FiHiTsIE2B3+8OnXQ3mGl9X7gak=",
"proof": {
"algorithm": "ES256",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwpptE_9atQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:buDezBlPJr91rBBKJQ7QiTC7uHeOhqO0ye0u03jPD_Q\n",
"serverTranscriptSha256": "a7af29202bf52b96b4f2dd208a670fdd411e801c8c15ab89aac88ad54d0b6a6a",
"expected": {
"accepted": false,
"reason": "DEVICE_KEY_UNSUPPORTED",
"verifiesMathematically": false
}
},
{
"name": "certificate request whose self-signature was altered",
"stage": "certificateRequest",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "One octet of the PKCS#10 signature flipped. The structure parses and the key is intact, so only actually checking the self-signature catches it.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXN",
"proof": {
"algorithm": "ES256",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwpptE_9atQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:9hgfdYlj31WfxakroWSOLY5A2aW3VmsCj2DQPzo9GC4\n",
"serverTranscriptSha256": "212d742af9dc4846d2b29513e740a111d879da232034c767d7af73f9b15d6312",
"expected": {
"accepted": false,
"reason": "CERTIFICATE_REQUEST_MALFORMED",
"verifiesMathematically": false
}
},
{
"name": "certificate request that is not DER at all",
"stage": "certificateRequest",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "Arbitrary octets. They still have a SHA-256 and therefore still produce a transcript, which is why the certificate request is checked before the proof rather than after it.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "dGhpcyBpcyBub3QgYSBQS0NTIzEwIGNlcnRpZmljYXRlIHJlcXVlc3Q=",
"proof": {
"algorithm": "ES256",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwpptE_9atQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:vrub5r3pd54hnKvH9kBvkfWy2OR6nT5GhNSVfG2D6s4\n",
"serverTranscriptSha256": "9e9abfa9bdbca21c754065f1a9a3a129305cba692df4beaaaf3f996677e569a1",
"expected": {
"accepted": false,
"reason": "CERTIFICATE_REQUEST_MALFORMED",
"verifiesMathematically": null
}
},
{
"name": "replay of an accepted exchange against the token it already spent",
"stage": "registrationToken",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "Byte-identical to the accepted exchange, replayed after the token was consumed. The proof still verifies and must: replay is a state decision the token row owns, not a signature failure.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "CONSUMED"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwpptE_9atQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "2552ab455a86703b75321cf70c7ee48034f0ad8a56dae0dbdc462c7fbe762477",
"expected": {
"accepted": false,
"reason": "REGISTRATION_TOKEN_UNUSABLE",
"verifiesMathematically": true
}
},
{
"name": "accepted exchange presented after its token expired",
"stage": "registrationToken",
"evaluatedAt": "2026-08-20T12:20:00Z",
"note": "The same octets five minutes after expiresUnix. Freshness is evaluated against the Connect clock and the stored row; the proof itself never goes stale, which is why an expiry lives in the transcript and a window does not.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwpptE_9atQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "2552ab455a86703b75321cf70c7ee48034f0ad8a56dae0dbdc462c7fbe762477",
"expected": {
"accepted": false,
"reason": "REGISTRATION_TOKEN_UNUSABLE",
"verifiesMathematically": true
}
},
{
"name": "proof produced by a key other than the one in the certificate request",
"stage": "proof",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "A real ES256 signature over the correct transcript, by the wrong key. The verifying key is only ever the one inside the presented certificate request.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "v5kin4DflqIdg9brBFQv1iqzz9Tk-gt8MBWjcm4XYZMNuAzeaOMLxW5hAPpYUibUn2zE0RxB-hmCvqllQ0GCXQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "2552ab455a86703b75321cf70c7ee48034f0ad8a56dae0dbdc462c7fbe762477",
"expected": {
"accepted": false,
"reason": "REGISTRATION_PROOF_INVALID",
"verifiesMathematically": false
}
},
{
"name": "accepted proof presented against a different token of the same cluster",
"stage": "proof",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "The token uid, the challenge nonce, and the expiry all come from the other row, so the rebuilt transcript differs in three fields at once.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-7a00-7c70-8381-8e9fa0b1c2d6",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "5c7e9a0b1d2f3041526374859607b8c9dae0f1023456789abcdef0123456789a",
"expiresUnix": 1787229000,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-7a00-7c70-8381-8e9fa0b1c2d6",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwpptE_9atQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-7a00-7c70-8381-8e9fa0b1c2d6\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:5c7e9a0b1d2f3041526374859607b8c9dae0f1023456789abcdef0123456789a\n10:1787229000\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "804816033a4525444ef4277ffe5b1f547a74b54b3e488377108e5ee587dab8fa",
"expected": {
"accepted": false,
"reason": "REGISTRATION_PROOF_INVALID",
"verifiesMathematically": false
}
},
{
"name": "accepted proof presented against another cluster of the same organization",
"stage": "proof",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "Only clusterUid differs. A device enrolled by an operator of one cluster can never land in another, even inside the tenant it belongs to.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-3c00-7e30-8f41-4a5b6c7d8e92",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwpptE_9atQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-3c00-7e30-8f41-4a5b6c7d8e92\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "2ad4639edb25e97e7f056a702afed2ac1c4c5c13f8361bf86e63d5d37273ac55",
"expected": {
"accepted": false,
"reason": "REGISTRATION_PROOF_INVALID",
"verifiesMathematically": false
}
},
{
"name": "accepted proof presented against a cluster of another organization",
"stage": "proof",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "The cross-tenant case. organizationUid is in the transcript so that a proof is unusable outside the tenant that issued its token even if every other value were somehow reproduced.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-4d00-7f40-9051-5b6c7d8e9fa3",
"clusterUid": "0198f4b0-5e00-7a50-8161-6c7d8e9fa0b4",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwpptE_9atQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-4d00-7f40-9051-5b6c7d8e9fa3\n36:0198f4b0-5e00-7a50-8161-6c7d8e9fa0b4\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "cb050ac257fdcd258eb24a9395c87ffc61113b4c2b3bcbf5d9ed2dccac2968a0",
"expected": {
"accepted": false,
"reason": "REGISTRATION_PROOF_INVALID",
"verifiesMathematically": false
}
},
{
"name": "accepted proof moved onto a different requestId",
"stage": "proof",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "A captured body resubmitted under a fresh idempotency key. Binding requestId is what keeps a captured exchange replayable only as itself.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "7c4d2e10-9f83-4a5b-b6c7-d8e9f0a1b2c3",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwpptE_9atQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:7c4d2e10-9f83-4a5b-b6c7-d8e9f0a1b2c3\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "561bda4be56f6334379c9832b73c74eca7d01a7cef95ef6ab0c5c13499bbb90c",
"expected": {
"accepted": false,
"reason": "REGISTRATION_PROOF_INVALID",
"verifiesMathematically": false
}
},
{
"name": "accepted proof against a row whose challenge nonce differs",
"stage": "proof",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "Everything a console reader can see is unchanged; only the value delivered once with the secret differs. A proof cannot be precomputed from public token metadata.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "ffeeddccbbaa99887766554433221100ffeeddccbbaa998877665544332211ff",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwpptE_9atQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:ffeeddccbbaa99887766554433221100ffeeddccbbaa998877665544332211ff\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "d2d678baf0f396b5d9bef9acea01498020ae45fd479a41b1e49d8ec1ed2ae117",
"expected": {
"accepted": false,
"reason": "REGISTRATION_PROOF_INVALID",
"verifiesMathematically": false
}
},
{
"name": "accepted proof against a row whose expiry was extended",
"stage": "proof",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "One hour added to expiresUnix. A window that is widened after the fact invalidates every proof produced for the old one instead of quietly resurrecting them.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787231700,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwpptE_9atQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787231700\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "bf31d8f89414ba4fe9c4984844c33da79114ef77833f6401bc02485dca779ef1",
"expected": {
"accepted": false,
"reason": "REGISTRATION_PROOF_INVALID",
"verifiesMathematically": false
}
},
{
"name": "accepted proof presented with a substituted certificate request",
"stage": "proof",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "A perfectly valid, correctly self-signed certificate request for an attacker key. This is the vector that a verifier checking only the PKCS#10 self-signature would accept, and it would issue a device certificate for the attacker.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRzCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARTzytYBF/PPAE2u2p2FYzUiVVwCnvUM/Mr27XLI9NIIP4vQlQ85RY4wSD70xXbUvOXqMwsMNBhp0edNfJ98Dw7oFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNJADBGAiEA/DI2AaTqO+JSj6xWLHHsyULX3s2P5cB3gFzrQ3gA1E4CIQDS5g7SkVicYc7IvJwszDjs7XluJTGB8bN0+MComIntAg==",
"proof": {
"algorithm": "ES256",
"value": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwpptE_9atQ"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:0quILEdbHQp-KF4fBFc7vrrE5-M4fErrGoyT3vPnxIM\n",
"serverTranscriptSha256": "673b6ccbb55c9e0ce7e645955ec0ab8a9d97c29da3b776c57a6047a557c55624",
"expected": {
"accepted": false,
"reason": "REGISTRATION_PROOF_INVALID",
"verifiesMathematically": false
}
},
{
"name": "proof over a transcript whose expiry carries a leading zero",
"stage": "proof",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "A real signature over a transcript whose sixth field reads 11:01787228100 instead of 10:1787228100. The length prefix moves with the value, so a variant spelling of one integer is a different transcript and not an equivalent one. A verifier normalising the number instead of refusing the proof would give one exchange two valid transcripts.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "E2Re4CCZK5AXtug_Q2So2W38x5QdNBT-sj3qMMZKpDRjbtj6gsJzjtvHcnJJ9qCwFn3vOCoHIfoecPj3HEJc_g"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "2552ab455a86703b75321cf70c7ee48034f0ad8a56dae0dbdc462c7fbe762477",
"expected": {
"accepted": false,
"reason": "REGISTRATION_PROOF_INVALID",
"verifiesMathematically": false
}
},
{
"name": "proof over a transcript separated by 0x00 instead of 0x0a",
"stage": "proof",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "The separator ADR 0009 uses for a signed document, applied to a transcript that fixes 0x0a. Two separator conventions must not produce one accepted proof.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "0raUrZjLCZqadBmJZHmV9IEd4Y0E0FwIYl_ODzN5OtYgPQtdM3GALzeUddkqUSkbp1fkJ4EFYVqtVAQyfiPS9Q"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "2552ab455a86703b75321cf70c7ee48034f0ad8a56dae0dbdc462c7fbe762477",
"expected": {
"accepted": false,
"reason": "REGISTRATION_PROOF_INVALID",
"verifiesMathematically": false
}
},
{
"name": "proof over a transcript whose domain is lowercased",
"stage": "proof",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "The domain is compared as octets. Case folding it would let a producer pick either spelling and give one exchange two identities.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "aZPdYJtL-SoyTpkUCFKbLNtYZLNRm57uoA2Q75-l8VgvwE84TzC3yM-mkQXqr6ffflJ-vz2zwF4gLUQBSpe7Zw"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "2552ab455a86703b75321cf70c7ee48034f0ad8a56dae0dbdc462c7fbe762477",
"expected": {
"accepted": false,
"reason": "REGISTRATION_PROOF_INVALID",
"verifiesMathematically": false
}
},
{
"name": "proof over a JSON serialisation of the same field values",
"stage": "proof",
"evaluatedAt": "2026-08-20T12:00:00Z",
"note": "The transcript is not a document and is never JSON. A JSON signature input would make key order, escaping, and number formatting part of the contract.",
"tokenRecord": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"state": "ACTIVE"
},
"request": {
"protocolVersion": "v1",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"certificateRequest": "MIIBRjCB7QIBADAvMS0wKwYDVQQDDCQwMTk4ZjRiMC04YjAwLTdkODAtOTQ5MS05ZmEwYjFjMmQzZTcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYqHoZ46cBcslpM7HxBgZFVJgNYIu0kOUlTDJ7f7bUSJKwiFd/WkKhjzTi+BtUcB2S/4hw7AO/AzEH6bAoKu8MoFwwWgYJKoZIhvcNAQkOMU0wSzBJBgNVHREEQjBAhj51cm46cnVzdGZzOmNvbm5lY3Q6ZGV2aWNlOjAxOThmNGIwLThiMDAtN2Q4MC05NDkxLTlmYTBiMWMyZDNlNzAKBggqhkjOPQQDAgNIADBFAiBFV0TbGna4C83UtAaLF4Ar7E0ofknbqY0ZUDXlsB9n8gIhAKBuWIviE8mhzvzCY5kjRgAgSChxfAZ9kFSQoMHZTkXM",
"proof": {
"algorithm": "ES256",
"value": "zMxwclr9Zrk4lxtPDkbrJs8LHwIStmDg7XONYNpa2As9PEXHqtHMSQPLUP4d4YB_Kcr5UdG3BU64UlwxLrYIrg"
}
},
"serverTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"serverTranscriptSha256": "2552ab455a86703b75321cf70c7ee48034f0ad8a56dae0dbdc462c7fbe762477",
"expected": {
"accepted": false,
"reason": "REGISTRATION_PROOF_INVALID",
"verifiesMathematically": false
}
}
]
}
@@ -0,0 +1,289 @@
{
"protocolVersion": "v1",
"fixtureSet": "registration",
"fixture": "transcript",
"description": "The frozen canonical byte sequence a RustFS device signs to prove possession of its device key during the registration token exchange, the frozen request body that carries it, and the order a verifier applies its checks in. protocol/agent/v1/registration-proof.md is the prose; this file is the machine-readable freeze. R01 and R02 implement against both; api/tests/Feature/Agent/RegistrationProofFixtureTest.php replays them.",
"transcript": {
"domain": "RUSTFS-CONNECT-REGISTRATION-V1",
"domainEncoding": "US-ASCII, 30 octets, compared octet for octet. Case sensitive; no other spelling exists.",
"domainTerminator": "0x0a",
"fieldEncoding": "decimalOctetLength 0x3a valueOctets 0x0a",
"lengthEncoding": "The shortest ASCII decimal spelling of the value length in octets: no sign, no leading zero, no padding, no separators. It counts octets and never characters.",
"fieldSeparator": "0x3a",
"fieldTerminator": "0x0a",
"fieldCount": 7,
"fieldOrder": [
"registrationTokenUid",
"organizationUid",
"clusterUid",
"requestId",
"challengeNonce",
"expiresUnix",
"certificateRequestSha256"
],
"trailingTerminator": true,
"isADocument": false,
"isJson": false,
"reserialisationPermitted": false,
"canonicalisationPermitted": false,
"normalisationPermitted": false,
"rule": "transcript = domain || 0x0a || field(registrationTokenUid) || field(organizationUid) || field(clusterUid) || field(requestId) || field(challengeNonce) || field(expiresUnix) || field(certificateRequestSha256), where field(v) = decimal(octetLength(v)) || 0x3a || v || 0x0a.",
"note": "The transcript is built, never parsed. A verifier constructs it from the registration token row it resolved plus two values the request carries, and compares nothing but the resulting signature. There is therefore no such thing as a malformed transcript on the wire: a field that cannot be spelled canonically is refused before a transcript exists.",
"fields": [
{
"position": 1,
"name": "registrationTokenUid",
"source": "the resolved registration token row",
"encoding": "lowercase canonical UUIDv7, 36 octets of US-ASCII",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
"binds": "the one token row this exchange spends",
"absenceWouldAllow": "A proof produced for one token to be presented with another token of the same cluster whose nonce and expiry an attacker could otherwise reproduce, spending a credential its holder never used."
},
{
"position": 2,
"name": "organizationUid",
"source": "the resolved registration token row",
"encoding": "lowercase canonical UUIDv7, 36 octets of US-ASCII",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
"binds": "the tenant that issued the token",
"absenceWouldAllow": "A device to be enrolled into a tenant other than the one whose operator issued its token, if a token row were ever moved between organizations by a bug or a restore. It also makes an implementation structurally unable to build a tenant-agnostic transcript."
},
{
"position": 3,
"name": "clusterUid",
"source": "the resolved registration token row",
"encoding": "lowercase canonical UUIDv7, 36 octets of US-ASCII",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
"binds": "the cluster the device joins",
"absenceWouldAllow": "A proof to enrol a device into a different cluster of the same tenant, which is the intra-tenant half of the same substitution and is not covered by organizationUid."
},
{
"position": 4,
"name": "requestId",
"source": "the request body",
"encoding": "lowercase canonical UUIDv4, 36 octets of US-ASCII",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
"binds": "the single idempotent attempt the proof belongs to",
"absenceWouldAllow": "A captured request body to be resubmitted under a fresh idempotency key. A registration token reservation is held by (requestId, csrHash), so a replay under a new requestId could take the exchange over once the original reservation lapsed. With requestId bound, a captured body can only ever be replayed as itself, which is exactly an idempotent retry."
},
{
"position": 5,
"name": "challengeNonce",
"source": "the resolved registration token row",
"encoding": "64 octets of lowercase hexadecimal, the 256 bit nonce as it is stored",
"pattern": "^[0-9a-f]{64}$",
"binds": "the one-time challenge delivered with the token secret",
"absenceWouldAllow": "A proof to be precomputed from public token metadata alone. The token uid, the organization, and the cluster all appear in ordinary console responses; the nonce is delivered exactly once, beside the secret, to the operator enrolling the cluster. Without it, anyone who can read the console could build every transcript in advance and would only need a secret leaked later through some other channel."
},
{
"position": 6,
"name": "expiresUnix",
"source": "the resolved registration token row",
"encoding": "seconds since the Unix epoch as shortest ASCII decimal: no sign, no leading zero, no fraction, no padding",
"pattern": "^[1-9][0-9]{0,18}$",
"binds": "the enrolment window the control plane recorded",
"absenceWouldAllow": "An expired exchange to be resurrected by widening the window it was produced against. Every proof made for the old window stops verifying the moment expires_at changes, so a row edited to extend a spent enrolment yields nothing."
},
{
"position": 7,
"name": "certificateRequestSha256",
"source": "recomputed over the exact octets of the presented certificate request",
"encoding": "unpadded base64url of the 32 octet SHA-256 digest, 43 octets of US-ASCII",
"pattern": "^[A-Za-z0-9_-]{43}$",
"binds": "the exact certificate request being answered, and through it the device public key",
"absenceWouldAllow": "A captured proof to be presented with an attacker certificate request, so that Connect issues a device certificate for an attacker key against an operator token. This is the proof-of-possession property itself. The digest covers the transmitted octets and not a parsed structure, so a re-encoded certificate request is a different artifact rather than an equivalent one."
}
],
"excluded": [
{
"value": "the registration token secret",
"reason": "It is proven by comparing its SHA-256 against the stored digest, and it is the one value in the exchange that must never reach a signature input, a debug dump, or an audit record. Nothing is weakened by leaving it out: the token uid already names the row, and the nonce already makes the transcript unguessable from public metadata."
},
{
"value": "the certificate request octets themselves",
"reason": "Only their digest. A signature input that carried a whole PKCS#10 would make every verifier hold the artifact in the hashing path for no additional binding."
},
{
"value": "any organization, cluster, device, or expiry the client states",
"reason": "The request body has no field for one. Every tenancy value in the transcript is read from the token row after the token is resolved, so a client cannot choose what its own proof is checked against (ADR 0002)."
},
{
"value": "a keyId beside the proof",
"reason": "The verifying key is the SubjectPublicKeyInfo inside the presented certificate request and nothing else. A keyId would be a lookup hint that is not one, and the first implementation to trust it would have re-introduced key substitution."
},
{
"value": "a timestamp produced by the device",
"reason": "Device clocks are advisory (ADR 0003). Freshness is the token row expiry evaluated against the Connect clock, which is already bound as expiresUnix."
}
]
},
"signature": {
"signatureAlgorithm": "ES256",
"curve": "P-256",
"hash": "SHA-256",
"signatureEncoding": "fixed-width-r-s",
"signatureLengthBytes": 64,
"signatureTransferEncoding": "base64url-unpadded",
"signatureValuePattern": "^[A-Za-z0-9_-]{86}$",
"lowSRequired": true,
"groupOrder": "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551",
"maxS": "7fffffff800000007fffffffffffffffde737d56d38bcf4279dce5617e3192a8",
"algorithmField": "proof.algorithm",
"algorithmEnumeration": [
"ES256"
],
"algorithmEnumerationIsClosed": true,
"verifyingKey": "the SubjectPublicKeyInfo of the presented certificate request, and never a key named anywhere in the request",
"subjectPublicKeyInfoDerPrefix": "3059301306072a8648ce3d020106082a8648ce3d030107034200",
"note": "Identical to the encoding ADR 0009 freezes for the offline surface, so one repository has one signature encoding. Only the signature input differs, and transcript.divergenceFromAdr0009 says why."
},
"divergenceFromAdr0009": {
"shared": [
"ES256 on P-256 with SHA-256.",
"The 64 octet fixed-width r||s encoding, low-S normalised, as unpadded base64url.",
"A domain separation tag in front of everything, so a signature made for one surface can never be replayed on another.",
"Closed enumerations: an unrecognised algorithm is refused, never discarded."
],
"differs": [
{
"aspect": "what is signed",
"adr0009": "the exact raw octets of a document as transmitted",
"here": "a transcript the verifier constructs from values it already holds",
"reason": "ADR 0009 signs bytes because a document crosses the wire and re-serialising it before verification would verify something the producer never signed. There is no such document here. Five of the seven bound values are never transmitted at all: they are read from the registration token row after the token is resolved. A signature input assembled from a document could therefore only bind what the client chose to send, which is the precise thing this exchange must not do."
},
{
"aspect": "the separator after the domain tag",
"adr0009": "0x00, the one octet that cannot appear in an ASCII tag or in JSON",
"here": "0x0a, once after the domain and once after every field",
"reason": "ADR 0009 needs an octet that cannot occur in either half because the second half is an opaque document of unbounded shape. Here every field is length-prefixed and the arity is fixed at seven, so the parse is unambiguous whatever the separator is; the separator only has to be a byte no field value can contain, and none of the seven patterns admits 0x0a. Choosing 0x0a instead makes the whole transcript printable US-ASCII, which is why this fixture can publish the literal canonical string beside its digest and ADR 0009 cannot."
}
],
"note": "These are two signature inputs, not two signing conventions. An implementation that already produces ADR 0009 signatures changes only what it hashes."
},
"request": {
"operation": "POST /agent/registrationTokens:exchange",
"authentication": "none; ADR 0008 lets only a pre-registration operation opt out, because a device has no certificate until this exchange gives it one",
"contentType": "application/json",
"abuseGate": "api/app/Modules/Agent/Http/Middleware/RegistrationAbuseGuard.php, which runs before this body is parsed",
"registrationTokenUidMustAppearWithinFirstBytes": 1024,
"registrationTokenUidPlacementNote": "The abuse gate finds the token uid by scanning at most the first 1024 octets of the body, so a producer MUST place registrationTokenUid inside that window. A body that does not is not rejected; it silently loses its per-token rate bucket and is bounded by source address alone. That is why this is a stated requirement and not left to JSON member order being unobservable.",
"fields": [
{
"name": "protocolVersion",
"required": true,
"type": "string",
"rule": "v<major>, exactly as protocol/agent/v1/authentication.md freezes it. Anything else is UNSUPPORTED_PROTOCOL and HTTP 400 with nothing partially processed."
},
{
"name": "requestId",
"required": true,
"type": "string",
"rule": "Lowercase canonical UUIDv4 idempotency key, bound into the transcript at position 4 and into the token reservation."
},
{
"name": "registrationTokenUid",
"required": true,
"type": "string",
"rule": "The public lookup half of the token. Not a secret, and not authorization evidence: it selects a row and nothing more."
},
{
"name": "registrationTokenSecret",
"required": true,
"type": "string",
"rule": "The 256 bit secret as unpadded base64url, compared in constant time against the stored SHA-256 digest. It is never part of the transcript and no fixture in this set carries one."
},
{
"name": "certificateRequest",
"required": true,
"type": "string",
"rule": "PKCS#10 DER as standard padded base64. Its digest is bound at position 7 and its SubjectPublicKeyInfo is the verifying key."
},
{
"name": "proof",
"required": true,
"type": "object",
"rule": "Exactly two members: algorithm, fixed at ES256, and value, the 64 octet r||s proof as unpadded base64url."
}
],
"absentByConstruction": [
"organizationUid",
"organizationName",
"clusterUid",
"clusterName",
"clusterDeviceUid",
"challengeNonce",
"expiresUnix",
"proof.keyId"
],
"reservationCertificateRequestHash": "lowercase SHA-256 hex over the same certificate request octets that position 7 digests",
"reservationCertificateRequestHashNote": "RegistrationToken::isReservableBy() holds a reservation under (requestId, csrHash). The reservation and the transcript must digest the same octets the same way, or one request could hold a token for a certificate request its proof does not cover. Same input, same algorithm, different transfer encoding only because one value is a database column and the other is a transcript field.",
"absentByConstructionNote": "There is no field for any of these, so no implementation can accept one \"just to compare it\". Connect reads all of them from the token row.",
"certificateRequestProfile": {
"format": "PKCS#10, DER",
"publicKey": "ECDSA on NIST P-256",
"selfSignature": "ES256 by the key it presents, verified over the DER-encoded certificationRequestInfo",
"selfSignatureEncodingConstrained": false,
"selfSignatureEncodingNote": "The PKCS#10 self-signature is ordinary ASN.1 DER and is not held to the r||s or low-S rules; it is not an artifact identity, and its exact octets are already bound by the position 7 digest. Two certificate requests that differ only in their self-signature are two different artifacts, each with its own transcript.",
"subjectUsed": false,
"sanUsed": false,
"extensionsUsed": false,
"attributesUsed": false,
"claimedDeviceUidInFixtures": "0198f4b0-8b00-7d80-9491-9fa0b1c2d3e7",
"claimedSubjectAlternativeNameInFixtures": "urn:rustfs:connect:device:0198f4b0-8b00-7d80-9491-9fa0b1c2d3e7",
"claimedIdentityNote": "Every certificate request in this set carries the subject CN=0198f4b0-8b00-7d80-9491-9fa0b1c2d3e7 and the matching device URN as its only subject alternative name. Connect assigned no such device, and no vector references that uid anywhere else. A verifier that reads an identity out of a certificate request will visibly agree with a value nothing else in the exchange corroborates, which is easier to notice than an omission.",
"ignoredFieldsNote": "Connect consumes a certificate request for its SubjectPublicKeyInfo and its self-signature and for nothing else. The subject, the subject alternative names, any requested extensions, and any attributes are ignored and are never copied into the issued certificate. A device cannot name itself: ADR 0008 fixes the issued subject as CN=<clusterDeviceUid> and the SAN as urn:rustfs:connect:device:<clusterDeviceUid>, and Connect assigns that uid during this exchange. A device has no uid to put in a certificate request, which is the structural reason the request cannot be the source of its own identity.",
"selfSignatureAloneIsInsufficient": "A valid self-signature proves only that somebody holds the key in the request. It binds no token, no tenant, no cluster, and no attempt, so a verifier that stopped there would issue a device certificate to any key presented with any stolen token. reject-vectors.json publishes exactly that vector under \"accepted proof presented with a substituted certificate request\"."
}
},
"verificationOrder": {
"principle": "Refuse on what can be refused without a database read, then resolve the token, then verify the proof. The order is not a preference: four of the seven transcript fields exist only in the token row, so no signature can be checked before that row is resolved.",
"steps": [
"read protocolVersion and refuse an unsupported major version with UNSUPPORTED_PROTOCOL",
"refuse a proof.algorithm other than ES256 with UNSUPPORTED_ALGORITHM",
"refuse a proof.value that is not 86 base64url characters decoding to 64 octets with r and s in [1, n) with SIGNATURE_MALFORMED",
"refuse a proof.value whose s exceeds half the group order with SIGNATURE_NOT_CANONICAL, before any key is loaded",
"decode the certificate request, refuse anything that is not one well-formed PKCS#10 DER with no trailing octets with CERTIFICATE_REQUEST_MALFORMED",
"refuse a SubjectPublicKeyInfo that is not an ECDSA key on P-256 with DEVICE_KEY_UNSUPPORTED",
"refuse a certificate request whose ES256 self-signature does not verify under its own key with CERTIFICATE_REQUEST_MALFORMED",
"resolve the registration token by uid and secret digest and refuse anything not usable now with REGISTRATION_TOKEN_UNUSABLE",
"rebuild the transcript from the resolved row plus requestId and the recomputed certificate request digest",
"verify the proof over those octets under the certificate request key and refuse with REGISTRATION_PROOF_INVALID"
],
"ownedByThisContract": [
"UNSUPPORTED_ALGORITHM",
"SIGNATURE_MALFORMED",
"SIGNATURE_NOT_CANONICAL",
"CERTIFICATE_REQUEST_MALFORMED",
"DEVICE_KEY_UNSUPPORTED",
"REGISTRATION_PROOF_INVALID"
],
"ownedElsewhere": [
{
"reason": "UNSUPPORTED_PROTOCOL",
"owner": "protocol/agent/v1/authentication.md"
},
{
"reason": "REGISTRATION_TOKEN_UNUSABLE",
"owner": "App\\Modules\\Clusters\\Application\\Contracts\\RegistrationTokenPort"
}
],
"note": "A rejection never says which of the seven bindings disagreed. All of them collapse into REGISTRATION_PROOF_INVALID, because a response that distinguished them would tell an unauthenticated caller which of its guesses about a token row was right."
},
"example": {
"note": "The first accept vector, written out. A producer that reproduces these octets from these inputs has a correct transcript builder and has not needed a single line of cryptography to prove it.",
"inputs": {
"registrationTokenUid": "0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"organizationUid": "0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"clusterUid": "0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"requestId": "3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"challengeNonce": "a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
"expiresUnix": 1787228100,
"certificateRequestSha256": "H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4"
},
"canonicalTranscript": "RUSTFS-CONNECT-REGISTRATION-V1\n36:0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5\n36:0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81\n36:3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b\n64:a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f\n10:1787228100\n43:H3RBnh-SfmAAnMKVdPKlQeWuuexx2_yY_c0t1TSddo4\n",
"canonicalTranscriptLengthBytes": 320,
"canonicalTranscriptSha256": "2552ab455a86703b75321cf70c7ee48034f0ad8a56dae0dbdc462c7fbe762477",
"canonicalTranscriptBase64": "UlVTVEZTLUNPTk5FQ1QtUkVHSVNUUkFUSU9OLVYxCjM2OjAxOThmNGIwLTZmMDAtN2I2MC05MjcxLTdkOGU5ZmEwYjFjNQozNjowMTk4ZjRiMC0xYTAwLTdjMTAtOGQyMS0yZTNmNGE1YjZjNzAKMzY6MDE5OGY0YjAtMmIwMC03ZDIwLTllMzEtM2Y0YTViNmM3ZDgxCjM2OjNmMmExYzk0LTViNmQtNGU4Zi05YTBiLTFjMmQzZTRmNWE2Ygo2NDphM2YxYzA3ZDliMmU0ODU2YWYwYzFkM2I1ZTdmOTAxMmM0YTZiOGQwZTJmNDA2MTczODQ5NWE2YjdjOGQ5ZTBmCjEwOjE3ODcyMjgxMDAKNDM6SDNSQm5oLVNmbUFBbk1LVmRQS2xRZVd1dWV4eDJfeVlfYzB0MVRTZGRvNAo=",
"proof": "iULWfq3BzJQ2mIqFkZomPHAXahWjbUP1ETO8KBIRr-s9RnUdst7MP_kuaizIZozfAmhaKIOImCejwpptE_9atQ"
}
}
@@ -0,0 +1,3 @@
610eeaf44cb7a9a4ed2e4f076c2aec6050a5b81e873fa5c97845155b7ee727a2 additive-compatibility.json
3c9453cdbb34557d08ac63a1e2d7024870c8794e7d0cdc48c3a7fdfd2fa8b15f field-registry.json
3c7fe86634b7c85da87865498a6677968a78fad1be6d69ed5b3607c29d47430e negotiation-vectors.json
@@ -0,0 +1,104 @@
{
"protocolVersion": "v1",
"fixtureSet": "version",
"fixture": "additive-compatibility",
"description": "Release skew in both directions. v1 grows by optional fields only, so an unknown field is discarded and an absent one takes its documented default.",
"vectors": [
{
"name": "new agent sends a v1 field this Connect does not know",
"direction": "new-agent-to-old-connect",
"payload": {
"protocolVersion": "v1",
"agentVersion": "1.9.0",
"capabilities": ["heartbeat"],
"telemetryProfile": "extended"
},
"expected": {
"decision": "ACCEPT",
"retained": ["protocolVersion", "agentVersion", "capabilities"],
"discarded": ["telemetryProfile"],
"defaultsApplied": {},
"echoedBack": [],
"stored": []
}
},
{
"name": "new agent sends several unknown optional fields at once",
"direction": "new-agent-to-old-connect",
"payload": {
"protocolVersion": "v1",
"capabilities": ["heartbeat", "inventory", "bundle.upload"],
"telemetryProfile": "extended",
"regionHint": "eu-west",
"experimentalFlags": {
"fastHeartbeat": true
}
},
"expected": {
"decision": "ACCEPT",
"retained": ["protocolVersion", "capabilities"],
"discarded": ["telemetryProfile", "regionHint", "experimentalFlags"],
"defaultsApplied": {
"agentVersion": null
},
"echoedBack": [],
"stored": []
}
},
{
"name": "old agent omits every optional field",
"direction": "old-agent-to-new-connect",
"payload": {
"protocolVersion": "v1"
},
"expected": {
"decision": "ACCEPT",
"retained": ["protocolVersion"],
"discarded": [],
"defaultsApplied": {
"agentVersion": null,
"capabilities": []
},
"echoedBack": [],
"stored": []
}
},
{
"name": "old agent reports no capabilities but names itself",
"direction": "old-agent-to-new-connect",
"payload": {
"protocolVersion": "v1",
"agentVersion": "1.0.0"
},
"expected": {
"decision": "ACCEPT",
"retained": ["protocolVersion", "agentVersion"],
"discarded": [],
"defaultsApplied": {
"capabilities": []
},
"echoedBack": [],
"stored": []
}
},
{
"name": "unknown fields do not rescue an unsupported major version",
"direction": "new-agent-to-old-connect",
"payload": {
"protocolVersion": "v2",
"agentVersion": "2.0.0",
"compatibilityShim": "v1"
},
"expected": {
"decision": "REJECT",
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400,
"retained": [],
"discarded": [],
"defaultsApplied": {},
"echoedBack": [],
"stored": []
}
}
]
}
@@ -0,0 +1,34 @@
{
"protocolVersion": "v1",
"fixtureSet": "version",
"fixture": "field-registry",
"description": "The frozen v1 negotiation envelope. Registration and heartbeat both carry it. Any top-level field not listed here is unknown and is discarded after acceptance.",
"envelope": "AgentProtocolNegotiation",
"supportedMajorVersions": [1],
"protocolVersionPattern": "^v[1-9][0-9]{0,3}$",
"unknownFieldPolicy": "accept-and-discard",
"unknownCapabilityPolicy": "discard",
"fields": [
{
"name": "protocolVersion",
"requiredness": "required",
"type": "string",
"default": null,
"note": "Major version only. The minor and patch level of an agent is not negotiated."
},
{
"name": "agentVersion",
"requiredness": "optional",
"type": "string",
"default": null,
"note": "Informational. Connect never compares it for equality with its own version and never gates behavior on it."
},
{
"name": "capabilities",
"requiredness": "optional",
"type": "array",
"default": [],
"note": "Unordered token set. A capability an operation requires but the device did not report fails that operation with a structured result, not the connection."
}
]
}
@@ -0,0 +1,132 @@
{
"protocolVersion": "v1",
"fixtureSet": "version",
"fixture": "negotiation-vectors",
"description": "Protocol version decisions. A rejected version fails closed: nothing in the payload is processed, stored, or echoed.",
"vectors": [
{
"name": "supported major version",
"request": {
"protocolVersion": "v1",
"agentVersion": "1.4.0",
"capabilities": ["heartbeat", "inventory"]
},
"expected": {
"decision": "ACCEPT",
"negotiatedProtocolVersion": "v1",
"reason": null,
"httpStatus": null
}
},
{
"name": "supported major version reported by an agent that sends nothing else",
"request": {
"protocolVersion": "v1"
},
"expected": {
"decision": "ACCEPT",
"negotiatedProtocolVersion": "v1",
"reason": null,
"httpStatus": null
}
},
{
"name": "next major version from a future agent",
"request": {
"protocolVersion": "v2",
"agentVersion": "2.0.0",
"capabilities": ["heartbeat"]
},
"expected": {
"decision": "REJECT",
"negotiatedProtocolVersion": null,
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400
}
},
{
"name": "far future major version",
"request": {
"protocolVersion": "v9999"
},
"expected": {
"decision": "REJECT",
"negotiatedProtocolVersion": null,
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400
}
},
{
"name": "missing protocol version",
"request": {
"agentVersion": "1.4.0"
},
"expected": {
"decision": "REJECT",
"negotiatedProtocolVersion": null,
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400
}
},
{
"name": "version without its prefix",
"request": {
"protocolVersion": "1"
},
"expected": {
"decision": "REJECT",
"negotiatedProtocolVersion": null,
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400
}
},
{
"name": "uppercase prefix",
"request": {
"protocolVersion": "V1"
},
"expected": {
"decision": "REJECT",
"negotiatedProtocolVersion": null,
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400
}
},
{
"name": "dotted version",
"request": {
"protocolVersion": "v1.2"
},
"expected": {
"decision": "REJECT",
"negotiatedProtocolVersion": null,
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400
}
},
{
"name": "zero major version",
"request": {
"protocolVersion": "v0"
},
"expected": {
"decision": "REJECT",
"negotiatedProtocolVersion": null,
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400
}
},
{
"name": "empty protocol version",
"request": {
"protocolVersion": ""
},
"expected": {
"decision": "REJECT",
"negotiatedProtocolVersion": null,
"reason": "UNSUPPORTED_PROTOCOL",
"httpStatus": 400
}
}
]
}
+4
View File
@@ -244,6 +244,10 @@ rustfs-concurrency = { workspace = true }
rustfs-scanner = { workspace = true }
tempfile = { workspace = true }
# Connect device identity: P-256 keys, PKCS#10 certificate requests, ES256 proofs.
p256 = { version = "0.13.2", features = ["ecdsa", "pkcs8"] }
rcgen = { workspace = true }
# Async Runtime and Networking
async-trait = { workspace = true }
axum.workspace = true
+265
View File
@@ -0,0 +1,265 @@
// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Device key, certificate request, and registration proof of possession.
//!
//! The transcript and signature rules implemented here are frozen by
//! `protocol/agent/v1/registration-proof.md` and by the golden fixtures under
//! `protocol/agent/v1/fixtures/registration/`. Connect verifies what this
//! module produces, so any divergence is a protocol break rather than a
//! local behaviour change.
use base64::Engine as _;
use base64::engine::general_purpose::{STANDARD as BASE64_STANDARD, URL_SAFE_NO_PAD as BASE64_URL_NO_PAD};
use p256::ecdsa::signature::Signer as _;
use p256::ecdsa::{Signature, SigningKey};
use p256::pkcs8::{DecodePrivateKey as _, EncodePrivateKey as _};
use sha2::{Digest as _, Sha256};
use zeroize::Zeroizing;
/// The 30 US-ASCII octets that open every registration transcript. Case is
/// significant: a lowercase spelling is a different transcript, and the
/// protocol publishes it as a reject vector so the two can never be confused.
const REGISTRATION_DOMAIN: &[u8] = b"RUSTFS-CONNECT-REGISTRATION-V1";
/// Separator between a field's decimal octet length and its value.
const FIELD_SEPARATOR: u8 = b':';
/// Terminator after the domain and after every field value, including the last.
const FIELD_TERMINATOR: u8 = b'\n';
/// The transcript binds exactly seven fields, always present, always in order.
const FIELD_COUNT: usize = 7;
/// The one algorithm this surface accepts. The enumeration is closed: an
/// unrecognised value is refused rather than discarded.
pub const PROOF_ALGORITHM: &str = "ES256";
#[derive(Debug, thiserror::Error)]
pub enum IdentityError {
/// A transcript field carried an octet the encoding cannot represent
/// unambiguously. The transcript is length-prefixed, so a newline inside a
/// value would still parse; it is refused because a caller that can place
/// one can shift the boundary a verifier reconstructs from its own row.
#[error("registration transcript field {field} is not printable US-ASCII without a line feed")]
UnencodableField { field: &'static str },
/// An expiry that predates the epoch cannot be spelled without a sign, and
/// the length rule admits no sign.
#[error("registration token expiry {expires_unix} is negative")]
NegativeExpiry { expires_unix: i64 },
#[error("device key is not a valid P-256 private key: {0}")]
MalformedKey(String),
#[error("failed to generate the device certificate request: {0}")]
CertificateRequest(String),
}
/// The canonical byte sequence a device signs, and its digest.
///
/// Built, never parsed: nothing reads a transcript back, so there is no such
/// thing as a malformed one once it has been constructed.
#[derive(Clone)]
pub struct RegistrationTranscript {
bytes: Vec<u8>,
}
impl std::fmt::Debug for RegistrationTranscript {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// The transcript embeds the challenge nonce, which is disclosed to an
// operator exactly once beside the token secret and is deliberately
// never republished. Rendering the octets would put it into any log or
// panic message that formats a transcript, so only the length and the
// digest — both already public in the fixtures — are shown.
f.debug_struct("RegistrationTranscript")
.field("len", &self.bytes.len())
.field("sha256", &self.sha256_hex())
.finish()
}
}
impl RegistrationTranscript {
/// Assemble the transcript from the seven bound values.
///
/// Five of them reach the device out of band with the token secret and are
/// never sent back, which is what stops a device choosing its own
/// transcript. They cross an operator-supplied boundary, so each one is
/// checked here rather than trusted.
pub fn build(
registration_token_uid: &str,
organization_uid: &str,
cluster_uid: &str,
request_id: &str,
challenge_nonce: &str,
expires_unix: i64,
certificate_request: &[u8],
) -> Result<Self, IdentityError> {
if expires_unix < 0 {
return Err(IdentityError::NegativeExpiry { expires_unix });
}
let expiry = expires_unix.to_string();
let csr_digest = BASE64_URL_NO_PAD.encode(Sha256::digest(certificate_request));
let fields: [(&'static str, &str); FIELD_COUNT] = [
("registrationTokenUid", registration_token_uid),
("organizationUid", organization_uid),
("clusterUid", cluster_uid),
("requestId", request_id),
("challengeNonce", challenge_nonce),
("expiresUnix", &expiry),
("certificateRequestSha256", &csr_digest),
];
let mut bytes = Vec::with_capacity(REGISTRATION_DOMAIN.len() + 1 + 320);
bytes.extend_from_slice(REGISTRATION_DOMAIN);
bytes.push(FIELD_TERMINATOR);
for (name, value) in fields {
if !value.is_ascii() || value.as_bytes().contains(&FIELD_TERMINATOR) {
return Err(IdentityError::UnencodableField { field: name });
}
// The length is the octet count, and `is_ascii` above makes octets
// and characters the same count for these values.
bytes.extend_from_slice(value.len().to_string().as_bytes());
bytes.push(FIELD_SEPARATOR);
bytes.extend_from_slice(value.as_bytes());
bytes.push(FIELD_TERMINATOR);
}
Ok(Self { bytes })
}
/// The exact octets that are signed.
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
/// SHA-256 over the transcript, as lowercase hex. Published beside the
/// canonical string in `transcript.json` so a producer can prove its
/// builder without performing any cryptography.
pub fn sha256_hex(&self) -> String {
let digest = Sha256::digest(&self.bytes);
digest.iter().fold(String::with_capacity(64), |mut out, byte| {
use std::fmt::Write as _;
let _ = write!(out, "{byte:02x}");
out
})
}
}
/// A proof of possession, in the shape the exchange body carries.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RegistrationProof {
pub algorithm: String,
/// 86 base64url characters, unpadded, decoding to a fixed-width 64 octet
/// `r || s`.
pub value: String,
}
/// A device's P-256 key and the operations that key authorises.
///
/// The private key never leaves this type: it is not exposed by a getter, not
/// rendered by `Debug`, and not written anywhere except the sealed store.
pub struct DeviceIdentity {
signing_key: SigningKey,
}
impl std::fmt::Debug for DeviceIdentity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// A device identity is a private key. Rendering any part of it, even a
// fingerprint, puts key-derived material into logs and support bundles.
f.write_str("DeviceIdentity(<redacted>)")
}
}
impl DeviceIdentity {
/// Generate a fresh P-256 key.
pub fn generate() -> Self {
// p256 is pinned to rand_core 0.6 while the workspace `rand` is 0.10, so
// the RNG comes from p256's own re-export rather than the workspace one.
Self {
signing_key: SigningKey::random(&mut p256::elliptic_curve::rand_core::OsRng),
}
}
/// Load a key from its PKCS#8 DER encoding. A key that does not decode is
/// an error rather than a reason to mint a replacement: silently
/// regenerating would strand the certificate already issued for the old one.
pub fn from_pkcs8_der(der: &[u8]) -> Result<Self, IdentityError> {
SigningKey::from_pkcs8_der(der)
.map(|signing_key| Self { signing_key })
.map_err(|error| IdentityError::MalformedKey(error.to_string()))
}
/// Serialise the key for the sealed store. The result is wrapped so it is
/// wiped when the caller drops it.
pub fn to_pkcs8_der(&self) -> Result<Zeroizing<Vec<u8>>, IdentityError> {
self.signing_key
.to_pkcs8_der()
.map(|der| Zeroizing::new(der.as_bytes().to_vec()))
.map_err(|error| IdentityError::MalformedKey(error.to_string()))
}
/// Build the PKCS#10 certificate request Connect consumes.
///
/// Connect reads the request for its SubjectPublicKeyInfo and its
/// self-signature and for nothing else: it assigns the device uid itself,
/// so the subject and SAN carried here name nothing Connect will honour.
pub fn certificate_request_der(&self) -> Result<Vec<u8>, IdentityError> {
let pkcs8 = self.to_pkcs8_der()?;
let key_pair =
rcgen::KeyPair::try_from(pkcs8.as_slice()).map_err(|error| IdentityError::CertificateRequest(error.to_string()))?;
let params = rcgen::CertificateParams::default();
let request = params
.serialize_request(&key_pair)
.map_err(|error| IdentityError::CertificateRequest(error.to_string()))?;
Ok(request.der().to_vec())
}
/// Standard padded base64 of the certificate request, as the body carries it.
pub fn certificate_request_base64(&self) -> Result<String, IdentityError> {
Ok(BASE64_STANDARD.encode(self.certificate_request_der()?))
}
/// Sign a transcript, producing the low-S fixed-width proof.
///
/// ECDSA admits two valid spellings of every signature, and a proof with
/// two spellings is not an identity, so `s` is normalised into the lower
/// half of the group order before encoding.
pub fn sign_registration(&self, transcript: &RegistrationTranscript) -> RegistrationProof {
let signature: Signature = self.signing_key.sign(transcript.as_bytes());
let canonical = signature.normalize_s().unwrap_or(signature);
RegistrationProof {
algorithm: PROOF_ALGORITHM.to_string(),
value: BASE64_URL_NO_PAD.encode(canonical.to_bytes()),
}
}
/// The device public key, DER SubjectPublicKeyInfo.
pub fn public_key_der(&self) -> Vec<u8> {
use p256::pkcs8::EncodePublicKey as _;
self.signing_key
.verifying_key()
.to_public_key_der()
.expect("a P-256 verifying key always encodes as SubjectPublicKeyInfo")
.as_bytes()
.to_vec()
}
}
+244
View File
@@ -0,0 +1,244 @@
// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! On-disk home of the device key.
//!
//! A device that loses its key loses the certificate issued for it and has to
//! spend a fresh registration token to get back, so the store is written
//! durably and published exactly once. It deliberately does not reuse
//! `rustfs_kms`'s `durable_file`, which implements the same commit protocol
//! for envelope keys but is `pub(crate)` to that crate and carries KMS error
//! and failpoint types this path has no use for.
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use zeroize::Zeroizing;
use super::identity::{DeviceIdentity, IdentityError};
/// Name of the key file inside the store directory.
const KEY_FILE: &str = "device.key";
/// Owner read/write only. The key is the device's whole identity.
#[cfg(unix)]
const KEY_MODE: u32 = 0o600;
/// Distinguishes the staging file of concurrent publishers. The process id
/// alone is not enough: several threads of one process may initialise the same
/// store, and a shared staging name would let them truncate each other's
/// half-written key and then link the result into place.
static STAGING_SEQUENCE: AtomicU64 = AtomicU64::new(0);
#[derive(Debug, thiserror::Error)]
pub enum StoreError {
#[error("connect identity store I/O failed at {path}: {source}")]
Io {
path: PathBuf,
#[source]
source: io::Error,
},
/// The key file exists but does not decode. Fail closed: regenerating here
/// would silently abandon a device certificate that is still valid and
/// still trusted by the control plane.
#[error("connect device key at {path} is unreadable and was left untouched: {source}")]
Corrupt {
path: PathBuf,
#[source]
source: IdentityError,
},
/// The key file is present with permissions that expose it. Refused rather
/// than repaired, because a key that has been world-readable has to be
/// treated as disclosed and rotated, not quietly re-sealed.
#[cfg(unix)]
#[error("connect device key at {path} has mode {mode:o}, expected {expected:o}")]
Permissions { path: PathBuf, mode: u32, expected: u32 },
#[error(transparent)]
Identity(#[from] IdentityError),
}
/// A directory holding one device identity.
#[derive(Clone, Debug)]
pub struct IdentityStore {
directory: PathBuf,
}
impl IdentityStore {
pub fn new(directory: impl Into<PathBuf>) -> Self {
Self {
directory: directory.into(),
}
}
pub fn key_path(&self) -> PathBuf {
self.directory.join(KEY_FILE)
}
/// Return the stored identity, or `None` when this deployment has never
/// been enrolled. Reading never creates anything, so an unconfigured
/// server can ask without acquiring an identity as a side effect.
pub fn load(&self) -> Result<Option<DeviceIdentity>, StoreError> {
let path = self.key_path();
let der = match fs::read(&path) {
Ok(der) => Zeroizing::new(der),
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(None),
Err(source) => return Err(StoreError::Io { path, source }),
};
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt as _;
let metadata = fs::metadata(&path).map_err(|source| StoreError::Io {
path: path.clone(),
source,
})?;
let mode = metadata.permissions().mode() & 0o7777;
if mode != KEY_MODE {
return Err(StoreError::Permissions {
path,
mode,
expected: KEY_MODE,
});
}
}
DeviceIdentity::from_pkcs8_der(&der)
.map(Some)
.map_err(|source| StoreError::Corrupt { path, source })
}
/// Return the stored identity, generating and publishing one the first
/// time. Concurrent callers converge on a single identity: publication is
/// a no-clobber link, and whoever loses the race discards its candidate
/// and reads the winner's.
pub fn load_or_create(&self) -> Result<DeviceIdentity, StoreError> {
if let Some(identity) = self.load()? {
return Ok(identity);
}
fs::create_dir_all(&self.directory).map_err(|source| StoreError::Io {
path: self.directory.clone(),
source,
})?;
let candidate = DeviceIdentity::generate();
let der = candidate.to_pkcs8_der()?;
match self.publish(&der) {
Ok(()) => Ok(candidate),
// Another process published first. Its key is the identity; ours
// was never written anywhere and simply goes out of scope.
Err(StoreError::Io { source, .. }) if source.kind() == io::ErrorKind::AlreadyExists => {
self.load()?.ok_or_else(|| StoreError::Io {
path: self.key_path(),
source: io::Error::new(
io::ErrorKind::NotFound,
"device key vanished immediately after another writer published it",
),
})
}
Err(error) => Err(error),
}
}
/// Write, seal, fsync, then link into place and fsync the directory. The
/// key is durable before it is reachable, and it is reachable only once.
fn publish(&self, der: &[u8]) -> Result<(), StoreError> {
use std::io::Write as _;
let final_path = self.key_path();
let temp_path = self.directory.join(format!(
"{KEY_FILE}.{}.{}.tmp",
std::process::id(),
STAGING_SEQUENCE.fetch_add(1, Ordering::Relaxed)
));
let io_at = |path: &Path| {
let path = path.to_path_buf();
move |source| StoreError::Io { path, source }
};
let mut options = fs::OpenOptions::new();
options.write(true).create(true).truncate(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt as _;
options.mode(KEY_MODE);
}
let mut file = options.open(&temp_path).map_err(io_at(&temp_path))?;
let result = (|| -> Result<(), StoreError> {
file.write_all(der).map_err(io_at(&temp_path))?;
// The umask can only narrow the creation mode, so set and verify
// the exact mode before the bytes become durable.
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt as _;
file.set_permissions(fs::Permissions::from_mode(KEY_MODE))
.map_err(io_at(&temp_path))?;
let mode = file.metadata().map_err(io_at(&temp_path))?.permissions().mode() & 0o7777;
if mode != KEY_MODE {
return Err(StoreError::Permissions {
path: temp_path.clone(),
mode,
expected: KEY_MODE,
});
}
}
file.sync_all().map_err(io_at(&temp_path))?;
Ok(())
})();
drop(file);
if let Err(error) = result {
let _ = fs::remove_file(&temp_path);
return Err(error);
}
// `hard_link` fails rather than replacing an existing key, which is
// what makes a retry return the original identity instead of minting
// a second one.
let published = fs::hard_link(&temp_path, &final_path);
let _ = fs::remove_file(&temp_path);
published.map_err(io_at(&final_path))?;
fsync_dir(&self.directory).map_err(io_at(&self.directory))?;
Ok(())
}
}
/// Fsync a directory so a freshly linked entry survives power loss. Directories
/// cannot be opened for syncing on Windows, where this is a no-op.
fn fsync_dir(dir: &Path) -> io::Result<()> {
#[cfg(unix)]
{
fs::File::open(dir)?.sync_all()?;
}
#[cfg(not(unix))]
let _ = dir;
Ok(())
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! RustFS Connect device identity.
//!
//! A cluster device proves possession of its own key when it exchanges a
//! one-time registration token for a durable certificate. This module owns the
//! device-side half of that exchange: the P-256 key, the PKCS#10 certificate
//! request built from it, and the proof-of-possession signature over the
//! canonical transcript frozen by
//! `protocol/agent/v1/registration-proof.md`.
//!
//! Nothing here contacts the network or starts a task. A deployment that has
//! not been enrolled into a Connect control plane never calls into it, so an
//! unconfigured server generates no key and holds no identity.
pub mod identity;
pub mod identity_store;
pub use identity::{DeviceIdentity, IdentityError, RegistrationProof, RegistrationTranscript};
pub use identity_store::{IdentityStore, StoreError};
+1
View File
@@ -80,6 +80,7 @@ pub(crate) mod bitrot_selftest;
pub mod capacity;
pub mod cluster_snapshot;
pub mod config;
pub mod connect;
pub mod delete_tail_activity;
pub mod diagnose;
pub mod embedded;
+119
View File
@@ -0,0 +1,119 @@
// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Conformance of this repository's copy of the Connect agent protocol fixtures.
//!
//! `fixture-sets.json` requires a byte-identical copy of every populated set,
//! and Connect's `make protocol-compat` runs this test by name (the Makefile's
//! `RUSTFS_CONSUMER_TESTS` default) after comparing the two trees. The
//! comparison there proves the copies match; this proves the copy is internally
//! consistent, so a fixture edited on this side is caught here even when
//! Connect is not checked out.
use std::fs;
use std::path::PathBuf;
use sha2::{Digest as _, Sha256};
fn fixture_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../protocol/agent/v1/fixtures")
}
fn sha256_hex(bytes: &[u8]) -> String {
Sha256::digest(bytes).iter().map(|byte| format!("{byte:02x}")).collect()
}
/// The registry is closed at eight sets; a ninth is a protocol change, not a
/// fixture change. Mirrors `EXPECTED_SETS` in Connect's checker.
const EXPECTED_SETS: [&str; 8] = [
"auth",
"version",
"registration",
"heartbeat",
"inventory",
"offline-enrollment",
"bundle",
"redaction",
];
#[test]
fn agent_protocol_fixtures_registry_is_the_frozen_eight_sets() {
let registry: serde_json::Value =
serde_json::from_slice(&fs::read(fixture_root().join("fixture-sets.json")).expect("read fixture-sets.json"))
.expect("fixture-sets.json parses");
let names: Vec<&str> = registry["sets"]
.as_array()
.expect("sets is an array")
.iter()
.map(|set| set["name"].as_str().expect("set has a name"))
.collect();
assert_eq!(names, EXPECTED_SETS, "the fixture registry must stay closed and ordered");
assert_eq!(
registry["consumerCopy"]["path"].as_str(),
Some("protocol/agent/v1/fixtures"),
"this copy lives at the path the registry declares"
);
}
#[test]
fn agent_protocol_fixtures_match_their_manifests() {
let root = fixture_root();
let registry: serde_json::Value =
serde_json::from_slice(&fs::read(root.join("fixture-sets.json")).expect("read fixture-sets.json"))
.expect("fixture-sets.json parses");
let mut checked = 0usize;
for set in registry["sets"].as_array().expect("sets is an array") {
let name = set["name"].as_str().expect("set has a name");
let status = set["status"].as_str().expect("set has a status");
let set_dir = root.join(name);
if status == "reserved" {
assert!(!set_dir.exists(), "reserved fixture set '{name}' must hold no files yet");
continue;
}
let manifest = fs::read_to_string(set_dir.join("MANIFEST.sha256"))
.unwrap_or_else(|error| panic!("populated set '{name}' must carry a manifest: {error}"));
let mut listed = Vec::new();
for line in manifest.lines().filter(|line| !line.trim().is_empty()) {
let (digest, file) = line
.split_once(" ")
.unwrap_or_else(|| panic!("malformed manifest line in '{name}': {line}"));
listed.push(file.to_string());
let bytes = fs::read(set_dir.join(file))
.unwrap_or_else(|error| panic!("set '{name}' lists {file} which is missing: {error}"));
assert_eq!(sha256_hex(&bytes), digest, "set '{name}' file {file} does not match its manifest");
checked += 1;
}
// A file present but unlisted would travel unchecked, so the manifest
// has to be exhaustive rather than merely correct about what it names.
let mut present: Vec<String> = fs::read_dir(&set_dir)
.expect("read fixture set directory")
.map(|entry| entry.expect("read dir entry").file_name().to_string_lossy().into_owned())
.filter(|file| file != "MANIFEST.sha256")
.collect();
present.sort();
listed.sort();
assert_eq!(present, listed, "set '{name}' holds files its manifest does not list");
}
assert!(checked > 0, "no fixture files were verified");
}
+506
View File
@@ -0,0 +1,506 @@
// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Connect device identity: transcript conformance, key durability, and the
//! properties the registration exchange depends on.
use std::fs;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use base64::Engine as _;
use base64::engine::general_purpose::URL_SAFE_NO_PAD as BASE64_URL_NO_PAD;
use rustfs::connect::identity::{DeviceIdentity, IdentityError, RegistrationTranscript};
use rustfs::connect::identity_store::{IdentityStore, StoreError};
fn transcript_fixture() -> serde_json::Value {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../protocol/agent/v1/fixtures/registration/transcript.json");
serde_json::from_slice(&fs::read(path).expect("read transcript.json")).expect("transcript.json parses")
}
fn accept_vectors() -> serde_json::Value {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../protocol/agent/v1/fixtures/registration/accept-vectors.json");
serde_json::from_slice(&fs::read(path).expect("read accept-vectors.json")).expect("accept-vectors.json parses")
}
/// Extract the SubjectPublicKeyInfo from a PKCS#10 request.
///
/// The protocol freezes the DER prefix of a P-256 SubjectPublicKeyInfo, and the
/// key that follows it is a 65 octet uncompressed point, so the whole structure
/// is a fixed 91 octets located by its prefix. This is a test reading a fixture,
/// not a parser: Connect owns certificate request parsing.
fn subject_public_key_info(csr_der: &[u8]) -> Vec<u8> {
let prefix = hex_to_bytes("3059301306072a8648ce3d020106082a8648ce3d030107034200");
let start = csr_der
.windows(prefix.len())
.position(|window| window == prefix)
.expect("certificate request carries a P-256 SubjectPublicKeyInfo");
csr_der[start..start + prefix.len() + 65].to_vec()
}
/// Rebuild each accept vector's transcript from the values a verifier holds.
///
/// This is the interoperability assertion the protocol asks a producer to make:
/// the five hidden fields come from the token row, the two visible ones from the
/// request, and the result must equal the transcript Connect published.
#[test]
fn transcript_reproduces_every_accept_vector() {
let vectors = accept_vectors();
let list = vectors["vectors"].as_array().expect("accept vectors are a list");
assert!(!list.is_empty(), "the accept vector set must not be empty");
for vector in list {
let name = vector["name"].as_str().unwrap_or("<unnamed>");
let token = &vector["tokenRecord"];
let request = &vector["request"];
let csr = base64::engine::general_purpose::STANDARD
.decode(
request["certificateRequest"]
.as_str()
.expect("vector carries a certificate request"),
)
.expect("certificate request is base64");
let transcript = RegistrationTranscript::build(
token["registrationTokenUid"].as_str().unwrap(),
token["organizationUid"].as_str().unwrap(),
token["clusterUid"].as_str().unwrap(),
request["requestId"].as_str().unwrap(),
token["challengeNonce"].as_str().unwrap(),
token["expiresUnix"].as_i64().unwrap(),
&csr,
)
.unwrap_or_else(|error| panic!("vector '{name}' must build: {error}"));
assert_eq!(
transcript.as_bytes(),
vector["serverTranscript"].as_str().unwrap().as_bytes(),
"vector '{name}' transcript must match octet for octet"
);
assert_eq!(
transcript.sha256_hex(),
vector["serverTranscriptSha256"].as_str().unwrap(),
"vector '{name}' transcript digest must match"
);
}
}
/// The published proofs were produced by the Connect-side implementation over
/// keys this repository does not hold. Verifying them against a transcript this
/// module rebuilt is the strongest available statement that the two
/// implementations agree: a single wrong octet anywhere in the transcript makes
/// real ECDSA verification fail.
#[test]
fn published_proofs_verify_over_locally_rebuilt_transcripts() {
use p256::ecdsa::signature::Verifier as _;
let vectors = accept_vectors();
let mut verified = 0usize;
for vector in vectors["vectors"].as_array().expect("accept vectors are a list") {
let name = vector["name"].as_str().unwrap_or("<unnamed>");
if vector["expected"]["verifiesMathematically"].as_bool() != Some(true) {
continue;
}
let token = &vector["tokenRecord"];
let request = &vector["request"];
let csr = base64::engine::general_purpose::STANDARD
.decode(request["certificateRequest"].as_str().unwrap())
.expect("certificate request is base64");
let transcript = RegistrationTranscript::build(
token["registrationTokenUid"].as_str().unwrap(),
token["organizationUid"].as_str().unwrap(),
token["clusterUid"].as_str().unwrap(),
request["requestId"].as_str().unwrap(),
token["challengeNonce"].as_str().unwrap(),
token["expiresUnix"].as_i64().unwrap(),
&csr,
)
.expect("transcript builds");
let raw = BASE64_URL_NO_PAD
.decode(request["proof"]["value"].as_str().expect("vector carries a proof"))
.expect("proof decodes");
let signature = p256::ecdsa::Signature::from_slice(&raw).expect("signature parses");
assert!(
signature.normalize_s().is_none(),
"vector '{name}' publishes a proof that is already low-S"
);
let verifying =
<p256::ecdsa::VerifyingKey as p256::pkcs8::DecodePublicKey>::from_public_key_der(&subject_public_key_info(&csr))
.expect("public key decodes");
verifying
.verify(transcript.as_bytes(), &signature)
.unwrap_or_else(|error| panic!("vector '{name}' proof must verify over the rebuilt transcript: {error}"));
verified += 1;
}
assert!(verified > 0, "no accept vector was cross-verified");
}
/// Drive the builder with the golden example's own inputs, using the accept
/// vector whose certificate request produces the digest it publishes.
fn transcript_from_fixture_inputs(csr_octets: &[u8]) -> Result<RegistrationTranscript, IdentityError> {
let fixture = transcript_fixture();
let inputs = &fixture["example"]["inputs"];
RegistrationTranscript::build(
inputs["registrationTokenUid"].as_str().unwrap(),
inputs["organizationUid"].as_str().unwrap(),
inputs["clusterUid"].as_str().unwrap(),
inputs["requestId"].as_str().unwrap(),
inputs["challengeNonce"].as_str().unwrap(),
inputs["expiresUnix"].as_i64().unwrap(),
csr_octets,
)
}
fn csr_octets_matching_golden_digest() -> Vec<u8> {
let want = transcript_fixture()["example"]["inputs"]["certificateRequestSha256"]
.as_str()
.unwrap()
.to_string();
for vector in accept_vectors()["vectors"].as_array().expect("accept vectors are a list") {
let Some(encoded) = vector["request"]["certificateRequest"].as_str() else {
continue;
};
let der = base64::engine::general_purpose::STANDARD
.decode(encoded)
.expect("certificate request is base64");
let digest = BASE64_URL_NO_PAD.encode(<sha2::Sha256 as sha2::Digest>::digest(&der));
if digest == want {
return der;
}
}
panic!("no accept vector carries the certificate request the golden example digests");
}
#[test]
fn transcript_reproduces_the_golden_example_byte_for_byte() {
let fixture = transcript_fixture();
let example = &fixture["example"];
let transcript = transcript_from_fixture_inputs(&csr_octets_matching_golden_digest()).expect("golden inputs build");
assert_eq!(
transcript.as_bytes(),
example["canonicalTranscript"].as_str().unwrap().as_bytes(),
"the canonical transcript must match octet for octet"
);
assert_eq!(
transcript.as_bytes().len() as u64,
example["canonicalTranscriptLengthBytes"].as_u64().unwrap(),
"the transcript length is frozen"
);
assert_eq!(
transcript.sha256_hex(),
example["canonicalTranscriptSha256"].as_str().unwrap(),
"the transcript digest is frozen"
);
}
#[test]
fn transcript_refuses_a_field_carrying_the_terminator() {
// A newline inside a value would move the boundary a verifier rebuilds
// from its own token row, which is the substitution the encoding exists to
// prevent. Length-prefixing alone would still parse it.
let error = RegistrationTranscript::build(
"0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70\n36:evil",
"0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
1_787_228_100,
b"csr",
)
.expect_err("a field carrying 0x0a must be refused");
assert!(
matches!(error, IdentityError::UnencodableField { field } if field == "organizationUid"),
"unexpected error: {error}"
);
}
#[test]
fn transcript_refuses_a_non_ascii_field() {
let error = RegistrationTranscript::build(
"0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
// Multi-byte input would make the octet length and the character count
// disagree, which is the exact confusion the length rule forbids.
"a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0é",
1_787_228_100,
b"csr",
)
.expect_err("a non-ASCII field must be refused");
assert!(
matches!(error, IdentityError::UnencodableField { field } if field == "challengeNonce"),
"unexpected error: {error}"
);
}
#[test]
fn transcript_refuses_a_negative_expiry() {
let error = transcript_negative_expiry().expect_err("a negative expiry has no unsigned spelling");
assert!(
matches!(error, IdentityError::NegativeExpiry { expires_unix: -1 }),
"unexpected error: {error}"
);
}
fn transcript_negative_expiry() -> Result<RegistrationTranscript, IdentityError> {
RegistrationTranscript::build(
"0198f4b0-6f00-7b60-9271-7d8e9fa0b1c5",
"0198f4b0-1a00-7c10-8d21-2e3f4a5b6c70",
"0198f4b0-2b00-7d20-9e31-3f4a5b6c7d81",
"3f2a1c94-5b6d-4e8f-9a0b-1c2d3e4f5a6b",
"a3f1c07d9b2e4856af0c1d3b5e7f9012c4a6b8d0e2f4061738495a6b7c8d9e0f",
-1,
b"csr",
)
}
#[test]
fn proof_is_a_canonical_low_s_signature_that_verifies() {
use p256::ecdsa::signature::Verifier as _;
let identity = DeviceIdentity::generate();
let csr = identity.certificate_request_der().expect("certificate request builds");
let transcript = transcript_from_fixture_inputs(&csr).expect("transcript builds");
let proof = identity.sign_registration(&transcript);
assert_eq!(proof.algorithm, "ES256");
assert_eq!(proof.value.len(), 86, "the transfer encoding is 86 unpadded base64url characters");
assert!(
proof
.value
.bytes()
.all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_'),
"the proof must use the base64url alphabet with no padding"
);
let raw = BASE64_URL_NO_PAD.decode(&proof.value).expect("proof decodes");
assert_eq!(raw.len(), 64, "the signature is a fixed-width r || s");
let signature = p256::ecdsa::Signature::from_slice(&raw).expect("signature parses");
assert!(
signature.normalize_s().is_none(),
"s must already be in the lower half of the group order"
);
let spki = identity.public_key_der();
let verifying =
<p256::ecdsa::VerifyingKey as p256::pkcs8::DecodePublicKey>::from_public_key_der(&spki).expect("public key decodes");
verifying
.verify(transcript.as_bytes(), &signature)
.expect("the proof must verify over the transcript octets");
}
#[test]
fn proof_does_not_verify_over_a_different_transcript() {
use p256::ecdsa::signature::Verifier as _;
let identity = DeviceIdentity::generate();
let csr = identity.certificate_request_der().expect("certificate request builds");
let transcript = transcript_from_fixture_inputs(&csr).expect("transcript builds");
let proof = identity.sign_registration(&transcript);
// A different certificate request is a different artifact and therefore a
// different transcript; this is the proof-of-possession binding itself.
let other = transcript_from_fixture_inputs(b"a different certificate request").expect("transcript builds");
assert_ne!(transcript.as_bytes(), other.as_bytes());
let raw = BASE64_URL_NO_PAD.decode(&proof.value).expect("proof decodes");
let signature = p256::ecdsa::Signature::from_slice(&raw).expect("signature parses");
let verifying = <p256::ecdsa::VerifyingKey as p256::pkcs8::DecodePublicKey>::from_public_key_der(&identity.public_key_der())
.expect("public key decodes");
assert!(
verifying.verify(other.as_bytes(), &signature).is_err(),
"a proof must not carry over to another transcript"
);
}
#[test]
fn certificate_request_presents_a_p256_key() {
let identity = DeviceIdentity::generate();
let der = identity.certificate_request_der().expect("certificate request builds");
// The prefix the protocol freezes for a P-256 SubjectPublicKeyInfo. Its
// presence proves the request carries the curve Connect requires.
let spki_prefix = hex_to_bytes("3059301306072a8648ce3d020106082a8648ce3d030107034200");
assert!(
der.windows(spki_prefix.len()).any(|window| window == spki_prefix),
"the certificate request must present an ECDSA P-256 SubjectPublicKeyInfo"
);
assert_eq!(der[0], 0x30, "a PKCS#10 request is a DER SEQUENCE");
}
fn hex_to_bytes(hex: &str) -> Vec<u8> {
(0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).expect("valid hex"))
.collect()
}
#[test]
fn unenrolled_deployment_holds_no_identity_and_reading_creates_none() {
let dir = tempfile::tempdir().expect("temp dir");
let store = IdentityStore::new(dir.path().join("connect"));
assert!(store.load().expect("load succeeds").is_none(), "an unenrolled server has no identity");
assert!(
!dir.path().join("connect").exists(),
"reading must not create the store directory, let alone a key"
);
}
#[test]
fn identity_survives_restart_and_retry_does_not_mint_a_second() {
let dir = tempfile::tempdir().expect("temp dir");
let store = IdentityStore::new(dir.path());
let first = store.load_or_create().expect("first create");
let first_key = first.public_key_der();
// A restart is a fresh store over the same directory.
let reopened = IdentityStore::new(dir.path());
let second = reopened.load_or_create().expect("second create");
assert_eq!(first_key, second.public_key_der(), "a retry must return the original identity");
}
#[test]
fn concurrent_initialisation_converges_on_one_identity() {
let dir = tempfile::tempdir().expect("temp dir");
let path = dir.path().to_path_buf();
let started = Arc::new(AtomicUsize::new(0));
// Every thread must be spawned before any is joined: the barrier below
// makes each one wait for all eight, so joining as we spawn would both
// serialise the race this test exists to create and deadlock on the first
// thread. A lazy iterator chain here is not equivalent.
let mut handles = Vec::with_capacity(8);
for _ in 0..8 {
let path = path.clone();
let started = Arc::clone(&started);
handles.push(std::thread::spawn(move || {
// Line the threads up so publication actually races.
started.fetch_add(1, Ordering::SeqCst);
while started.load(Ordering::SeqCst) < 8 {
std::hint::spin_loop();
}
IdentityStore::new(&path).load_or_create().expect("create").public_key_der()
}));
}
let keys: Vec<Vec<u8>> = handles.into_iter().map(|handle| handle.join().expect("thread")).collect();
assert!(
keys.windows(2).all(|pair| pair[0] == pair[1]),
"every concurrent initialiser must observe the same device identity"
);
}
#[test]
fn corrupt_key_is_refused_and_left_on_disk() {
let dir = tempfile::tempdir().expect("temp dir");
let store = IdentityStore::new(dir.path());
store.load_or_create().expect("create");
let key_path = store.key_path();
fs::write(&key_path, b"not a pkcs8 key").expect("corrupt the key");
set_mode(&key_path, 0o600);
let error = store.load().expect_err("a corrupt key must fail closed");
assert!(matches!(error, StoreError::Corrupt { .. }), "unexpected error: {error}");
// Regenerating would strand a certificate the control plane still trusts,
// so the damaged file has to survive for an operator to inspect.
assert_eq!(fs::read(&key_path).expect("key still present"), b"not a pkcs8 key");
}
#[cfg(unix)]
#[test]
fn key_is_sealed_and_widened_permissions_are_refused() {
use std::os::unix::fs::PermissionsExt as _;
let dir = tempfile::tempdir().expect("temp dir");
let store = IdentityStore::new(dir.path());
store.load_or_create().expect("create");
let key_path = store.key_path();
let mode = fs::metadata(&key_path).expect("metadata").permissions().mode() & 0o7777;
assert_eq!(mode, 0o600, "the device key must be owner-only");
set_mode(&key_path, 0o644);
let error = store.load().expect_err("a world-readable key must be refused");
assert!(matches!(error, StoreError::Permissions { mode: 0o644, .. }), "unexpected error: {error}");
}
#[cfg(unix)]
fn set_mode(path: &std::path::Path, mode: u32) {
use std::os::unix::fs::PermissionsExt as _;
fs::set_permissions(path, fs::Permissions::from_mode(mode)).expect("set mode");
}
#[cfg(not(unix))]
fn set_mode(_path: &std::path::Path, _mode: u32) {}
#[test]
fn unwritable_directory_fails_closed_without_publishing() {
let dir = tempfile::tempdir().expect("temp dir");
let store_dir = dir.path().join("sealed");
fs::create_dir(&store_dir).expect("create store dir");
set_mode(&store_dir, 0o500);
let store = IdentityStore::new(&store_dir);
let result = store.load_or_create();
set_mode(&store_dir, 0o700);
#[cfg(unix)]
{
assert!(result.is_err(), "an unwritable store must not silently succeed");
assert!(!store.key_path().exists(), "no key may be published when the write failed");
}
#[cfg(not(unix))]
let _ = result;
}
#[test]
fn stored_key_round_trips_through_pkcs8() {
let identity = DeviceIdentity::generate();
let der = identity.to_pkcs8_der().expect("serialise");
let reloaded = DeviceIdentity::from_pkcs8_der(&der).expect("deserialise");
assert_eq!(identity.public_key_der(), reloaded.public_key_der(), "the key must survive a round trip");
}
#[test]
fn device_identity_does_not_render_key_material() {
let identity = DeviceIdentity::generate();
assert_eq!(format!("{identity:?}"), "DeviceIdentity(<redacted>)");
}