mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-27 15:37:02 +00:00
feat(connect): add device identity store and registration proof (#6267)
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user