diff --git a/.github/workflows/spec-update.yml b/.github/workflows/spec-update.yml new file mode 100644 index 0000000..d343517 --- /dev/null +++ b/.github/workflows/spec-update.yml @@ -0,0 +1,29 @@ +name: Spec Update +on: + workflow_dispatch: {} + pull_request: {} + push: + branches: [main] +jobs: + main: + name: Build, Validate and Deploy + runs-on: ubuntu-20.04 + permissions: + contents: write + steps: + - uses: actions/checkout@v3 + - uses: w3c/spec-prod@v2 + with: + TOOLCHAIN: bikeshed + + # Modify as appropriate + GH_PAGES_BRANCH: gh-pages + + # if your doc isn't in the root folder, + # or Bikeshed otherwise can't find it: + SOURCE: spec.bs + + # output filename defaults to your input + # with .html extension instead, + # but if you want to customize it: + DESTINATION: index.html diff --git a/README.md b/README.md index 156e9e2..ff30d51 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ DBSC(E) removes the vulnerability DBSC has, where a malware, if already present ## High level overview ![High level diagram](reg_and_refresh.svg) -[Link to editable diagram](https://sequencediagram.org/index.html#initialData=A4QwTgLglgxloDsIAIBEAVACgWVckAzshAFCiSzwhJoBCYA9gO4ECmYehyARmeNHEQpUAZXYA3dpyJMScktwC0APiYAuZASgBzBIqgJkAMwA2zEkxXcNAJgAMd5EwD0W3awAm+wzAZJWSAA0yGCsBMB+bMgGMCYArh5hyAAWrCCJYMQMmhD8mqwwcaH5BFp+AHTIyAA6CAASaRkaqKIFimKlUH6KAEqs2lAEEGAg0H4abJ1+APpQiUhQRlDsALzl68EwySAmJgHarGvrLbUAFFs7ewgHIawAjnFQoR7GDJnAYFDio6zIANasACeyA+DAYRmQ4JBDFKYTKCAAlCQEAwIL8GJJMtYeIwWOxoggoNAfkRJvCeAYPAZtLVuIRPJDDKl0viPmEAjBWAoVBANKEHmEUDBQj9-kDQE8SBArHzWBAioZgHFuCZYGLAcjUejMTwNMK0mjkAApADq6Cczguu32XKUyl5-Ligs0OkMpvQUplt3lYDdZpdulGRVtKnUyEwAHkRObXAVg2SughXLlIAm-DUELUWgBvVA7bSoNTrcrBVAQQHAQuod2oYLFgC+2eLgVQAMBVYAPEqVbBpm3lKh6y1kJrDRj8WGhm9ft3VTB1cFBSAewRkiV4RYvfZHC5fAw-st8AgXmnDABJAAitIYHmBMXiiVJvmA6Ihe4PSVObx0BmQAGoQVGZIEVqWpmSaNBWmlABhMEPw0EA4ggZJpnfZYTgQbgb0BZpUFzU9ZnmaAlikIt1iHVASBWFYRAYABbX5oAYwDYQIYtqNHbV8WxOI2EyWJYD+IhVQQP5XneIDkFceiCj8NEkG5ZRsS2AoxMWSFPgGBA-1AZDbgeJ4kiwuJj2QNCuR2FBjNM8zkBRFBHUMjxFLDABxABRGMCBk3x-AUywlNsBwLV8+TSFYEwomsl5bMc55FOU1IYDUiE4oZcyiFYAAPQZSEs-THmeMy4MPNk2BoagXns5BsuAJyXI0DyvJ8uSAlIALsW3ELWoUiKojSmKSt+OjBi0a4NJqrK6vi+yuKxWUBSGZBEiMdgiqYZIoD2JxfgOIUhrY9YGuQJqpLYQpQlPZxQiMS61zAxp2FwsQYHaOFE0UM8PA0ABtU9kEvABdFpN0CgAWOwAEZQPqR6wGetoOnhRRoO2a1rlYCZ3pmOY2sWZYwCOEszLRq4DiJkHZshHVlJFQ13RC0mbUUh17idJa3D9D1pUC70FWNf1OaDUJjtO2MLvZKYkxuu6M3Ap7IJet6pd6MIIgQNhfvdYHKKp8dMjDb5VQ8UVQShKEIlYxNQesLrdyGo8T2x88r0w7CCViBIkgIZ9X2K-dliIL9NN-ADdOAmH5fhxW5RRoaEKQlDzIwrDb1w-DncIvGSI4Mjygozjqe4rGJfXRMaqGZdVVXTxgkuuIGNqb8tJ2ArBVFzyzpavz2q3YKXG85jQrakgAmcsegA) +[Link to editable diagram](https://sequencediagram.org/index.html#initialData=A4QwTgLglgxloDsIAIBEAVACgWVckAzshAFCiSzwhJoBCYA9gO4ECmYehyARmeNHEQpUAZXYA3dpyJMScktwC0APiYAuZASgBzBIqgJkAMwA2zEkxXcNAJgAMd5EwD0W3awAm+wzAZJWSAA0yGCsBMB+bMgGMCYArh5hyAAWrCCJYMQMmhD8mqwwcaH5BFp+AHTIyAA6CAASaRkaqKIFimKlUH6KAEqs2lAEEGAg0H4a5ZPIMMkgJiYB2qwAvJPlLbUAFDNzCwhLIawAjnFQoR7GDJnAYFDio6zIANasAJ7INwwMRsjfHwylMJlBAAShICAYEEeDEkmWsPEYLHY0QQUGgDyIbE6fh4Bg8Bm0tW4hE8v0MqXSyJuYQCMFYChUEA0oROYRQMFCD2eb1AZxIECszNYECKhmAcW4Jlg3Ne4Mh0NhPA0HLSUOQACkAOroJzOHbzRb0pTKJksuJszQ6Qxa9D8wWHEVga3ay26UZFI0qdTITAAeREOtcBQ9WOBrlykFDXUMtVqLQA3qg5tpUGo1sFUBBXsBU6gbahgmsAL7x9OoF6vXMAHnFktgAH0K8pUEWWsg5WqYcjvUMro9a1KYDLgmyQHWCMkSsCLPb7I4XL4GE8oI9qBcoziAJIAESJDA87xi8USmN8wGhP0Xy6SmyuOgMyAA1B9RskQbGEBSmmhWgKAMJfNeGggHEEDJPWV4rhsCDcPurzNKgiYbgg9ZQIkSBQEYK4cGmkytqgJDLMsIgMAAto80DkS+gIEGsREdgqyLwnEbCZLEsBPEQUoIE8lzXK+yCuGRBR+FCSAMso8IzAUvGYb8twDAgj6gGBhwnGcSSwXECAXJB9JzCgWk6dMgErsgEIoGaGkeBJ3oAOIAKKBgQwm+P44mWJJtgOLqbliaQrAmFERm6aZjxWecElSakMCyT8EWknpRCsAAHoMpAGWppznCZS5mdSbA0Gu5mQsgqXANZtkaI5zmuaJASkJ58Jzr59XiYFUQJaFeWPKRgxaPs8llSlFWRRZjFwkKrJDMgiRGOwOVMMkUALE4jxLOyYW0ZMVXIDVglsIUoTIc4oRGMdk61F+7AIWIMDtEC0aKJuHgaAA2shyA7gAui0M5eQALHYACMH4NJSYC3W0HTAoof6zAa+ysBoyGoeh0BYewqyTME+p7Es2PrAR42-IqUmcmqNq+Qj+OeiaahmhabjOraApeQ6ooai6zPuqEu37UGR00tiCCnaw51hJdn6NDdP53Q9Iu9GEEQIGw702r9xPyqT3YaPcUoeFynx-H8EQ0dG-3WC1C5hfgxmfTue4HiisQJEkBBnheuXXkQt4KQ+z4qW+H7XZDcvCnDYXAaB4F6dBsEHghSGPX4aMNZh2GpsWf0k12k35ELU7RmVQxjlKE6eMEx1xORtR3opcxZWy-NOQddXuY1s4+S4LlUX5DUkAENlD0AA) The general flow of a secure session is as follows: 1. The website requests that the browser start a new session, providing an HTTP endpoint to negotiate registration parameters. 1. The browser creates a device-bound key pair, and calls the registration HTTP endpoint to set up the session and register the public key. @@ -125,16 +125,22 @@ We do not reccomend this option for most deployments, but it is possibly for tho The session start process is initiated by the server attaching a header with Sec-Session-Registration and appropriate parameters, this looks like: ```http HTTP/1.1 200 OK -Sec-Session-Registration: (RS256 ES256);challenge="nonce";path="StartSession" +Sec-Session-Registration: (RS256 ES256);challenge="challenge_value";path="StartSession" ``` -This is a structured header with a list of token arguments representing the allowed algorithms (possibilities are ES256 and RS256). The list have multiple string attributes, "path" is required describing the endpoint to use, "challenge" is to provide a nonce for the registration JWT. There is also an optional string attribute called authorization. There can be more than one registration on one response: +This is a structured header with a list of token arguments representing the allowed algorithms (possibilities are ES256 and RS256). The list have multiple string attributes, "path" is required describing the endpoint to use, "challenge" is to provide a challenge value for the registration JWT. There is also an optional string attribute called authorization. There can be more than one registration on one response: ```http HTTP/1.1 200 OK -Sec-Session-Registration: (ES256 RS256);path="path1";challenge="nonce";authorization="authcode" -Sec-Session-Registration: (ES256);path="path2";challenge="nonce" +Sec-Session-Registration: (ES256 RS256);path="path1";challenge="challenge_value";authorization="authcode" +Sec-Session-Registration: (ES256);path="path2";challenge="challenge_value" ``` -The authorization value is optional. If present, it will be sent to the registration endpoint in the `Authorization` header, and included in the registration JWT. This allows passing a bearer token that allows the server to link registration with some preceding sign in flow, as an alternative to the more traditional use of cookies. While this can also facilitate integration with some existing infrastructure, e.g. ones based on OAuth 2.0, this parameter is general and is not limited to the similarly named [Authorization Code](https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.1) in OAuth 2.0. +An equivalent way of writing this is: +```http +HTTP/1.1 200 OK +Sec-Session-Registration: (ES256 RS256);path="path1";challenge="challenge_value";authorization="authcode", (ES256);path="path2";challenge="challenge_value" +``` + +The authorization value is optional for servers to send, but mandatory for clients to implement. If present, it will be sent to the registration endpoint in the `Authorization` header, and included in the registration JWT. This allows passing a bearer token that allows the server to link registration with some preceding sign in flow, as an alternative to the more traditional use of cookies. While this can also facilitate integration with some existing infrastructure, e.g. ones based on OAuth 2.0, this parameter is general and is not limited to the similarly named [Authorization Code](https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.1) in OAuth 2.0. #### Session Registration JWT The browser responds to the session start by selecting a compatible signature algorithm and creating a device-bound private key for the new session. It then makes the following HTTP request (assuming the endpoint URL is https://auth.example.com/securesession): @@ -149,8 +155,8 @@ Cookie: whatever_cookies_apply_to_this_request=value; Sec-Session-Response: registration JWT ``` -The JWT is signed with the newly created private key, and needs to contain the following values: -```json +The JWT is signed with the newly created private key, and needs to contain the following values (the public key is in the [JWK](https://datatracker.ietf.org/doc/html/rfc7517) format): +```jsonc // Header { "alg": "Signature Algorithm", @@ -159,9 +165,12 @@ The JWT is signed with the newly created private key, and needs to contain the f // Payload { "aud": "URL of this request", - "jti": "nonce", + "jti": "challenge_value", "iat": "timestamp", - "key": "public key", + "key": { + "kty": "key type", + "": "", + }, "authorization": "", // optional, only if set in registration header } ``` @@ -176,7 +185,7 @@ Cache-Control: no-store Set-Cookie: auth_cookie=abcdef0123; Domain=example.com; Max-Age=600; Secure; HttpOnly; ``` -```json +```jsonc { "session_identifier": "session_id", "refresh_url": "/RefreshEndpoint", @@ -214,9 +223,12 @@ If the request is not properly authorized, the server can request a new signed r ```http HTTP/1.1 401 -Sec-Session-Challenge: challenge="nonce" +Sec-Session-Challenge: "challenge_value" ``` +Where Sec-Session-Challenge header is a structured header with a list of challenge values that may specify an optional "id" parameter: "challenge_value";id="session_id". +The challenge applies to the current context if "id" is not present; otherwise it applies to the specific session. The browser ignores the challenge if "id" doesn't match any session locally. + Subsequently, as long as the browser considers this session "active", it follows the steps above, namely by refreshing the auth_cookie whenever needed, as covered in the next section. Note if multiple cookies are required, the browser returns multiple Set-Cookie headers, with corresponding entries in the "credentials" array in the response body. @@ -243,14 +255,28 @@ In response to this the server can optionally first request a proof of possessio ```http HTTP/1.1 401 -Sec-Session-Challenge: session_identifier="session_id",challenge="nonce" +Sec-Session-Challenge: "challenge_value";id="session_id" ``` The server can also serve challenges ahead of time attached to any response as an optimization, for example: ```http HTTP/1.1 XXX -Sec-Session-Challenge: session_identifier="session_id",challenge="nonce" +Sec-Session-Challenge: "challenge_value";id="session_id" +``` + +It is also possible to send challenges to multiple sessions: +```http +HTTP/1.1 XXX +Sec-Session-Challenge: "challenge 1";id="session 1" +Sec-Session-Challenge: "challenge 2";id="session 2" +``` + +This can also be formatted as: +```http +HTTP/1.1 XXX +Sec-Session-Challenge: "challenge 1";id="session 1", "challenge 2";id="session 2" ``` +as each challenge is a structured header item. The browser replies to that response with a Sec-Session-Response header, containing a signed JWT: @@ -262,7 +288,7 @@ Sec-Session-Response: refresh JWT The JWT contains: ```json { - "jti": "nonce", + "jti": "challenge_value", "aud": "the URL to which the Sec-Session-Response will be sent", "sub": "the session ID corresponding to the binding key", } diff --git a/reg_and_refresh.svg b/reg_and_refresh.svg index 1a04036..bb23052 100644 --- a/reg_and_refresh.svg +++ b/reg_and_refresh.svg @@ -1 +1 @@ -participant%20%22TPM%22%20as%20t%0Aparticipant%20%22Browser%22%20as%20b%0Aparticipant%20%22Server%22%20as%20w%0A%0A%0A%0Ab-%3Ew%3A%20sign-in%20flow%0Aw-%3Eb%3A%20200%20w%2Fsigned-in%20content%2C%20response%20includes%20header%20to%20start%20secure%20session.%20%20%5CnHeader%3A%20%22%22Sec-Session-Registration%3A%20session_identifier%3D...%2C%20challenge%3D...%22%22%5Cn(challenge%20required%20for%20private%20key%20proof%20of%20possession)%0Anote%20over%20b%3A%20browser%20initiates%20session%20binding%5Cnbased%20on%20header%20presence%0Ab-%3Et%3A%20request%20create%20keypair%0At-%3Eb%3A%20return%20public%20key%0Anote%20over%20b%3A%20create%20JWT%20w%2Fchallenge%0Ab-%3Et%3Arequest%20sign%20JWT%0At-%3Eb%3A%20return%20JWT%20signature%0Ab-%3Ew%3A%20POST%20%2Fsecuresession%2Fstartsession%20%5Cn%5Cn%22%22%7B%22alg%22%3A...%2C%20%22typ%22%3A%22JWT%22%2C%20...%7D%7B...%2C%22key%22%3A%22%3Cpublic_key%3E%22%7D%22%22%20%0Anote%20over%20w%3A%20store%20public%20key%2C%20establish%20session%0Aw-%3Eb%3A%20200%20w%2Fcookie%20and%20session%20ID%5Cnbody%20includes%20scope%20of%20cookies%20(origin%20%2B%20path)%5Cn%5Cnheader%3A%20%22%22Set-Cookie%3A%20auth_cookie%22%22%5Cnbody%3A%20%22%22%7B%22session_identifier%22%3A...%7D%22%22%0A%3D%3DSome%20time%20passes...%3D%3D%0Anote%20over%20b%3A%20user%20clicks%20link%20for%20path%20%2Fsomecontent%0Ab-%3Eb%3A%20check%20if%20origin%2Bpath%20requires%20bound%20cookie%0Aalt%20bound%20cookie%20not%20required%0Ab-%3Ew%3A%20GET%20%2Fsomecontent%0Aw-%3Eb%3A%20200%20w%2Fcontent%0Aelse%20bound%20cookie%20required%0Ab-%3Eb%3A%20check%20if%20required%20cookies%20exist%0Aalt%20required%20cookie%20present%20and%20not%20expired%0Ab-%3Ew%3A%20GET%20%2Fsomecontent%0Aw-%3Eb%3A%20200%20w%2Fcontent%0Aelse%20required%20cookie%20missing%20or%20expired%0Anote%20over%20b%3A%20request%20deferred%20while%20we%20get%20cookies...%0Ab-%3Ew%3A%20GET%20%2Fsecuresession%2Frefresh%20%5Cnheader%3A%20%22%22Sec-Session-Id%3A%20%5Bsession%20ID%5D%22%22%0Aw-%3Eb%3A401%5Cn%5CnHeader%3A%20%22%22Sec-Session-Challenge%3A%20session_identifier%3D...%2C%20challenge%3D...%22%22%0Anote%20over%20b%3A%20create%20JWT%20w%2Fchallenge%0Ab-%3Et%3Arequest%20sign%20JWT%0At-%3Eb%3A%20return%20JWT%20signature%0Ab-%3Ew%3A%20GET%20%2Fsecuresession%2Frefresh%20%5Cnheader%3A%20%22%22Sec-Session-Response%3A%20%5BJWT%5D%22%22%0Anote%20over%20w%3A%20validate%20proof%20of%20possesion%0Aw-%3Eb%3A200%20w%2Fcookie%20and%20session%20ID%5Cnbody%20includes%20scope%20of%20cookies%20(origin%20%2B%20path)%5Cn%5Cnheader%3A%20%22%22Set-Cookie%3A%20auth_cookie%22%22%5Cnbody%3A%20%22%22%7B%22session_identifier%22%3A...%7D%22%22%0Anote%20over%20b%3A%20secure%20session%20established%2C%20resume%5Cnoriginal%20request%0Ab-%3Ew%3A%20GET%20%2Fsomecontent%0Aw-%3Eb%3A%20200%20w%2Fsome%20content%0Aend%0AendTPMBrowserServersign-in flow200 w/signed-in content, response includes header to start secure session.  Header: Sec-Session-Registration: session_identifier=..., challenge=...(challenge required for private key proof of possession)browser initiates session bindingbased on header presencerequest create keypairreturn public keycreate JWT w/challengerequest sign JWTreturn JWT signaturePOST /securesession/startsession {"alg":..., "typ":"JWT", ...}{...,"key":"<public_key>"} store public key, establish session200 w/cookie and session IDbody includes scope of cookies (origin + path)header: Set-Cookie: auth_cookiebody: {"session_identifier":...}Some time passes...user clicks link for path /somecontentcheck if origin+path requires bound cookieGET /somecontent200 w/contentcheck if required cookies existGET /somecontent200 w/contentrequest deferred while we get cookies...GET /securesession/refresh header: Sec-Session-Id: [session ID]401Header: Sec-Session-Challenge: session_identifier=..., challenge=...create JWT w/challengerequest sign JWTreturn JWT signatureGET /securesession/refresh header: Sec-Session-Response: [JWT]validate proof of possesion200 w/cookie and session IDbody includes scope of cookies (origin + path)header: Set-Cookie: auth_cookiebody: {"session_identifier":...}secure session established, resumeoriginal requestGET /somecontent200 w/some contentalt[bound cookie not required][bound cookie required]alt[required cookie present and not expired][required cookie missing or expired] \ No newline at end of file +participant%20%22TPM%22%20as%20t%0Aparticipant%20%22Browser%22%20as%20b%0Aparticipant%20%22Server%22%20as%20w%0A%0A%0A%0Ab-%3Ew%3A%20sign-in%20flow%0Aw-%3Eb%3A%20200%20w%2Fsigned-in%20content%2C%20response%20includes%20header%20to%20start%20secure%20session.%20%20%5CnHeader%3A%20%22%22Sec-Session-Registration%3A%20...%20challenge%3D...%22%22%5Cn(challenge%20required%20for%20private%20key%20proof%20of%20possession)%0Anote%20over%20b%3A%20browser%20initiates%20session%20binding%5Cnbased%20on%20header%20presence%0Ab-%3Et%3A%20request%20create%20keypair%0At-%3Eb%3A%20return%20public%20key%0Anote%20over%20b%3A%20create%20JWT%20w%2Fchallenge%0Ab-%3Et%3Arequest%20sign%20JWT%0At-%3Eb%3A%20return%20JWT%20signature%0Ab-%3Ew%3A%20POST%20%2Fsecuresession%2Fstartsession%20%5Cn%5Cn%22%22%7B%22alg%22%3A...%2C%20%22typ%22%3A%22JWT%22%2C%20...%7D%7B...%2C%22key%22%3A%22%3Cpublic_key%3E%22%7D%22%22%20%0Anote%20over%20w%3A%20store%20public%20key%2C%20establish%20session%0Aw-%3Eb%3A%20200%20w%2Fcookie%20and%20session%20ID%5Cnbody%20includes%20scope%20of%20cookies%20(origin%20%2B%20path)%5Cn%5Cnheader%3A%20%22%22Set-Cookie%3A%20auth_cookie%22%22%5Cnbody%3A%20%22%22%7B%22session_identifier%22%3A...%7D%22%22%0A%3D%3DSome%20time%20passes...%3D%3D%0Anote%20over%20b%3A%20user%20clicks%20link%20for%20path%20%2Fsomecontent%0Ab-%3Eb%3A%20check%20if%20origin%2Bpath%20requires%20bound%20cookie%0Aalt%20bound%20cookie%20not%20required%0Ab-%3Ew%3A%20GET%20%2Fsomecontent%0Aw-%3Eb%3A%20200%20w%2Fcontent%0Aelse%20bound%20cookie%20required%0Ab-%3Eb%3A%20check%20if%20required%20cookies%20exist%0Aalt%20required%20cookie%20present%20and%20not%20expired%0Ab-%3Ew%3A%20GET%20%2Fsomecontent%0Aw-%3Eb%3A%20200%20w%2Fcontent%0Aelse%20required%20cookie%20missing%20or%20expired%0Anote%20over%20b%3A%20request%20deferred%20while%20we%20get%20cookies...%0Ab-%3Ew%3A%20GET%20%2Fsecuresession%2Frefresh%20%5Cnheader%3A%20%22%22Sec-Session-Id%3A%20%5Bsession%20ID%5D%22%22%0Aw-%3Eb%3A401%5Cn%5CnHeader%3A%20%22%22Sec-Session-Challenge%3A%20session_identifier%3D...%2C%20challenge%3D...%22%22%0Anote%20over%20b%3A%20create%20JWT%20w%2Fchallenge%0Ab-%3Et%3Arequest%20sign%20JWT%0At-%3Eb%3A%20return%20JWT%20signature%0Ab-%3Ew%3A%20GET%20%2Fsecuresession%2Frefresh%20%5Cnheader%3A%20%22%22Sec-Session-Response%3A%20%5BJWT%5D%22%22%0Anote%20over%20w%3A%20validate%20proof%20of%20possesion%0Aw-%3Eb%3A200%20w%2Fcookie%20and%20session%20ID%5Cnbody%20includes%20scope%20of%20cookies%20(origin%20%2B%20path)%5Cn%5Cnheader%3A%20%22%22Set-Cookie%3A%20auth_cookie%22%22%5Cnbody%3A%20%22%22%7B%22session_identifier%22%3A...%7D%22%22%0Anote%20over%20b%3A%20secure%20session%20established%2C%20resume%5Cnoriginal%20request%0Ab-%3Ew%3A%20GET%20%2Fsomecontent%0Aw-%3Eb%3A%20200%20w%2Fsome%20content%0Aend%0AendTPMBrowserServersign-in flow200 w/signed-in content, response includes header to start secure session.  Header: Sec-Session-Registration: ... challenge=...(challenge required for private key proof of possession)browser initiates session bindingbased on header presencerequest create keypairreturn public keycreate JWT w/challengerequest sign JWTreturn JWT signaturePOST /securesession/startsession {"alg":..., "typ":"JWT", ...}{...,"key":"<public_key>"} store public key, establish session200 w/cookie and session IDbody includes scope of cookies (origin + path)header: Set-Cookie: auth_cookiebody: {"session_identifier":...}Some time passes...user clicks link for path /somecontentcheck if origin+path requires bound cookieGET /somecontent200 w/contentcheck if required cookies existGET /somecontent200 w/contentrequest deferred while we get cookies...GET /securesession/refresh header: Sec-Session-Id: [session ID]401Header: Sec-Session-Challenge: session_identifier=..., challenge=...create JWT w/challengerequest sign JWTreturn JWT signatureGET /securesession/refresh header: Sec-Session-Response: [JWT]validate proof of possesion200 w/cookie and session IDbody includes scope of cookies (origin + path)header: Set-Cookie: auth_cookiebody: {"session_identifier":...}secure session established, resumeoriginal requestGET /somecontent200 w/some contentalt[bound cookie not required][bound cookie required]alt[required cookie present and not expired][required cookie missing or expired] \ No newline at end of file diff --git a/spec.bs b/spec.bs new file mode 100644 index 0000000..2deaad3 --- /dev/null +++ b/spec.bs @@ -0,0 +1,714 @@ + + + + +
+spec: RFC8941;urlPrefix:https://datatracker.ietf.org/doc/html/rfc8941#;type:dfn
+  text: sf-dictionary; url: dictionary
+  text: sf-inner-list; url: inner-list
+  text: sf-item; url: item
+  text: sf-list; url: list
+  text: sf-string; url: string
+  text: sf-token; url: token
+  url:text-parse;text:parsing structured fields
+
+ +# Introduction # {#intro} + +This section is not normative.
+Note this is a very early drafting for writing collaboration only + +The web is built on a stateless protocol, to provide required functionality Web +applications store data locally on a user's computer in order to provide +functionality the are very common today. Primarily this is used for logged in +user sessions that can last for a long time. + +In general user agents do not have a secure way of storing files supporting +these activities across commonly used operating systems, and actions may have +serious consequences, for example transferring money from a bank account. + +This document defines a new API, Device Bound Sessions Credentials (DBSC), that +enables the server to verify that a session cannot be exported from a device by +using commonly available TPMs, or similar APIs, that are designed for this +purpose. + +The goal is to provide users with a safe and secure experience, while offering +the use cases users are already used to. At the same time we want to ensure that +the users privacy is respected with no new privacy identifiers being leaked by +this protocol. + +## Examples ## {#examples} +Device Bound Session Credentials are designed to make users more secure in +different situations. Some of the use cases of DBSC are: + +### Signed in session ### {#example-signin} +
+ A user logs in to his social account, to protect the user's private data the + site protects his logged in session wwith a DBSC session. If the user tries to + log with the same cookie file on a different device, the site can detect and + refuse this as an unathorized user. +
+ +### Device integrity ### {#example-device-integrity} +
+ A commercial site has different ways of detecting unahtorized log-in attempts. + A DBSC session on device could be used to see which users has logged on to + this device before. +
+ +### Device reputation ### {#example-device-reputation} +
+ A payment company hosted at site `payment.example` could create a session bound + to when users visit commercial site `shopping.example`. It could track the + reliability of the device over time to decide how likely a transaction is + legitimate. +
+ +# Security Considerations # {#privacy} +The goal of DBSC is to reduce session theft by offering an alternative to +long-lived cookie bearer tokens, that allows session authentication that is +bound to the user's device. This makes the internet safer for users in that it +is less likely their identity is abused, as malware is forced to act locally and +thus becomes easier to detect and mitigate. At the same time the goal is to +disrupt the cookie theft ecosystem and force it to adapt to new protections long +term. + +As long as the session is valid a host can know with cryptographic certainty +that it is on the same device as the session was originally bound to if the +session was registered to a secure device. + +## Non-goals ## {#non-goals} +DBSC will not prevent temporary access to the browser session while the attacker +is resident on the user's device. The private key should be stored as safe as +modern operating systems allow, preventing exfiltration of the session private +key, but the signing capability will still be available for any program running +as the user on the user's device. + +DBSC will also not prevent an attack if the attacker is replacing or injecting +into the user agent at the time of session registration as the attacker can bind +the session either to keys that are not TPM bound, or to a TPM that the attacker +controls permanently. + +DBSC is not designed to give hosts any sort of guarantee about the device a +session is registered to, or the state of this device. + +# Privacy Considerations # {#privacy-considerations} +The goal of the DBSC protocol is to introduce no additional surface for user +tracking: implementing this API (for a browser) or enabling it (for a website) +should not entail any significant user privacy tradeoffs. + +Some of the consideration taken to ensure this: + +- Lifetime of a session/key material: This should provide no additional client + data storage (i.e., a pseudo-cookie). As such, we require that browsers MUST + clear sessions and keys when clearing other site data (like cookies). +- Implementing this API should not meaningfully increase the entropy of + heuristic device fingerprinting signals. Unless allowed by user policy, DBSC + should not leak any stable TPM-based device identifier. +- As this API MAY allow background "pings" for performance, this must not enable + long-term tracking of a user when they have navigated away from the connected + site. +- Each session has a separate new key created, and it should not be possible to + detect that different sessions are from the same device unless the user allows + this by policy. + +## Cookies considerations ## {#privacy-cookies} +Cross-site/cross-origin data leakage: It should be impossible for a site to use +this API to circumvent the same origin policy, 3P cookie policies, etc. + +Due to the complexity of current and changing cookie behavior and the +interaction between DBSC and cookies the current solution is that each user +agent should use the same policy for DBSC as it uses for cookies. If the DBSC +cookie credential would not apply to a network request, based on user settings, +applied policies or user agent implementation details, neither would any of the +DBSC heuristics. This ensures no new privacy behavior due to implementing DBSC. + +## Timing side channel leak ## {#privacy-side-channel-leak} +If third party cookies are enabled it is possible for an attacker to leak +whether or not a user is authenticated by measuring how long the request takes +as the refresh is quite slow, partially due to the latency of TPM operations. + +This only applies if the site to be leaked about has enabled third party +cookies, if an attacker does have third-party cookie access there are often +attackes and leaks. + +This is not a very reliable leak as the user needs to have a session on the site +that is currently out of date and would need to be refreshed. The leak cannot be +trivially repeated as the first request will renew the session that would likely +not expire again for some time. + +It is important websites think about this privacy leak before adopting DBSC, +even more so if the plan is to use sessions with third party cookies. + +# Alternatives considered # {#alternatives} +## WebAuthn and silent mediation ## {#alternatives-webauthn} + +# Framework # {#framework} +This document uses ABNF grammar to specify syntax, as defined in [[!RFC5234]] +and updated in [[!RFC7405]], along with the `#rule` extension defined in +Section 7 of +[[!RFC9112]], and the `quoted-string` rule defined in +Section 3.2.6 +of the same document. + +This document depends on the Infra Standard for a number of foundational +concepts used in its algorithms and prose [[!INFRA]]. + +## Sessions by registrable domain ## {#framework-sessions-origin} +A registrable domain sessions is a [=ordered map=] from +[=host/registrable domain=] to [=session by id=]. + +## Sessions by id ## {#framework-sessions-id} +A session by id is an [=ordered map=] from +[=device bound session/session identifier=] to [=device bound session=]s for a +given [=host/registrable domain=]. + +## Device bound session ## {#framework-session} +A device bound session is a [=struct=] with the following +[=struct/items=]: +
+ : session identifier + :: an [=string=] that is a unique identifier of a session on an + [=host/registrable domain=] + : refresh url + :: an [=string=] that is representing the [=url=] to be used to refresh the + session + : defer requests + :: an OPTIONAL [=boolean=] defining if the browser should defer other + requests while refreshing a session + : cached challenge + :: an [=string=] that is to be used as the next challenge for this session + : [=session scope=] + :: a [=struct=] defining which [=url=]'s' are in scope for this session + : [=session credential=] + :: a [=list=] of [=session credential=] used by the session +
+ +## Session scope ## {#framework-scope} +The session scope is a [=struct=] with the following +[=struct/items=]: +
+ : include site + :: a [=boolean=] that indicates if all subdomains of are included by + default. + : [=scope specification=] + :: a [=list=] of [=scope specification=] used by the session +
+ +## Scope specification ## {#framework-scope-specification} +The scope specification is a [=struct=] with the following +[=struct/items=]: +
+ : type + :: a [=string=] to be either "include" or "exclude", defining if the item + defined in this struct should be added or removed from the scope + : path + :: a [=string=] that defines the path part of this scope item +
+ +## Session credential ## {#framework-session-credential} +The session credential is a [=struct=] with the following +[=struct/items=]: +
+ : name + :: a [=string=] that defines the name of the credential cookie + : attributes + :: a [=string=] that defines the other attributes of the credential cookie +
+ +# Algorithms # {#algorithms} +## Identify session ## {#algo-identify-session} +
+ This algorithm describes how to + identify a session out of all the + sessions that exist on a user agent. The + [=device bound session/session identifier=] is unique within a + [=host/registrable domain=]. + + Given a [=url=] and [=device bound session/session identifier=] + (|session identifier|), this algorithm returns a [=device bound session=] or + null if no such session exists. + + 1. Let |site| be the [=host/registrable domain=] of the [=url=] + 1. Let |domain sessions| be [=registrable domain sessions=][|site|] as a + [=/session by id=] + 1. Return |domain sessions|[|session identifier|] +
+ +## Process challenge ## {#algo-process-challenge} +
+ This algorithm describes how to + process a challenge received in an HTTP + header. + + Given a [=response=] (|response|), a [=registrable domain sessions=], this + algorithm updates the [=device bound session/cached challenge=] for a + [=device bound session=], or immediatly resends the [=DBSC proof=] signed with + the new challenge if the [=response/status=] is 401. + + 1. Let |header name| be "Sec-Session-Challenge". + 1. Let |challenge list| be the result of executing get a structured + field value given |header name| and "list" from |response|’s + [=response/header list=]. + 1. [=list/For each=] |challenge entry| of |challenge list|: + 1. Parse |challenge entry| according to parsing structured fields. + 1. If the type of |challenge entry| is not an sf-string, + [=iteration/continue=]. + 1. Let |challenge| be the parsed item. + 1. Let |session id| be null. + 1. If params["id"] exists and is a sf-string, Set |session id| to + params["id"]. + 1. If [=response/status=] is 401, resend this request as is with updated + |challenge| in [=DBSC proof=] and [=iteration/continue=]. + 1. If |session id| is null, [=iteration/continue=]. + 1. Identify session as described in [=identify a session=] given + |response| and |session id| and store as |session object|. + 1. If |session object| is null, [=iteration/continue=]. + 1. Store |challenge| in |session object| to be used next time a + [=DBSC proof=] is to be sent from this [=device bound session=]. +
+ +## Session refresh ## {#algo-session-refresh} +To Refreshing an existing session + +## Send request ## {#algo-session-request} + +## Create session ## {#algo-create-session} +To Create a new session, start with +parsing the registration structured header defined in +[:Sec-Session-Registration:]: +
+ 1. Let |header name| be "Sec-Session-Registration". + 1. Let |registration list| be the result of executing get a structured + field value given |header name| and "list" from |response|’s + [=response/header list=]. + 1. [=list/For each=] |registration entry|, |params| → |registration list|: + 1. Parse |registration entry| according to parsing structured fields. + 1. If |registration entry| is not an sf-inner-list, + [=iteration/continue=]. + 1. Let |algorithm list| be an empty [=list=]. + 1. [=list/For each=] |algorithm| → |registration entry| + 1. If |algorithm| is not a sf-token, [=iteration/continue=]. + 1. If |algorithm| represents a crypto algorithm supported in + [:Sec-Session-Registration:], and is supported on this client, add + |algorithm| to |algorithm list| + 1. If |algorithm list| is empty, [=iteration/continue=]. + 1. If |params|["path"] does not exist, or is not of type sf-string, + [=iteration/continue=]. + 1. Let |path| be |params|["path"]. + 1. Let |challenge| be null, and Let |authorization| be null. + 1. If |params|["challenge"] exists and is of type sf-string + Set |challenge| to |params|["challenge"]. + 1. If |params|["authorization"] exists and is a string Set |authorization| + to |params|["authorization"]. + 1. Call [[#algo-session-request]] with |algorithm list|, |path|, + |challenge| and |authorization| parameters. +
+ +## Closing session ## {#algo-close-session} +To close a session + +## Fetch Integration ## {#algo-fetch-integration} +To fetch Integration + +# DBSC Formats # {#format} +## \``Sec-Session-Registration`\` HTTP header field ## {#header-sec-session-registration} +The \` +Sec-Session-Registration\` header field can be used in a +[=response=] by the server to start a new [=/device bound session=] on the +client. + +[:Sec-Session-Registration:] is a List Structured Header [[RFC8941]]. Its ABNF +is: + +
Sec-Session-Registration = sf-list
+ +Each item in the list must be an inner list, and each item in the inner list +MUST be a sf-token representing a supported algorithm (ES256, RS256). +Only these two values are currently supported. + +The following parameters are defined: +- A parameter whose key is "path", and whose value is a String (Section 3.3.3 of + [[RFC8941]]), conveying the path to the registration endpoint. This may be + relative to the current [=url=], or a full [=url=]. Entries without this + parameter will be ignored in [=Create a new session=]. +- A parameter whose key is "challenge", and whose value is a String (Section + 3.3.3 of [[RFC8941]]), conveying the challenge to be used in the session + registration. +- A parameter whose key is "authorization", and whose value is a String (Section + 3.3.3 of [[RFC8941]]), this parameter will be copied into the registration + JWT. + +
+ Some examples of [:Sec-Session-Registration:] from + https://example.com/login.html: + + ```html + HTTP/1.1 200 OK + Sec-Session-Registration: (ES256);path="reg";challenge="cv";authorization="ac" + ``` + ```html + HTTP/1.1 200 OK + Sec-Session-Registration: (ES256 RS256);path="reg";challenge="cv" + ``` + ```html + HTTP/1.1 200 OK + Sec-Session-Registration: (ES256);path="reg1";challenge="cv1";authorization="a" + Sec-Session-Registration: (RS256);path="reg2";challenge="cv2";authorization="b" + ``` + ```html + HTTP/1.1 200 OK + Sec-Session-Registration: (ES256);path="reg1";challenge="cv1";authorization="a", (RS256);path="reg2";challenge="cv2";authorization="b" + ``` +
+ +## \``Sec-Session-Challenge`\` HTTP Header Field ## {#header-sec-session-challenge} +The \` +Sec-Session-Challenge\` header field can be used in a +[=response=] by the server to send a challenge to the client that it expects to +be used in future Sec-Session-Response headers inside the [=DBSC proof=], or to +request a newly signed [=DBSC proof=] right away if the + +[:Sec-Session-Challenge:] is a structured header. Its value must be a string. +Its ABNF is:
SecSessionChallenge = sf-string
+The semantics of the item are defined in +[[#challenge-structured-header-serialization]]. + +The processing steps are defined in [[#algo-process-challenge]]. + +### Sec-Session-Challenge structured header serialization ### {#challenge-structured-header-serialization} +The [:Sec-Session-Challenge:] is represented as a Structured Field.[[!RFC8941]] + +In this representation, a challenge is represented by a string. + +Challenges MAY have a Parameter named `"id"`, whose value MUST be a String +representing a [=device bound session/session identifier=]. Any other +parameters SHOULD be ignored. + +Note: The server might need to use this header to request the [=DBSC proof=] to +be signed with a new challenge before a session id has been assigned. In this +case the session ID is optional. + +
+ Some examples of [:Sec-Session-Challenge:] from + https://example.com/login.html: + + ```html + HTTP/1.1 401 OK + Sec-Session-Challenge: "new challenge" + ``` + ```html + HTTP/1.1 401 OK + Sec-Session-Challenge: "new challenge";id="my session" + ``` + ```html + HTTP/1.1 200 OK + Sec-Session-Challenge: "new challenge";id="my session" + ``` + ```html + HTTP/1.1 200 OK + Sec-Session-Challenge: "new challenge";id="my session 1" + Sec-Session-Challenge: "another challenge";id="my session 2" + ``` + ```html + HTTP/1.1 200 OK + Sec-Session-Challenge: "c1";id="session 1", "c2";id="session 2" + ``` +
+ +## `Sec-Session-Response` HTTP Header Field ## {#header-sec-session-response} +The \` +Sec-Session-Response\` header field can be used in the +[=request=] by the user agent to send a [=DBSC proof=] to the server to prove +that the client is still in possesion of the private key of the session key. + +\`Sec-Session-Response\` is a structured +header. Its value must be a string. It's ABNF is: +
SecSessionChallenge = sf-string
+This string MUST only contain the [=DBSC proof=] JWT. Any parameters SHOULD be +ignored. + +
+ ```html + POST example.com/refresh + Sec-Session-Response: "eyJhbGciOiJFUzI1NiIsInR5cCI6ImRic2Mrand0In0.eyJhdWQiOiJodHRwczovL2V4YW1wbGUuY29tL3JlZyIsImp0aSI6ImN2IiwiaWF0IjoiMTcyNTU3OTA1NSIsImp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IjZfR0Iydm9RMHFyb01oNk9sREZDRlNfU0pyaVFpMVBUdnZCT2hHWjNiSEkiLCJ5IjoiSWVnT0pVTHlFN1N4SF9DZDFLQ0VSN2xXQnZHRkhRLWgweHlqelVqRUlXRSJ9LCJhdXRob3JpemF0aW9uIjoiYWMifQ.6Fb_vVBDmfNghQiBmIGe8o7tBfYPbPCywhQruP0vIhxgmcJmuNTaMHeVn_M8ZnOm1_bzIitbZqCWEn-1Qzmtyw" + ``` +
+ +## `Sec-Session-Id` HTTP Header Field ## {#header-sec-session-id} +The \` +Sec-Session-Id\` header field can be used in the +[=request=] by the user agent to request the current session is refreshed, +with the current session identifier as a string argument. + +\`Sec-Session-Id\` is a structured header. +Its value must be a string. It's ABNF is: +
SecSessionChallenge = sf-string
+This string MUST only contain the session identifier. Any paramters SHOULD be +ignored. + +
+ ```html + POST example.com/refresh + Sec-Session-Id: "session-id" + ``` +
+ +## DBSC Session Instruction Format ## {#format-session-instructions} +The server sends session instructions during session registration and +optionally during session refresh. If the response contains session instructions +it MUST be in JSON format. + +At the root of the JSON object the following keys can exist: +
+ : session identifier + :: a [=string=] representing a [=device bound session/session identifier=]. + If this [=session instructions=] is sent during a refresh request this MUST be + the [=device bound session/session identifier=] for the current session. If + not these instructions SHOULD be ignored. + If this [=session instructions=] is sent during a registration it MUST either + be a unique iditifier for this [=host/registrable domain=], or it will + overwrite the current [=device bound session=] with this identifier for the + current [=host/registrable domain=]. + This key MUST be present. + + : refresh_url + :: a [=string=] representing the [=url=] used for future refresh requests. + This can be a full url, or relative to the current [=request=]. + This key is OPTIONAL, if not present the registration url will be used for + future refresh requests. + + : continue + :: a [=boolean=] representing if the current session should continue, or be + closed on the client. This key is OPTIONAL, and if not present the default + value will be true. + + : defer_requests + :: a [=boolean=] describing the wanted session behavior during a session + refresh. If this value is true all requests related to this session will be + deferred while the session is refreshed. If instead the value is false every + request will instead be sent as normal, but with a [:Sec-Session-Response:] + header containing the [=DBSC proof=]. + This key is OPTIONAL, and if not present a value of true is default. +
+ +
+ ```json + { + "session_identifier": "session_id", + "refresh_url": "/RefreshEndpoint", + + "scope": { + // Origin-scoped by default (i.e. https://example.com) + // Specifies to include https://*.example.com except excluded subdomains. + // This can only be true if the origin's host is the root eTLD+1. + "origin": "example.com", + "include_site": true, + "continue": false, + "defer_requests": true, // optional and true by default + + "scope_specification" : [ + { "type": "include", "domain": "trusted.example.com", "path": "/only_trusted_path" }, + { "type": "exclude", "domain": "untrusted.example.com", "path": "/" }, + { "type": "exclude", "domain": "*.example.com", "path": "/static" } + ] + }, + + "credentials": [{ + "type": "cookie", + // This specifies the exact cookie that this config applies to. Attributes + // match the cookie attributes in RFC 6265bis and are parsed similarly to + // a normal Set-Cookie line, using the same default values. + // These SHOULD be equivalent to the Set-Cookie line accompanying this + // response. + "name": "auth_cookie", + "attributes": "Domain=example.com; Path=/; Secure; SameSite=None" + // Attributes Max-Age, Expires and HttpOnly are ignored + }] + } + ``` +
+ +## DBSC Proof JWT Syntax ## {#format-jwt} +A DBSC proof proof is a JWT that is signed (using JSON Web Signature +(JWS)), with a private key chosen by the client. The header of a [=DBSC proof=] +MUST contain at least the following parameters: +
+ : typ + :: a [=string=] MUST be "dbsc+jwt" + : alg + :: a [=string=] defining the algorithm used to sign this JWT. It MUST be + either "RS256" or "ES256" from [IANA.JOSE.ALGS]. +
+ +The payload of [=DBSC proof=] MUST contain at least the following claims: +
+ : aud + :: a [=string=], MUST be the [=url=] this JWT was originally sent to. + Example: "https://example.com/refresh.html" + : jti + :: a [=string=], a copy of the challenge value sent in the registration + header. + : iat + :: a [=string=], this claim identifies the time at which the JWT was + issued. This claim can be used to determine the age of the JWT. Its + value MUST be a number containing a NumericDate value. + : jwk + :: a [=string=] defining a JWK as specified in [rfc7517]. +
+ +In addition the following claims MUST be present if present in +[:Sec-Session-Registration:]: +
+ : authorization + :: a [=string=], direct copy of the string from + [:Sec-Session-Registration:], if set there. Note that this string is + OPTIONAL to include in the header, but if it is present it is + MANDATORY for clients to add the claim in the [=DBSC proof=]. +
+ +
+ An example [=DBSC proof=] sent to https://example.com/reg: + + ```json + // Header + { + "alg": "ES256", + "typ": "dbsc+jwt" + } + // Payload + { + "aud": "https://example.com/reg", + "jti": "cv", + "iat": "1725579055", + "jwk": { + "kty": "EC", + "crv": "P-256", + "x": "6_GB2voQ0qroMh6OlDFCFS_SJriQi1PTvvBOhGZ3bHI", + "y": "IegOJULyE7SxH_Cd1KCER7lWBvGFHQ-h0xyjzUjEIWE" + }, + "authorization": "ac" + } + ``` + + Based on this response header from the server: + ```html + HTTP/1.1 200 OK + Sec-Session-Registration: (ES256);path="reg";challenge="cv";authorization="ac" + ``` + recieved on a response from ```http://example.com/page.html``` +
+ +# Changes to other specifications # {#changes-to-other-specifications} +## Changes to the Fetch specification ## {#changes-to-fetch} +--> Check if session should be refreshed before sending request + - Alternatively add proof with Sec-Session-Response +## Changes to the HTML specification ## {#changes-to-html} +--> Clear Site Data: Clear the session if this is received + +# IANA Considerations # {#iana-considerations} + +The permanent message header field registry should be updated with the following +registrations: [[!RFC3864]] + +## Sec-Session-Challenge ## {#iana-ses-session-challenge} +
+
Header field name
+
Sec-Session-Challenge
+ +
Applicable protocol
+
http
+ +
Status
+
draft
+ +
Author/Change controller
+
W3C
+ +
Specification document
+
This specification (See [[#header-sec-session-challenge]])
+
+ +## Sec-Session-Id ## {#iana-ses-session-id} +
+
Header field name
+
Sec-Session-Id
+ +
Applicable protocol
+
http
+ +
Status
+
draft
+ +
Author/Change controller
+
W3C
+ +
Specification document
+
This specification (See [[#header-sec-session-id]])
+
+ +## Sec-Session-Registration ## {#iana-sec-session-registration} +
+
Header field name
+
Sec-Session-Registration
+ +
Applicable protocol
+
http
+ +
Status
+
draft
+ +
Author/Change controller
+
W3C
+ +
Specification document
+
This specification (See [[#header-sec-session-registration]])
+
+ +## Sec-Session-Response ## {#iana-ses-session-response} +
+
Header field name
+
Sec-Session-Response
+ +
Applicable protocol
+
http
+ +
Status
+
draft
+ +
Author/Change controller
+
W3C
+ +
Specification document
+
This specification (See [[#header-sec-session-response]])
+
+ +# Changelog # {#changelog} +This is an early draft of the spec. + +# Acknowledgements # {#acknowledgements}