diff --git a/rust/profiles/src/profiles.rs b/rust/profiles/src/profiles.rs index d18ce49d..3d240f1c 100644 --- a/rust/profiles/src/profiles.rs +++ b/rust/profiles/src/profiles.rs @@ -139,10 +139,10 @@ pub const X: Profile = Profile { }), }; -/// A confidential client. The exchange runs in the deployment because the -/// secret cannot reach the browser, and the two sessions are served by -/// DIFFERENT hosts -- which is why one pinned authority per profile would -/// be wrong. +/// The credential GitHub calls `client_secret` is sent and revealed, so +/// an attestation publishes it and nothing here is confidential. The two +/// sessions are served by DIFFERENT hosts -- which is why one pinned +/// authority per profile would be wrong. pub const GITHUB: Profile = Profile { platform: "github", ceremony_version: 1, @@ -153,7 +153,7 @@ pub const GITHUB: Profile = Profile { path: "/login/oauth/access_token", request_line: "POST /login/oauth/access_token ", }, - secret_field: Some("client_secret"), + secret_field: None, required_headers: &[ "host: github.com", "content-type: application/x-www-form-urlencoded", diff --git a/rust/profiles/tests/wire.rs b/rust/profiles/tests/wire.rs index 1d5d68b1..38066c68 100644 --- a/rust/profiles/tests/wire.rs +++ b/rust/profiles/tests/wire.rs @@ -126,7 +126,7 @@ async fn the_x_token_request_head_is_one_the_profile_admits() { #[tokio::test] async fn the_github_exchange_head_is_one_the_profile_admits() { let session = GITHUB.token.expect("github notarizes a token session"); - // GitHub's body carries the secret last, per REQ-COMMON-22. + // GitHub's body carries the credential as its last field. let body = b"client_id=Iv1.abc&code=xyz&redirect_uri=https%3A%2F%2Fexample.test%2Fcb&code_verifier=5teBDl6cz4U77aFweV5PbMhBJ_lEFv6LLNKzqnDI5lo&client_secret=deadbeef"; let wire = head_hyper_writes(&session, body).await; assert_head_admits(&session, &wire, body.len()); diff --git a/solidity/contracts/ceremony/CeremonyAttestation.sol b/solidity/contracts/ceremony/CeremonyAttestation.sol index fdb4bf30..ab870058 100644 --- a/solidity/contracts/ceremony/CeremonyAttestation.sol +++ b/solidity/contracts/ceremony/CeremonyAttestation.sol @@ -25,12 +25,10 @@ import {CeremonyFields} from "./CeremonyFields.sol"; /// must contain belong to the Platform Verifier. /// /// IT DOES NOT CHECK COVERAGE. `decode` accepts a transcript byte covered -/// by neither a revealed range nor a commitment, because REQ-COMMON-35 is -/// conditional: it governs an identity-session request that commits a -/// credential in an `Authorization` header, and REQ-COMMON-43 explicitly -/// withholds it from a credential committed in a request body, which is -/// GitHub's `client_secret`. A library that tiled unconditionally would -/// reject every valid GitHub token exchange. +/// by neither a revealed range nor a commitment: which directions a +/// profile tiles is that profile's rule, and this library answers for the +/// encoding alone. Each Platform Verifier calls `requireExactCoverage` +/// for the directions its own profile accounts for. /// /// A gap is where a prover hides bytes, so the Platform Verifier of an /// identity session MUST call `requireExactCoverage` and MUST run the @@ -234,10 +232,9 @@ library CeremonyAttestation { /// `Authorization` header. /// /// @dev At launch that is X's `/2/users/me` request and GitHub's `/user` - /// request, and nothing else. REQ-COMMON-43 forbids applying these to - /// a credential committed in a request body -- GitHub's token - /// exchange commits `client_secret` in a form body, so demanding a - /// CRLF-framed header around it would reject every valid exchange. + /// request, and nothing else. A token request carries its credential + /// as a form field, where there is no header line to count and none + /// to frame, so none of the three reaches one. /// /// The three are one call because they are one property, and two of /// them are worthless alone. The uniqueness scan counts the needle diff --git a/solidity/contracts/ceremony/CeremonyProfile.sol b/solidity/contracts/ceremony/CeremonyProfile.sol index 8c3b471d..da6e8236 100644 --- a/solidity/contracts/ceremony/CeremonyProfile.sol +++ b/solidity/contracts/ceremony/CeremonyProfile.sol @@ -92,7 +92,7 @@ library CeremonyProfile { /// is one or zero and never a preference. uint256 internal constant X_TOKEN_SENT_COMMITMENTS = 0; - uint256 internal constant GITHUB_TOKEN_SENT_COMMITMENTS = 1; + uint256 internal constant GITHUB_TOKEN_SENT_COMMITMENTS = 0; /// @dev The two identity members a Platform Verifier reads out of the /// revealed response bytes (REQ-PLAT-51). diff --git a/solidity/contracts/ceremony/GitHubPlatformVerifier.sol b/solidity/contracts/ceremony/GitHubPlatformVerifier.sol index c1c76e78..e5bbbd6d 100644 --- a/solidity/contracts/ceremony/GitHubPlatformVerifier.sol +++ b/solidity/contracts/ceremony/GitHubPlatformVerifier.sol @@ -8,13 +8,10 @@ import {TlsNotaryVerifierBase} from "./TlsNotaryVerifierBase.sol"; /// @title GitHubPlatformVerifier — the `github/v1` profile. /// -/// @notice The same relation X states, from a confidential client. +/// @notice The same relation X states, for a second platform. /// -/// @dev GitHub uses a confidential client, so the exchange cannot run in the -/// browser and the deployment's Token-Exchange Service is the notarized -/// party for that session. On chain that changes nothing: this verifier -/// sees two attestations and one proof, exactly as X does. It differs in -/// four constants and two reads. +/// @dev This verifier sees two attestations and one proof, exactly as X does. +/// It differs in four constants and two reads. /// /// TWO AUTHORITIES, NOT ONE. `github.com` serves the exchange and /// `api.github.com` serves the identity read, so an authority is per @@ -27,36 +24,31 @@ import {TlsNotaryVerifierBase} from "./TlsNotaryVerifierBase.sol"; /// /// THE REVEALED LAYOUT IS A PROFILE DECISION, as it is for X: /// -/// token exchange — ONE revealed sent range, from offset 0 through the -/// body PREFIX; `_tokenBody` refuses any other count. -/// `client_secret` is ordered last per REQ-COMMON-22 -/// and committed, so the prefix ends where it begins -/// and every field a verifier reads sits inside it. +/// token exchange — ONE revealed sent range covering the request whole, +/// and NO commitment; `_tokenBody` refuses any other +/// count. The credential GitHub calls `client_secret` +/// is sent in the body and revealed with everything +/// around it, so every field a verifier reads lies in +/// the open and the request has no hidden suffix. /// -/// Nothing frames that commitment, unlike the bearer's: -/// its `&client_secret=` opener is on the hidden side, -/// so there is no revealed anchor to compare. A prover -/// starting it earlier hides a body suffix, and -/// `formField` cannot see a duplicate in there. What -/// closes that is ASM-PROV-07 -- the platform rejects a -/// body carrying a profile field twice -- backed by the -/// recurring probes REQ-COMMON-32 requires. Framing it -/// here would mean revealing `&client_secret=`, which -/// the profile's reveal table marks `no`, so it is a -/// specification question rather than a contract one. +/// What the revealed bytes do not settle is the +/// decoded form. `formField` refuses a name it finds +/// at two `&`-anchored positions, but a value carrying +/// a raw `&` or `=` would decode as fields nobody +/// counted. That is ASM-PROV-07 -- the platform +/// rejects a body carrying a profile field twice -- +/// backed by the recurring probes REQ-COMMON-32 +/// requires, exactly as it is for X. /// token response — the `"access_token":"` delimiter and closing quote /// revealed; the bearer and everything else committed. /// identity request — the bearer committed, every other byte revealed /// and tiled exactly. /// identity response — `id` and `login` with their full delimiters. /// -/// The three identity-session checks do NOT reach the exchange's body -/// credential: REQ-COMMON-43 forbids demanding a CRLF-framed -/// `authorization: Bearer ` around a form-body range, which would reject -/// every valid exchange. The secret is protected instead by being ordered -/// last with a delimiter-free charset, which REQ-PLAT-35 makes a -/// deployment obligation — GitHub secrets are hex, so it holds by -/// inspection. +/// An attestation of this exchange carries the application credential in +/// plaintext: to the notary that observed the session, and to every +/// reader of the chain that verifies it. That follows from the layout +/// above rather than from any choice a prover makes. contract GitHubPlatformVerifier is TlsNotaryVerifierBase { /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -103,8 +95,8 @@ contract GitHubPlatformVerifier is TlsNotaryVerifierBase { return CeremonyProfile.AUTHORITY_GITHUB_API; } - /// @dev GitHub commits its `client_secret`, ordered last under - /// REQ-COMMON-22, so exactly one committed range reaches the end. + /// @dev GitHub's exchange hides no body field, so its request is revealed + /// whole and this direction carries no commitment at all. function _tokenSentCommitments() internal pure override returns (uint256) { return CeremonyProfile.GITHUB_TOKEN_SENT_COMMITMENTS; } @@ -113,10 +105,9 @@ contract GitHubPlatformVerifier is TlsNotaryVerifierBase { return CeremonyProfile.GITHUB_TOKEN_REQUEST_LINE; } - /// @dev REQ-COMMON-21B: `host` and section 6.2's media type. The - /// Token-Exchange Service composes this request rather than a browser, - /// so these bytes are agreed here before that service is written; what - /// else it sends is not compared. + /// @dev REQ-COMMON-21B: `host` and section 6.2's media type. What else + /// the sender puts in the head is not compared: it changes what + /// GitHub answers, never how GitHub parses the body. function _tokenRequiredHeaders() internal pure override returns (bytes memory) { return CeremonyProfile.GITHUB_TOKEN_REQUIRED_HEADERS; } diff --git a/solidity/contracts/ceremony/TlsNotaryVerifierBase.sol b/solidity/contracts/ceremony/TlsNotaryVerifierBase.sol index f3402b62..ea844ab0 100644 --- a/solidity/contracts/ceremony/TlsNotaryVerifierBase.sol +++ b/solidity/contracts/ceremony/TlsNotaryVerifierBase.sol @@ -132,9 +132,9 @@ abstract contract TlsNotaryVerifierBase is IPlatformVerifier, PlatformVerifierBa /// `authorization`, and the framing around the committed value. function _tokenRequiredHeaders() internal pure virtual returns (bytes memory); - /// @dev How many committed ranges the token request carries. X hides no - /// body field and uses a public client, so zero; GitHub commits its - /// `client_secret`, ordered last, so one. + /// @dev How many committed ranges the token request carries. Both launch + /// profiles hide no body field, so both answer zero and their + /// requests are revealed whole. function _tokenSentCommitments() internal pure virtual returns (uint256); /// @dev Anything the profile checks in the token body beyond the fields @@ -469,9 +469,8 @@ abstract contract TlsNotaryVerifierBase is IPlatformVerifier, PlatformVerifierBa /// /// So the profile fixes the shape exactly: ONE revealed run beginning /// at offset 0, and exactly the committed ranges the profile expects. - /// X carries none, because it hides no body field and authenticates - /// with a public client; GitHub carries one, its `client_secret`, - /// ordered last under REQ-COMMON-22 and reaching the transcript end. + /// Both launch profiles expect none: the run covers the request + /// through to its signed length, so no body byte is hidden. /// /// AND THE HEAD ITSELF, byte for byte. Revealing the headers is not /// checking them: they were public and unconstrained here, while @@ -735,9 +734,11 @@ abstract contract TlsNotaryVerifierBase is IPlatformVerifier, PlatformVerifierBa uint256 declared = _checkTokenHead(_slice(whole, 0, at)); at += 4; - // `signedLength` is the whole request, and the head is revealed, so the - // remainder is the body -- GitHub's committed `client_secret` included, - // which the revealed run stops short of. + // `signedLength` is the whole request and the head is revealed, so the + // remainder is the body. It comes from the signed length rather than + // from the revealed run so that a profile committing part of its body + // is framed by what the notary signed; where a profile commits nothing + // the two are the same number. if (declared != signedLength - at) revert WrongDeclaredBodyLength(declared, signedLength - at); body = new bytes(whole.length - at); diff --git a/solidity/contracts/ceremony/profiles.json b/solidity/contracts/ceremony/profiles.json index a319a7b7..0758f98e 100644 --- a/solidity/contracts/ceremony/profiles.json +++ b/solidity/contracts/ceremony/profiles.json @@ -126,34 +126,37 @@ "ceremonyVersion": 1, "proofLifetimeSeconds": 3600, "note": [ - "A confidential client. The exchange runs in the deployment because the", - "secret cannot reach the browser, and the two sessions are served by", - "DIFFERENT hosts -- which is why one pinned authority per profile would", - "be wrong." + "The credential GitHub calls `client_secret` is sent and revealed, so", + "an attestation publishes it and nothing here is confidential. The two", + "sessions are served by DIFFERENT hosts -- which is why one pinned", + "authority per profile would be wrong." ], "sessions": { "token": { "authority": "github.com", "method": "POST", "path": "/login/oauth/access_token", - "secretField": "client_secret", + "secretField": null, "requiredHeaders": [ "host: github.com", "content-type: application/x-www-form-urlencoded" ], "note": [ - "The client secret is committed rather than revealed, ordered last", - "in the body under REQ-COMMON-22 so the committed run is a suffix", - "and not a hole.", + "The request is revealed whole, as X's is. The credential sits in", + "the body and is revealed with the rest of it, so the verifier", + "accepts exactly zero committed ranges in this direction.", + "", + "Which is why the media type is required. Revealed is not checked:", + "it was public and unconstrained until the verifier compared it,", + "and it decides how GitHub parses the very bytes `formField`", + "reads.", "", "The media type is section 6.2's and `host` names the authority", - "the notary authenticated. The Token-Exchange Service also sends", + "the notary authenticated. Whoever sends this request also sends", "`accept: application/json`, without which GitHub answers", "form-encoded and the verifier finds no `\"access_token\":\"`, and", "`connection: close`, which ends every notarized session here;", - "neither is compared. The service is the party that sends this", - "request, so unlike X's it is agreed here BEFORE it is written", - "rather than read off a browser that already sends it." + "neither is compared." ] }, "identity": { diff --git a/solidity/contracts/ceremony/test/GitHubPlatformVerifier.t.sol b/solidity/contracts/ceremony/test/GitHubPlatformVerifier.t.sol index 4aec45c4..1be312c1 100644 --- a/solidity/contracts/ceremony/test/GitHubPlatformVerifier.t.sol +++ b/solidity/contracts/ceremony/test/GitHubPlatformVerifier.t.sol @@ -110,32 +110,34 @@ contract GitHubPlatformVerifierTest is Test { ); } - /// The exchange: request line, then the revealed body PREFIX. The secret is - /// ordered last and committed, so the prefix stops where it begins. - /// The exchange request as the profile fixes it: one revealed run up to - /// the secret, which is ordered last and committed. The head boundary sits - /// inside the revealed run, so the body is located by the framing rather - /// than by a range position. - function _exchangeSent() private view returns (AttestationBuilder.Direction memory) { - bytes memory prefix = abi.encodePacked( + /// The exchange body: the five fields the profile lists, the credential + /// among them and revealed like the rest. + function _exchangeBody() private view returns (bytes memory) { + return abi.encodePacked( "client_id=Iv1.8a61f9b3a7aba766&code=abc&redirect_uri=https%3A%2F%2Fa.example&code_verifier=", - CeremonyAuthorization.codeVerifier(digest, AUTH_NONCE) + CeremonyAuthorization.codeVerifier(digest, AUTH_NONCE), + "&client_secret=0123456789abcdef0123456789abcdef" ); - // The declared length covers the committed `client_secret` too: it is - // the body GitHub parsed, not the part of it this side can read. - bytes memory whole = abi.encodePacked(_exchangeHead(prefix.length + 40), prefix); - uint32 wholeEnd = uint32(whole.length); - uint32 secretEnd = wholeEnd + 40; // `&client_secret=`, committed + } + /// One revealed run covering a request whole, with no commitment: the + /// shape `github/v1` fixes for its exchange. + function _wholeSent(bytes memory whole) private pure returns (AttestationBuilder.Direction memory) { return AttestationBuilder.Direction({ revealed: AttestationBuilder.one(AttestationBuilder.Range({start: 0, value: whole})), - commitments: AttestationBuilder.one( - AttestationBuilder.Commitment({start: wholeEnd, end: secretEnd, value: bytes32(uint256(0x5EC1E7))}) - ), - length: secretEnd + commitments: AttestationBuilder.none(), + length: uint32(whole.length) }); } + /// The exchange request as the profile fixes it: one revealed run over the + /// request whole. The head boundary sits inside that run, so the body is + /// located by the framing rather than by a range position. + function _exchangeSent() private view returns (AttestationBuilder.Direction memory) { + bytes memory body = _exchangeBody(); + return _wholeSent(abi.encodePacked(_exchangeHead(body.length), body)); + } + /// The exchange response: the bearer committed and framed by the revealed /// `"access_token":"` anchor and its closing quote, every other byte hidden /// behind a commitment of its own. @@ -514,9 +516,9 @@ contract GitHubPlatformVerifierTest is Test { proof: vm.parseJsonBytes(json, ".identity.notary_signature") }); ICeremony.VerifiedClaim memory f = this.run{value: quote}(s); - assertEq(f.userId, "18346821"); - assertEq(f.handle, "xgreenx"); - assertEq(string(f.clientIdentifier), "Ov23liIOfT7uQ9707Fpz"); + assertEq(f.userId, "293919812"); + assertEq(f.handle, "testyakly"); + assertEq(string(f.clientIdentifier), "Iv23lioEM9NAR9vO8CmT"); assertEq(f.sessionId, digest); } @@ -561,8 +563,9 @@ contract GitHubPlatformVerifierTest is Test { return ICeremony.Attestation({attestedData: attested, proof: _sign(attested)}); } - /// Commitment FIRST, then one revealed run: tiles, one commitment as the profile demands, - /// head boundary inside the run. Only TlsNotaryVerifierBase.sol:294 stands between this and acceptance. + /// A commitment over the request line, then one revealed run: the two + /// tile, so coverage passes and the origin rule is what refuses this -- + /// before the commitment count is ever looked at. function test_rejectsAnExchangeWhoseRequestLineIsHidden() public { bytes memory prefix = abi.encodePacked( "client_id=Iv1.8a61f9b3a7aba766&code=abc&redirect_uri=https%3A%2F%2Fa.example&code_verifier=", @@ -583,6 +586,28 @@ contract GitHubPlatformVerifierTest is Test { this.run{value: quote}(s); } + /// @dev The exchange layout itself, asserted against the verifier rather + /// than against the generated constant: a request that hides a body + /// suffix behind a commitment is refused, however well formed the + /// rest of it is. + function test_rejectsAnExchangeThatCommitsABodySuffix() public { + bytes memory body = _exchangeBody(); + bytes memory whole = abi.encodePacked(_exchangeHead(body.length + 8), body); + uint32 wholeEnd = uint32(whole.length); + AttestationBuilder.Direction memory sent = AttestationBuilder.Direction({ + revealed: AttestationBuilder.one(AttestationBuilder.Range({start: 0, value: whole})), + commitments: AttestationBuilder.one( + AttestationBuilder.Commitment({start: wholeEnd, end: wholeEnd + 8, value: bytes32(uint256(0x5EC1E7))}) + ), + length: wholeEnd + 8 + }); + bytes memory a = AttestationBuilder.encode(CeremonyProfile.AUTHORITY_GITHUB, T0, sent, _exchangeResponse()); + TlsNotaryVerifierBase.TlsNotaryProof memory s = _payload(); + s.tokenSession = ICeremony.Attestation({attestedData: a, proof: _sign(a)}); + vm.expectRevert(abi.encodeWithSelector(TlsNotaryVerifierBase.WrongTokenRequestLayout.selector, 1, 1)); + this.run{value: quote}(s); + } + /// @dev REQ-COMMON-21B, on the profile whose exchange this repository does /// not compose: `github/v1` pins its OWN head, so a service sending a /// request X's constant would have accepted is still refused here. @@ -590,24 +615,12 @@ contract GitHubPlatformVerifierTest is Test { /// what decides whether GitHub reads the bytes `formField` reads as a /// form at all. function test_rejectsAnotherMediaTypeOnTheExchange() public { - bytes memory prefix = abi.encodePacked( - "client_id=Iv1.8a61f9b3a7aba766&code=abc&redirect_uri=https%3A%2F%2Fa.example&code_verifier=", - CeremonyAuthorization.codeVerifier(digest, AUTH_NONCE) - ); + bytes memory body = _exchangeBody(); bytes memory head = _exchangeHead( "host: github.com\r\ncontent-type: application/json\r\naccept: application/json\r\nconnection: close\r\n", - prefix.length + 40 + body.length ); - bytes memory whole = abi.encodePacked(head, prefix); - AttestationBuilder.Direction memory sent = AttestationBuilder.Direction({ - revealed: AttestationBuilder.one(AttestationBuilder.Range({start: 0, value: whole})), - commitments: AttestationBuilder.one( - AttestationBuilder.Commitment({ - start: uint32(whole.length), end: uint32(whole.length) + 40, value: bytes32(uint256(0x5EC1E7)) - }) - ), - length: uint32(whole.length) + 40 - }); + AttestationBuilder.Direction memory sent = _wholeSent(abi.encodePacked(head, body)); bytes memory a = AttestationBuilder.encode(CeremonyProfile.AUTHORITY_GITHUB, T0, sent, _exchangeResponse()); TlsNotaryVerifierBase.TlsNotaryProof memory s = _payload(); s.tokenSession = ICeremony.Attestation({attestedData: a, proof: _sign(a)}); @@ -619,25 +632,13 @@ contract GitHubPlatformVerifierTest is Test { /// Service's own business -- a `user-agent`, say -- as long as it is /// not one of the forbidden names. The exchange still verifies. function test_acceptsAnUnlistedHeaderOnTheExchange() public { - bytes memory prefix = abi.encodePacked( - "client_id=Iv1.8a61f9b3a7aba766&code=abc&redirect_uri=https%3A%2F%2Fa.example&code_verifier=", - CeremonyAuthorization.codeVerifier(digest, AUTH_NONCE) - ); + bytes memory body = _exchangeBody(); bytes memory head = _exchangeHead( "host: github.com\r\nuser-agent: libid-bridge/0.3.0\r\ncontent-type: application/x-www-form-urlencoded\r\n" "accept: application/json\r\nconnection: close\r\n", - prefix.length + 40 + body.length ); - bytes memory whole = abi.encodePacked(head, prefix); - AttestationBuilder.Direction memory sent = AttestationBuilder.Direction({ - revealed: AttestationBuilder.one(AttestationBuilder.Range({start: 0, value: whole})), - commitments: AttestationBuilder.one( - AttestationBuilder.Commitment({ - start: uint32(whole.length), end: uint32(whole.length) + 40, value: bytes32(uint256(0x5EC1E7)) - }) - ), - length: uint32(whole.length) + 40 - }); + AttestationBuilder.Direction memory sent = _wholeSent(abi.encodePacked(head, body)); bytes memory a = AttestationBuilder.encode(CeremonyProfile.AUTHORITY_GITHUB, T0, sent, _exchangeResponse()); TlsNotaryVerifierBase.TlsNotaryProof memory s = _payload(); s.tokenSession = ICeremony.Attestation({attestedData: a, proof: _sign(a)}); diff --git a/solidity/contracts/ceremony/test/fixtures/github-ceremony-real.json b/solidity/contracts/ceremony/test/fixtures/github-ceremony-real.json index c93c89a0..be8e575d 100644 --- a/solidity/contracts/ceremony/test/fixtures/github-ceremony-real.json +++ b/solidity/contracts/ceremony/test/fixtures/github-ceremony-real.json @@ -1,27 +1,27 @@ { "authorization_digest": "0x09951c39dc62c5e74abc8c8607ec5c01b78e8d8bf6ff6da82f4cecbe72ace31f", "authorization_nonce": "0x5555555555555555555555555555555555555555555555555555555555555555", - "captured_at": 1789084207, + "captured_at": 1789657072, "ceremony_version": 1, "chain_id": 31337, "code_verifier": "NaJ86Rvwho11eSM3UDVCvhXt8Ykz9mWqpo2EVki2PH0", "identity": { - "attested_data": "0xa5d9c1d593bc385a23a2d56116aab1951e3c66296476c7a7396a515105e8b2c1000000006aa3422f0000014900000c710000000000000002000000000000000000000040474554202f7573657220485454502f312e310d0a686f73743a206170692e6769746875622e636f6d0d0a617574686f72697a6174696f6e3a20426561726572200000006800000000000000e10d0a6163636570743a206170706c69636174696f6e2f766e642e6769746875622b6a736f6e0d0a757365722d6167656e743a204d6f7a696c6c612f352e3020284d6163696e746f73683b20496e74656c204d6163204f5320582031305f31355f3729204170706c655765624b69742f3533372e333620284b48544d4c2c206c696b65204765636b6f29204368726f6d652f3132382e302e302e30205361666172692f3533372e33360d0a782d6769746875622d6170692d76657273696f6e3a20323032322d31312d32380d0a636f6e6e656374696f6e3a20636c6f73650d0a0d0a000000000000000100000040000000684a8b915bb6394ab6c2bfba66f54089a17e90eed92df06160e9796c24504a84130000000000000002000005b30000000000000012226c6f67696e223a202278677265656e7822000005c9000000000000000f226964223a2031383334363832312c000000000000000300000000000005b35183621029bef8d9d52e91675ec58ecf770847f2e499edfd905c55d3cd65f0bd000005c5000005c9d820f020ce766b8ba0e815ffb641af76a82c7fd66731482284a43b8aee093ca6000005d800000c71af235a0c9e39a1d376b0587288d38baebee103983763f194fbf81083912dc201", + "attested_data": "0xa5d9c1d593bc385a23a2d56116aab1951e3c66296476c7a7396a515105e8b2c1000000006aabfff00000014900000b770000000000000002000000000000000000000040474554202f7573657220485454502f312e310d0a686f73743a206170692e6769746875622e636f6d0d0a617574686f72697a6174696f6e3a20426561726572200000006800000000000000e10d0a6163636570743a206170706c69636174696f6e2f766e642e6769746875622b6a736f6e0d0a757365722d6167656e743a204d6f7a696c6c612f352e3020284d6163696e746f73683b20496e74656c204d6163204f5320582031305f31355f3729204170706c655765624b69742f3533372e333620284b48544d4c2c206c696b65204765636b6f29204368726f6d652f3132382e302e302e30205361666172692f3533372e33360d0a782d6769746875622d6170692d76657273696f6e3a20323032322d31312d32380d0a636f6e6e656374696f6e3a20636c6f73650d0a0d0a000000000000000100000040000000686ceadbed508dd07aff0789c145fd3d95e677fa44b2647aabe2599c20913367db0000000000000002000006310000000000000014226c6f67696e223a20227465737479616b6c7922000006490000000000000010226964223a203239333931393831322c0000000000000003000000000000063196a24541d69b549beeaa08dcbffbcb56aa028619098a2cbc5de20820071f8bc30000064500000649a27b4f47b7a9e2443d084c6de4063c5c69b17cc47e182eaf85ddb233a2bf96540000065900000b77a86ec4c9f4198203a5c32e6b8055023b7fc8901fef060e45b6d977eea2c6da23", "authority": "api.github.com", - "created_at": 1789084207, + "created_at": 1789657072, "endpoint": "https://api.github.com/user", - "notary_signature": "0x6177d3bedb32a33c5396ead197a7cf2f787636b55143e8a722e2237975be4a232d0cc48b58cc6fd710f1f365f72a5073d6c369934151dc315cccebdcc9a9f79e1b" + "notary_signature": "0xd3d5bddf9008cd9be7b8ff51db584c3308d8c006366e0adef2ce8169b9629b16642ad918c6fb64bc5a4d8ad6896127069be32191cfc9223d7a3992805a9536001b" }, "notary": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "operation_domain": "0xcb29bed0428519ef88a3d670e8203db76e06f41aca3e684e2c63b516c9b93e1b", "platform": "github", "source": "captured: a real MPC-TLS session against the platform, the verifier in-process, by libid-rs examples/capture_ceremony.rs", "token": { - "attested_data": "0x06785da520052bf40d5bf506fb493c41162f55d4e17dffa8b21f02598e981533000000006aa342050000019f000012410000000000000001000000000000000000000168504f5354202f6c6f67696e2f6f617574682f6163636573735f746f6b656e20485454502f312e310d0a686f73743a206769746875622e636f6d0d0a636f6e74656e742d747970653a206170706c69636174696f6e2f782d7777772d666f726d2d75726c656e636f6465640d0a6163636570743a206170706c69636174696f6e2f6a736f6e0d0a636f6e6e656374696f6e3a20636c6f73650d0a636f6e74656e742d6c656e6774683a203233390d0a0d0a636c69656e745f69643d4f7632336c69494f66543775513937303746707a26636f64653d63383434383335663636316164633930616266362672656469726563745f7572693d687474702533412532462532463132372e302e302e31253341383732322532466175746825324667697468756225324663616c6c6261636b26636f64655f76657269666965723d4e614a3836527677686f313165534d33554456437668587438596b7a396d5771706f3245566b69325048300000000000000001000001680000019f3c3c7c45453cdc6951880d71bc1928371f2074323b3d45f1b2937c775c9e767f0000000000000002000011d60000000000000010226163636573735f746f6b656e223a220000120e000000000000000122000000000000000300000000000011d6b4c1eeaa38e297c51ce58609d94b3e447c88ad940fbb782b63fa78e22a8b86ed000011e60000120e672ce8194b6527adae0462be6adb1693612c6d714a5ae2ea35d5b60a5435885c0000120f00001241e028481b94f4c033d88de66386fd9b4cefca86568ba9d09dd7f3af9f8918e1e9", + "attested_data": "0x06785da520052bf40d5bf506fb493c41162f55d4e17dffa8b21f02598e981533000000006aabffdb00000196000012ec0000000000000001000000000000000000000196504f5354202f6c6f67696e2f6f617574682f6163636573735f746f6b656e20485454502f312e310d0a686f73743a206769746875622e636f6d0d0a636f6e74656e742d747970653a206170706c69636174696f6e2f782d7777772d666f726d2d75726c656e636f6465640d0a6163636570743a206170706c69636174696f6e2f6a736f6e0d0a636f6e6e656374696f6e3a20636c6f73650d0a636f6e74656e742d6c656e6774683a203233300d0a0d0a636c69656e745f69643d497632336c696f454d394e415239764f38436d5426636f64653d34646339313832643066623031313433366435612672656469726563745f7572693d687474702533412532462532463132372e302e302e31253341383732322532466175746825324663616c6c6261636b26636f64655f76657269666965723d4e614a3836527677686f313165534d33554456437668587438596b7a396d5771706f3245566b693250483026636c69656e745f7365637265743d3430666665353235303137363139623337306637306537353564653139633738373739313565383600000000000000000000000000000002000011f00000000000000010226163636573735f746f6b656e223a2200001228000000000000000122000000000000000300000000000011f0e8ca400a1e9696eccb427f8f2642adb14947aa9acedea44101addaae631b54b6000012000000122840b353ca528c35c8a82c80ace2f96d195a87be1d09f5fded20238a551183768d00001229000012ecdc2293498f444a554c2757fb2a89c31b5ed22afbaed3274e5ad3d2801e5cc148", "authority": "github.com", - "created_at": 1789084165, + "created_at": 1789657051, "endpoint": "https://github.com/login/oauth/access_token", - "notary_signature": "0xe25b86e34c0c354dab242be5c80af5f31886002077135a69511655dad048fda672ce3c2562f9b466c48ccada92dd4c21accd9ef1c3cdd79a682c4c12ed326f891b" + "notary_signature": "0x2ebee6ee6912b495c07d1f527e03119681fe12c09b0783805b4fd73c5e89f9d54b3ef6638fa31024fea276ef3b343dc4d874febbeb1d5cc86b0bd96d4e0eb95b1b" }, "transaction_data": "0x000000000000000000000000000000000000000000000000000000000000beef" } diff --git a/solidity/contracts/ceremony/test/fixtures/github-ceremony-session.json b/solidity/contracts/ceremony/test/fixtures/github-ceremony-session.json index 5ae23b84..12cb835e 100644 --- a/solidity/contracts/ceremony/test/fixtures/github-ceremony-session.json +++ b/solidity/contracts/ceremony/test/fixtures/github-ceremony-session.json @@ -59,22 +59,12 @@ "operation_domain": "0xcb29bed0428519ef88a3d670e8203db76e06f41aca3e684e2c63b516c9b93e1b", "platform": "github", "token": { - "attested_data": "0x06785da520052bf40d5bf506fb493c41162f55d4e17dffa8b21f02598e9815330000000069800e800000017f000000c30000000000000001000000000000000000000140504f5354202f6c6f67696e2f6f617574682f6163636573735f746f6b656e20485454502f312e310d0a686f73743a206769746875622e636f6d0d0a636f6e74656e742d747970653a206170706c69636174696f6e2f782d7777772d666f726d2d75726c656e636f6465640d0a6163636570743a206170706c69636174696f6e2f6a736f6e0d0a636f6e6e656374696f6e3a20636c6f73650d0a636f6e74656e742d6c656e6774683a203230370d0a0d0a636c69656e745f69643d4976312e3861363166396233613761626137363626636f64653d6162633132332672656469726563745f7572693d68747470732533412532462532466170702e6578616d706c65253246636226636f64655f76657269666965723d4e614a3836527677686f313165534d33554456437668587438596b7a396d5771706f3245566b69325048300000000000000001000001400000017f20fc3ee20a3c9a01ba2fe4d83ee3d79aef04568ad7904faebf3197eb6954efdb00000000000000020000006a0000000000000010226163636573735f746f6b656e223a22000000a00000000000000001220000000000000003000000000000006a7e321e6797d9677799af03b5066aa04bf19ed953750f3ac525c42b984d34777f0000007a000000a0b03a9a05bacef13ee707781f408db6742e38a5ec05ebf4244a6237c710ae986f000000a1000000c3e651e1d0e45b96591c33265d677a2033572834836de79568b908170bf5a28dc8", + "attested_data": "0x06785da520052bf40d5bf506fb493c41162f55d4e17dffa8b21f02598e9815330000000069800e800000017f000000c3000000000000000100000000000000000000017f504f5354202f6c6f67696e2f6f617574682f6163636573735f746f6b656e20485454502f312e310d0a686f73743a206769746875622e636f6d0d0a636f6e74656e742d747970653a206170706c69636174696f6e2f782d7777772d666f726d2d75726c656e636f6465640d0a6163636570743a206170706c69636174696f6e2f6a736f6e0d0a636f6e6e656374696f6e3a20636c6f73650d0a636f6e74656e742d6c656e6774683a203230370d0a0d0a636c69656e745f69643d4976312e3861363166396233613761626137363626636f64653d6162633132332672656469726563745f7572693d68747470732533412532462532466170702e6578616d706c65253246636226636f64655f76657269666965723d4e614a3836527677686f313165534d33554456437668587438596b7a396d5771706f3245566b693250483026636c69656e745f7365637265743d303132333435363738396162636465663031323334353637383961626364656630313233343536373839616263646566000000000000000000000000000000020000006a0000000000000010226163636573735f746f6b656e223a22000000a00000000000000001220000000000000003000000000000006a69ef2b9767091c2e7474d55f22b3891053d487f75010f3cc4229d8cc174c3ad20000007a000000a06c698116c9abf9ab89846e5ee03bf56acafb76180010ffd52791e52e26c384f6000000a1000000c3a9c474140fd209c2808e8fe0c58c65dbc3ccd2e672a1db595b2565e84672f9fd", "endpoint": "https://github.com/login/oauth/access_token", - "notary_signature": "0x22a0630c0b94db766cdf35de49558f96d009f7a3f6d72cffe6533cf2611ef1be0ed3781fb25c745ac5dbeef3d493d1e68d3e5173f7585466b094de2dcde835561c", + "notary_signature": "0xf34f6f6de1d2daec424ff0fb2b89a6afeb43f9737a226c1a11bf0c8fb75146a9415cc33046b0757aba2a371ee58087067115280cffc17be1fed3fcc3593b0db71b", "openings": [ { "blinder": "0xf2bea951f6b30b9ac6d20b9b5fc8d897", - "direction": "sent", - "ranges": [ - [ - 320, - 383 - ] - ] - }, - { - "blinder": "0xe906469f340e60279fee288dfa21d72c", "direction": "received", "ranges": [ [ @@ -84,7 +74,7 @@ ] }, { - "blinder": "0x4e9159aa47b770b706d1b66d61435d7a", + "blinder": "0xe906469f340e60279fee288dfa21d72c", "direction": "received", "ranges": [ [ @@ -94,7 +84,7 @@ ] }, { - "blinder": "0x7973e68ddcaa8649646a049a405fab62", + "blinder": "0x4e9159aa47b770b706d1b66d61435d7a", "direction": "received", "ranges": [ [ diff --git a/ts/packages/contracts/src/ceremony/profiles.ts b/ts/packages/contracts/src/ceremony/profiles.ts index 2c7633ba..69683d24 100644 --- a/ts/packages/contracts/src/ceremony/profiles.ts +++ b/ts/packages/contracts/src/ceremony/profiles.ts @@ -106,10 +106,10 @@ export const X: Profile = { } /** - * A confidential client. The exchange runs in the deployment because the - * secret cannot reach the browser, and the two sessions are served by - * DIFFERENT hosts -- which is why one pinned authority per profile would - * be wrong. + * The credential GitHub calls `client_secret` is sent and revealed, so + * an attestation publishes it and nothing here is confidential. The two + * sessions are served by DIFFERENT hosts -- which is why one pinned + * authority per profile would be wrong. */ export const GITHUB: Profile = { platform: 'github', @@ -121,7 +121,7 @@ export const GITHUB: Profile = { path: '/login/oauth/access_token', requestLine: 'POST /login/oauth/access_token ', }, - secretField: 'client_secret', + secretField: null, requiredHeaders: ['host: github.com', 'content-type: application/x-www-form-urlencoded'], }, identity: {