<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE rfc [
  <!ENTITY nbsp    "&#160;">
  <!ENTITY zwsp   "&#8203;">
  <!ENTITY nbhy   "&#8209;">
  <!ENTITY wj     "&#8288;">
]>
<?xml-stylesheet type="text/xsl" href="rfc2629.xslt"?>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude"
     ipr="trust200902"
     docName="draft-rampalli-suradar-00"
     category="std"
     consensus="true"
     submissionType="IETF"
     version="3"
     xml:lang="en">

  <front>
    <title abbrev="SURADAR">SURADAR: Context-Bound Per-Request Authentication for Machine-to-Machine APIs</title>

    <seriesInfo name="Internet-Draft" value="draft-rampalli-suradar-00"/>

    <author fullname="Karthik Rampalli" initials="K." surname="Rampalli">
      <organization>Glyphzero Labs Inc.</organization>
      <address>
        <email>karthik@phantomcorgi.com</email>
      </address>
    </author>

    <date year="2026" month="April" day="25"/>

    <area>Security</area>
    <workgroup>HTTP</workgroup>

    <keyword>authentication</keyword>
    <keyword>HMAC</keyword>
    <keyword>machine-to-machine</keyword>
    <keyword>context-bound</keyword>
    <keyword>agentic AI</keyword>

    <abstract>
      <t>
        This document defines SURADAR (Subsurface Undertow RADAR), an HTTP
        authentication scheme in which each request is authenticated by a
        one-time HMAC tag derived from a shared seed, the current time band,
        and the full request context (method, path, organisation, scope, and
        body).  Unlike bearer-token schemes, a captured SURADAR token is
        cryptographically bound to exactly one request and cannot be reused,
        replayed, or re-scoped.  The protocol requires zero per-request
        handshakes, produces 48-byte tokens, and relies solely on
        HMAC-SHA-256 and SHA-256 -- no asymmetric cryptography.
      </t>
    </abstract>
  </front>

  <middle>

    <!-- ============================================================ -->
    <section anchor="introduction" numbered="true" toc="include">
      <name>Introduction</name>

      <t>
        Existing HTTP authentication mechanisms -- Bearer tokens
        <xref target="RFC6750"/>, JSON Web Tokens <xref target="RFC7519"/>,
        OAuth 2.0 <xref target="RFC6749"/>, and HTTP Message Signatures
        <xref target="RFC9421"/> -- authenticate the caller but not the
        request.  A valid token for one endpoint is equally valid for any
        other endpoint within its scope, for any request body, until the
        token expires.  This creates a class of attacks where a stolen or
        intercepted token can be reused for unintended purposes.
      </t>

      <t>
        SURADAR addresses this by deriving a unique one-time key for each
        request from:
      </t>

      <ul>
        <li>A shared secret seed (established at enrollment)</li>
        <li>The current time band (30-second window)</li>
        <li>A context fingerprint: SHA-256(method || path || orgID || scope)</li>
        <li>A random client nonce</li>
        <li>The request body</li>
      </ul>

      <t>
        The resulting token is valid for exactly one HTTP request, at one
        endpoint, with one scope, within one time window, with exactly the
        body that was sent.
      </t>

      <section anchor="goals" numbered="true" toc="include">
        <name>Goals</name>
        <dl>
          <dt>Zero per-request handshakes</dt>
          <dd>
            Token generation is entirely local; the client never contacts
            the server before sending the authenticated request.
          </dd>
          <dt>Context binding</dt>
          <dd>
            The HTTP method, path, organisation identifier, and scope are
            cryptographically embedded in the key derivation, not merely
            carried as metadata.
          </dd>
          <dt>Body integrity</dt>
          <dd>
            The full request body is covered by the HMAC, preventing
            body-swap attacks.
          </dd>
          <dt>Minimal blast radius</dt>
          <dd>
            A captured token is valid for exactly one request; it cannot be
            replayed, re-scoped, or used against a different endpoint.
          </dd>
          <dt>Symmetric simplicity</dt>
          <dd>
            The protocol uses only HMAC-SHA-256 and SHA-256.  No asymmetric
            cryptography is required.
          </dd>
        </dl>
      </section>

      <section anchor="non-goals" numbered="true" toc="include">
        <name>Non-Goals</name>
        <dl>
          <dt>Key distribution</dt>
          <dd>
            Seed enrollment is specified at the minimum level required
            for interoperability (<xref target="enrollment"/>).  Full key
            lifecycle management is out of scope.
          </dd>
          <dt>Authorization</dt>
          <dd>
            SURADAR authenticates requests; authorization policy is left to
            the application.
          </dd>
          <dt>Transport security</dt>
          <dd>
            TLS <bcp14>MUST</bcp14> still be used.  SURADAR provides
            authentication and integrity within the application layer, not
            confidentiality.
          </dd>
        </dl>
      </section>
    </section>

    <!-- ============================================================ -->
    <section anchor="conventions" numbered="true" toc="include">
      <name>Conventions and Definitions</name>

      <t>
        The key words "<bcp14>MUST</bcp14>", "<bcp14>MUST NOT</bcp14>",
        "<bcp14>REQUIRED</bcp14>", "<bcp14>SHALL</bcp14>",
        "<bcp14>SHALL NOT</bcp14>", "<bcp14>SHOULD</bcp14>",
        "<bcp14>SHOULD NOT</bcp14>", "<bcp14>RECOMMENDED</bcp14>",
        "<bcp14>NOT RECOMMENDED</bcp14>", "<bcp14>MAY</bcp14>", and
        "<bcp14>OPTIONAL</bcp14>" in this document are to be interpreted as
        described in BCP 14 <xref target="RFC2119"/>
        <xref target="RFC8174"/> when, and only when, they appear in all
        capitals, as shown here.
      </t>

      <section anchor="notation" numbered="true" toc="include">
        <name>Notation</name>

        <table anchor="notation-table">
          <name>SURADAR Notation</name>
          <thead>
            <tr>
              <th>Symbol</th>
              <th>Definition</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>S</td>
              <td>Shared seed (32 bytes)</td>
            </tr>
            <tr>
              <td>RSK</td>
              <td>Root Server Key (32 bytes)</td>
            </tr>
            <tr>
              <td>T</td>
              <td>Time band index: floor(unix_seconds / TBandSeconds)</td>
            </tr>
            <tr>
              <td>TBandSeconds</td>
              <td>Time band width (default: 30)</td>
            </tr>
            <tr>
              <td>TBandSkew</td>
              <td>Adjacent bands accepted (default: 1)</td>
            </tr>
            <tr>
              <td>ctx</td>
              <td>Context fingerprint: SHA-256(method || 0x00 || path || 0x00 || orgID || 0x00 || scope)</td>
            </tr>
            <tr>
              <td>K1</td>
              <td>Partial key: HMAC-SHA-256(S, T_bytes || ctx)</td>
            </tr>
            <tr>
              <td>R</td>
              <td>Client nonce: 16 random bytes</td>
            </tr>
            <tr>
              <td>K</td>
              <td>Full one-time key: HMAC-SHA-256(K1, R)</td>
            </tr>
            <tr>
              <td>sig</td>
              <td>Request authenticator: HMAC-SHA-256(K, body)</td>
            </tr>
            <tr>
              <td>token</td>
              <td>Wire encoding: base64url(R || sig) -- 64 chars, 48 bytes</td>
            </tr>
            <tr>
              <td>||</td>
              <td>Byte concatenation</td>
            </tr>
            <tr>
              <td>0x00</td>
              <td>Null byte separator</td>
            </tr>
          </tbody>
        </table>
      </section>

      <section anchor="algorithms" numbered="true" toc="include">
        <name>Algorithms</name>
        <t>
          SURADAR uses HMAC-SHA-256 <xref target="RFC2104"/>
          <xref target="RFC6234"/> for key derivation and request
          authentication, and SHA-256 <xref target="FIPS180-4"/> for
          context fingerprinting.
        </t>
      </section>
    </section>

    <!-- ============================================================ -->
    <section anchor="overview" numbered="true" toc="include">
      <name>Protocol Overview</name>

      <section anchor="overview-enrollment" numbered="true" toc="include">
        <name>Enrollment Phase (Once Per Client)</name>

        <t>
          The enrollment phase establishes a shared seed S between client
          and server.  It occurs once per client over an authenticated
          channel.
        </t>

        <artwork type="ascii-art"><![CDATA[
Client                                    Server
  |--- [authenticated channel] ------------>|
  |    clientID, orgID, pubKey              |
  |    enrollNonce = rand(16)               |
  |    S = HMAC-SHA-256(RSK, clientID || enrollNonce)
  |    encrypted_S = XOR(S, SHA-256(pubKey))|
  |<-- enrollNonce, encrypted_S ------------|
  |    S = XOR(encrypted_S, SHA-256(privKey))
  |    Store S in secure keychain           |
  Server stores: (clientID, orgID, enrollNonce)
  Server NEVER stores S
]]></artwork>
      </section>

      <section anchor="overview-perrequest" numbered="true" toc="include">
        <name>Per-Request Phase (Zero Roundtrips)</name>

        <t>
          Each HTTP request is independently authenticated with no
          additional roundtrips.
        </t>

        <artwork type="ascii-art"><![CDATA[
Client                                    Server
  | T = floor(now / 30)                     |
  | ctx = SHA-256(method||0x00||path||0x00||orgID||0x00||scope)
  | K1 = HMAC-SHA-256(S, T_bytes || ctx)    |
  | R = rand(16)                            |
  | K = HMAC-SHA-256(K1, R)                 |
  | sig = HMAC-SHA-256(K, body)             |
  | zero(K1, K)                             |
  | token = base64url(R || sig)             |
  |--- HTTP request with headers: --------->|
  |    X-SURADAR-Auth: <token>              |
  |    X-SURADAR-Client: <clientID>         |
  |    X-SURADAR-TBand: <T>                 |
  |    S' = HMAC-SHA-256(RSK, clientID || enrollNonce)
  |    ctx' = SHA-256(method||0x00||path||0x00||orgID||0x00||scope)
  |    K1' = HMAC-SHA-256(S', T_bytes || ctx')
  |    K' = HMAC-SHA-256(K1', R_from_token) |
  |    verify: sig == HMAC-SHA-256(K', body)|
  |    replay_check(T, ctx, R)              |
  |    zero(K1', K')                        |
  |<-- 200 OK or 401 Unauthorized ---------|
]]></artwork>
      </section>
    </section>

    <!-- ============================================================ -->
    <section anchor="enrollment" numbered="true" toc="include">
      <name>Enrollment</name>

      <section anchor="enrollment-prereqs" numbered="true" toc="include">
        <name>Prerequisites</name>
        <t>
          The client <bcp14>MUST</bcp14> authenticate via an existing
          mechanism (OAuth 2.0, mutual TLS, or a one-time enrollment token)
          before requesting seed enrollment.  The enrollment channel
          <bcp14>MUST</bcp14> be protected by TLS.
        </t>
      </section>

      <section anchor="seed-derivation" numbered="true" toc="include">
        <name>Seed Derivation</name>
        <t>
          The server generates a 16-byte enrollment nonce using a
          cryptographically secure pseudorandom number generator (CSPRNG)
          <xref target="RFC4086"/>:
        </t>
        <artwork type="ascii-art"><![CDATA[
enrollNonce = CSPRNG(16)
S = HMAC-SHA-256(RSK, clientID || enrollNonce)
]]></artwork>
        <t>
          The RSK <bcp14>MUST</bcp14> be stored in a hardware security
          module (HSM) or equivalent secure storage.  The server
          <bcp14>MUST NOT</bcp14> store S.  The server stores the tuple
          (clientID, orgID, enrollNonce).
        </t>
      </section>

      <section anchor="seed-delivery" numbered="true" toc="include">
        <name>Seed Delivery</name>
        <t>
          The server delivers the seed to the client encrypted under the
          client's public key:
        </t>
        <artwork type="ascii-art"><![CDATA[
encrypted_S = XOR(S, SHA-256(clientPubKey))
]]></artwork>
        <t>
          The client recovers S:
        </t>
        <artwork type="ascii-art"><![CDATA[
S = XOR(encrypted_S, SHA-256(privKey))
]]></artwork>
        <t>
          Alternative delivery mechanisms (e.g., direct TLS-protected
          transport, envelope encryption) are permitted provided
          confidentiality and integrity of S are maintained.
        </t>
      </section>

      <section anchor="seed-storage" numbered="true" toc="include">
        <name>Seed Storage (Client)</name>
        <t>
          The client <bcp14>MUST</bcp14> store S in one of the following:
        </t>
        <ul>
          <li>Operating system keychain (e.g., macOS Keychain, Windows Credential Manager)</li>
          <li>Hardware security element or TPM</li>
          <li>Secrets manager (e.g., HashiCorp Vault, AWS Secrets Manager)</li>
        </ul>
        <t>
          The client <bcp14>MUST NOT</bcp14> store S in plaintext on disk
          or in environment variables.
        </t>
      </section>

      <section anchor="rsk-management" numbered="true" toc="include">
        <name>RSK Management</name>
        <t>
          The RSK <bcp14>MUST</bcp14> be stored in an HSM or secrets
          manager.  RSK rotation is supported: the server
          <bcp14>MUST</bcp14> accept tokens derived from the previous RSK
          for a grace period equal to NonceTTL (default: 90 seconds) after
          rotation.  During this period, both old and new RSK values are
          tried during verification.
        </t>
      </section>
    </section>

    <!-- ============================================================ -->
    <section anchor="token-generation" numbered="true" toc="include">
      <name>Token Generation (Client)</name>

      <section anchor="compute-tband" numbered="true" toc="include">
        <name>Compute Time Band</name>
        <t>
          The client computes the time band index:
        </t>
        <artwork type="ascii-art"><![CDATA[
T = floor(unix_seconds / TBandSeconds)
]]></artwork>
        <t>
          T is encoded as an 8-byte big-endian unsigned integer (T_bytes).
        </t>
      </section>

      <section anchor="compute-ctx" numbered="true" toc="include">
        <name>Compute Context Fingerprint</name>
        <t>
          The context fingerprint binds the token to the specific request:
        </t>
        <artwork type="ascii-art"><![CDATA[
ctx = SHA-256(method || 0x00 || path || 0x00 || orgID || 0x00 || scope)
]]></artwork>
        <t>
          Null byte (0x00) separators prevent field boundary ambiguity.
          For example, without separators, method="GETX" path="Y" and
          method="GET" path="XY" would produce the same hash input.
        </t>
      </section>

      <section anchor="derive-k1" numbered="true" toc="include">
        <name>Derive Partial Key</name>
        <artwork type="ascii-art"><![CDATA[
K1 = HMAC-SHA-256(S, T_bytes || ctx)
]]></artwork>
        <t>
          K1 <bcp14>MUST</bcp14> be zeroed from memory immediately after
          K is derived.
        </t>
      </section>

      <section anchor="generate-nonce" numbered="true" toc="include">
        <name>Generate Client Nonce</name>
        <t>
          The client generates a 16-byte random nonce:
        </t>
        <artwork type="ascii-art"><![CDATA[
R = CSPRNG(16)
]]></artwork>
      </section>

      <section anchor="derive-k" numbered="true" toc="include">
        <name>Derive Full Key</name>
        <artwork type="ascii-art"><![CDATA[
K = HMAC-SHA-256(K1, R)
]]></artwork>
        <t>
          K <bcp14>MUST</bcp14> be zeroed from memory immediately after
          the signature is computed.
        </t>
      </section>

      <section anchor="compute-sig" numbered="true" toc="include">
        <name>Compute Signature</name>
        <artwork type="ascii-art"><![CDATA[
sig = HMAC-SHA-256(K, body)
]]></artwork>
        <t>
          For requests with no body (e.g., GET), body is the empty byte
          string.  K <bcp14>MUST</bcp14> be zeroed after this step.
        </t>
      </section>

      <section anchor="encode-token" numbered="true" toc="include">
        <name>Encode Token</name>
        <artwork type="ascii-art"><![CDATA[
token = base64url_no_pad(R || sig)
]]></artwork>
        <t>
          R is 16 bytes and sig is 32 bytes, producing 48 bytes total,
          which encodes to exactly 64 base64url characters (no padding)
          per <xref target="RFC4648" section="5"/>.
        </t>
      </section>

      <section anchor="set-headers" numbered="true" toc="include">
        <name>Set HTTP Headers</name>
        <t>
          The client sets the following HTTP headers on the request:
        </t>

        <table anchor="request-headers-table">
          <name>SURADAR Request Headers</name>
          <thead>
            <tr>
              <th>Header</th>
              <th>Value</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>X-SURADAR-Auth</td>
              <td>token (64 base64url characters)</td>
            </tr>
            <tr>
              <td>X-SURADAR-Client</td>
              <td>clientID</td>
            </tr>
            <tr>
              <td>X-SURADAR-TBand</td>
              <td>T (decimal integer)</td>
            </tr>
          </tbody>
        </table>
      </section>
    </section>

    <!-- ============================================================ -->
    <section anchor="token-verification" numbered="true" toc="include">
      <name>Token Verification (Server)</name>

      <section anchor="parse-headers" numbered="true" toc="include">
        <name>Parse Headers</name>
        <t>
          The server extracts X-SURADAR-Auth, X-SURADAR-Client, and
          X-SURADAR-TBand from the request.  If any header is absent, the
          server <bcp14>MUST</bcp14> respond with HTTP 401 Unauthorized.
        </t>
      </section>

      <section anchor="validate-tband" numbered="true" toc="include">
        <name>Validate Time Band</name>
        <t>
          The server computes T_server = floor(now / TBandSeconds) and
          checks:
        </t>
        <artwork type="ascii-art"><![CDATA[
|T_client - T_server| <= TBandSkew
]]></artwork>
        <t>
          If the time band is outside the acceptable skew window, the
          server <bcp14>MUST</bcp14> reject the request with HTTP 401.
        </t>
      </section>

      <section anchor="rederive-seed" numbered="true" toc="include">
        <name>Re-derive Seed</name>
        <t>
          The server looks up enrollNonce for the given clientID, then
          re-derives the seed:
        </t>
        <artwork type="ascii-art"><![CDATA[
S' = HMAC-SHA-256(RSK, clientID || enrollNonce)
]]></artwork>
        <t>
          During RSK rotation, the server <bcp14>MUST</bcp14> attempt
          verification with both the current and previous RSK values.
        </t>
      </section>

      <section anchor="recompute-keychain" numbered="true" toc="include">
        <name>Recompute Key Chain</name>
        <t>
          The server recomputes the context fingerprint and key chain from
          the actual HTTP request (method, path, orgID looked up from the
          client record, scope from application context):
        </t>
        <artwork type="ascii-art"><![CDATA[
ctx' = SHA-256(method || 0x00 || path || 0x00 || orgID || 0x00 || scope)
K1'  = HMAC-SHA-256(S', T_bytes || ctx')
K'   = HMAC-SHA-256(K1', R_from_token)
]]></artwork>
        <t>
          The server <bcp14>MUST NOT</bcp14> use any client-supplied
          values for method, path, or orgID in this computation.
        </t>
      </section>

      <section anchor="verify-sig" numbered="true" toc="include">
        <name>Verify Signature</name>
        <artwork type="ascii-art"><![CDATA[
expected = HMAC-SHA-256(K', body)
valid    = constant_time_equal(sig_from_token, expected)
]]></artwork>
        <t>
          The comparison <bcp14>MUST</bcp14> use a constant-time equality
          function to prevent timing side-channel attacks.  K'
          <bcp14>MUST</bcp14> be zeroed after use.
        </t>
      </section>

      <section anchor="check-replay" numbered="true" toc="include">
        <name>Check Replay</name>
        <t>
          Replay checking <bcp14>MUST</bcp14> occur after HMAC
          verification succeeds.  This ordering prevents an attacker from
          poisoning the nonce store with garbage tuples.
        </t>
        <t>
          The server checks the tuple (T, ctx, R) against the nonce store.
          If the tuple is already present, the request is a replay and
          <bcp14>MUST</bcp14> be rejected with HTTP 401.
        </t>
        <t>
          The nonce store entry TTL <bcp14>MUST</bcp14> be at least
          (TBandSkew + 1) * TBandSeconds seconds.  With defaults, this is
          (1 + 1) * 30 = 60 seconds.  A default of 90 seconds is
          <bcp14>RECOMMENDED</bcp14> to account for clock drift.
        </t>
      </section>

      <section anchor="return-principal" numbered="true" toc="include">
        <name>Return Principal</name>
        <t>
          Upon successful verification, the server makes the following
          values available to downstream authorization logic:
        </t>
        <ul>
          <li>clientID</li>
          <li>orgID</li>
          <li>scope</li>
        </ul>
      </section>
    </section>

    <!-- ============================================================ -->
    <section anchor="replay-prevention" numbered="true" toc="include">
      <name>Replay Prevention</name>

      <section anchor="nonce-store-reqs" numbered="true" toc="include">
        <name>Nonce Store Requirements</name>
        <t>
          The nonce store provides a single operation:
        </t>
        <artwork type="ascii-art"><![CDATA[
Check(T, ctx, R) -> OK | REPLAY
]]></artwork>
        <t>
          The store <bcp14>MUST</bcp14> be concurrent-safe.  Entries
          <bcp14>MUST</bcp14> expire after NonceTTL seconds (default: 90).
        </t>
      </section>

      <section anchor="bloom-filter" numbered="true" toc="include">
        <name>Bloom Filter Implementation</name>
        <t>
          A Bloom filter implementation is <bcp14>RECOMMENDED</bcp14> for
          single-server deployments.  The following parameters provide
          approximately 0.01% false positive rate:
        </t>
        <ul>
          <li>m = 10,000,000 bits (~1.2 MB)</li>
          <li>k = 7 hash functions</li>
        </ul>
        <t>
          The implementation uses dual-rotating filters: the active filter
          receives new entries, while the previous filter is kept for the
          duration of NonceTTL before being cleared and rotated.
        </t>
        <t>
          Hash functions for the Bloom filter are derived from SHA-256 of
          the (T, ctx, R) tuple by splitting the 256-bit output into
          segments.
        </t>
      </section>

      <section anchor="distributed-replay" numbered="true" toc="include">
        <name>Distributed Implementation</name>
        <t>
          For distributed deployments, a Redis SETNX with TTL equal to
          NonceTTL is <bcp14>RECOMMENDED</bcp14>:
        </t>
        <artwork type="ascii-art"><![CDATA[
key = "suradar:nonce:" || hex(SHA-256(T || ctx || R))
result = SETNX(key, 1)
EXPIRE(key, NonceTTL)
]]></artwork>
        <t>
          If SETNX returns 0 (key already exists), the request is a
          replay.
        </t>
      </section>
    </section>

    <!-- ============================================================ -->
    <section anchor="header-registration" numbered="true" toc="include">
      <name>HTTP Header Registration</name>
      <t>
        This document registers the following HTTP header fields:
      </t>
      <dl>
        <dt>X-SURADAR-Auth</dt>
        <dd>
          Contains the SURADAR authentication token: 64 base64url
          characters encoding the 16-byte client nonce concatenated with
          the 32-byte HMAC-SHA-256 signature.
        </dd>
        <dt>X-SURADAR-Client</dt>
        <dd>
          Contains the client identifier used to look up the enrollment
          record on the server.
        </dd>
        <dt>X-SURADAR-TBand</dt>
        <dd>
          Contains the time band index as a decimal integer, allowing the
          server to verify the token was generated within an acceptable
          time window.
        </dd>
      </dl>
    </section>

    <!-- ============================================================ -->
    <section anchor="security" numbered="true" toc="include">
      <name>Security Considerations</name>

      <section anchor="threat-model" numbered="true" toc="include">
        <name>Threat Model</name>
        <t>
          The attacker is assumed to be able to observe network traffic
          (despite TLS, e.g., via compromised middlebox), capture tokens,
          and attempt to reuse them.  The attacker cannot obtain the shared
          seed S or the Root Server Key RSK.
        </t>
      </section>

      <section anchor="stolen-token" numbered="true" toc="include">
        <name>Stolen Token Analysis</name>
        <t>
          A captured token reveals R (not secret) and sig.  The attacker
          cannot derive K without K1, cannot derive K1 without S, and
          cannot derive S without RSK.  The blast radius of a stolen
          SURADAR token is exactly one request -- the request for which
          it was generated.
        </t>
      </section>

      <section anchor="scope-escalation" numbered="true" toc="include">
        <name>Scope Escalation Prevention</name>
        <t>
          The scope is embedded in ctx, which is embedded in the K1
          derivation.  Changing the scope changes ctx, which changes K1,
          which changes K, which changes sig.  An attacker cannot
          re-scope a captured token -- this is a cryptographic guarantee,
          not a policy check.
        </t>
      </section>

      <section anchor="body-integrity" numbered="true" toc="include">
        <name>Body Integrity</name>
        <t>
          The HMAC signature covers the full request body.  Any
          modification to the body invalidates the signature.
        </t>
      </section>

      <section anchor="nonce-poisoning" numbered="true" toc="include">
        <name>Nonce Store Poisoning Resistance</name>
        <t>
          The replay check occurs after HMAC verification.  An attacker
          who sends garbage tokens cannot poison the nonce store because
          those tokens will fail HMAC verification before the nonce is
          recorded.
        </t>
      </section>

      <section anchor="timing-attacks" numbered="true" toc="include">
        <name>Timing Attacks</name>
        <t>
          Implementations <bcp14>MUST</bcp14> use constant-time comparison
          for HMAC verification.  Implementations <bcp14>MUST</bcp14> zero
          all key material (K1, K) immediately after use to limit the
          window for memory-disclosure attacks.
        </t>
      </section>

      <section anchor="key-lifetime" numbered="true" toc="include">
        <name>Key Material Lifetime</name>

        <table anchor="key-lifetime-table">
          <name>Key Material Lifetime and Storage</name>
          <thead>
            <tr>
              <th>Material</th>
              <th>Lifetime</th>
              <th>Storage</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>RSK</td>
              <td>Long-lived, rotatable</td>
              <td>HSM / Vault</td>
            </tr>
            <tr>
              <td>S</td>
              <td>Client lifetime</td>
              <td>OS keychain</td>
            </tr>
            <tr>
              <td>K1</td>
              <td>~microseconds</td>
              <td>Memory only</td>
            </tr>
            <tr>
              <td>K</td>
              <td>~microseconds</td>
              <td>Memory only</td>
            </tr>
            <tr>
              <td>R</td>
              <td>Single request</td>
              <td>Transmitted then discarded</td>
            </tr>
          </tbody>
        </table>
      </section>

      <section anchor="clock-sync" numbered="true" toc="include">
        <name>Clock Synchronization</name>
        <t>
          Both client and server clocks <bcp14>SHOULD</bcp14> be
          synchronized via NTP or an equivalent protocol.  The TBandSkew
          parameter (default: 1) allows for minor clock drift by
          accepting tokens from adjacent time bands.
        </t>
      </section>

      <section anchor="forward-secrecy" numbered="true" toc="include">
        <name>Forward Secrecy</name>
        <t>
          K1 is derived from the time band T.  Once a time band expires,
          the corresponding K1 cannot be recomputed without the seed S
          and the exact time band value.  Past tokens are unrecoverable
          without the R values, which are ephemeral and not stored by
          the server after replay checking.
        </t>
      </section>

      <section anchor="comparison-rfc9421" numbered="true" toc="include">
        <name>Comparison with HTTP Message Signatures (RFC 9421)</name>

        <table anchor="rfc9421-comparison-table">
          <name>SURADAR vs RFC 9421 Comparison</name>
          <thead>
            <tr>
              <th>Property</th>
              <th>RFC 9421</th>
              <th>SURADAR</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>Key per request</td>
              <td>Same key</td>
              <td>Unique key</td>
            </tr>
            <tr>
              <td>Context in key</td>
              <td>No (in signature input)</td>
              <td>Yes (in key derivation)</td>
            </tr>
            <tr>
              <td>Body coverage</td>
              <td>Optional</td>
              <td>Always</td>
            </tr>
            <tr>
              <td>Token size</td>
              <td>Variable (large)</td>
              <td>Fixed (48 bytes)</td>
            </tr>
            <tr>
              <td>Asymmetric support</td>
              <td>Yes</td>
              <td>No</td>
            </tr>
            <tr>
              <td>Replay prevention</td>
              <td>Not specified</td>
              <td>Built-in</td>
            </tr>
          </tbody>
        </table>
      </section>
    </section>

    <!-- ============================================================ -->
    <section anchor="iana" numbered="true" toc="include">
      <name>IANA Considerations</name>

      <section anchor="iana-auth-scheme" numbered="true" toc="include">
        <name>HTTP Authentication Scheme Registration</name>
        <t>
          This document requests registration of the following HTTP
          authentication scheme in the "HTTP Authentication Scheme
          Registry" established by <xref target="RFC9421"/>:
        </t>
        <dl>
          <dt>Authentication Scheme Name:</dt>
          <dd>SURADAR</dd>
          <dt>Reference:</dt>
          <dd>This document</dd>
          <dt>Notes:</dt>
          <dd>
            The SURADAR scheme uses per-request HMAC-based authentication.
            The token is transmitted in the X-SURADAR-Auth header rather
            than the Authorization header to avoid conflicts with existing
            authentication infrastructure.
          </dd>
        </dl>
      </section>

      <section anchor="iana-headers" numbered="true" toc="include">
        <name>HTTP Header Field Registration</name>
        <t>
          This document requests registration of the following HTTP header
          fields in the "Hypertext Transfer Protocol (HTTP) Field Name
          Registry":
        </t>
        <dl>
          <dt>X-SURADAR-Auth</dt>
          <dd>
            <dl>
              <dt>Status:</dt><dd>permanent</dd>
              <dt>Reference:</dt><dd><xref target="header-registration"/> of this document</dd>
            </dl>
          </dd>
          <dt>X-SURADAR-Client</dt>
          <dd>
            <dl>
              <dt>Status:</dt><dd>permanent</dd>
              <dt>Reference:</dt><dd><xref target="header-registration"/> of this document</dd>
            </dl>
          </dd>
          <dt>X-SURADAR-TBand</dt>
          <dd>
            <dl>
              <dt>Status:</dt><dd>permanent</dd>
              <dt>Reference:</dt><dd><xref target="header-registration"/> of this document</dd>
            </dl>
          </dd>
        </dl>
      </section>

      <section anchor="iana-algorithm-registry" numbered="true" toc="include">
        <name>SURADAR Algorithm Registry</name>
        <t>
          IANA is requested to create a new registry titled "SURADAR
          Algorithm Registry" with the following initial entry, under
          the registration policy of Specification Required
          <xref target="RFC8126"/>:
        </t>

        <table anchor="algorithm-registry-table">
          <name>SURADAR Algorithm Registry Initial Contents</name>
          <thead>
            <tr>
              <th>Algorithm Name</th>
              <th>HMAC Function</th>
              <th>Hash Function</th>
              <th>Reference</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>SURADAR-SHA256</td>
              <td>HMAC-SHA-256</td>
              <td>SHA-256</td>
              <td>This document</td>
            </tr>
          </tbody>
        </table>
      </section>
    </section>

  </middle>

  <back>

    <!-- ============================================================ -->
    <references>
      <name>References</name>

      <references>
        <name>Normative References</name>

        <reference anchor="RFC2104" target="https://www.rfc-editor.org/info/rfc2104">
          <front>
            <title>HMAC: Keyed-Hashing for Message Authentication</title>
            <author fullname="H. Krawczyk" initials="H." surname="Krawczyk"/>
            <author fullname="M. Bellare" initials="M." surname="Bellare"/>
            <author fullname="R. Canetti" initials="R." surname="Canetti"/>
            <date month="February" year="1997"/>
          </front>
          <seriesInfo name="RFC" value="2104"/>
          <seriesInfo name="DOI" value="10.17487/RFC2104"/>
        </reference>

        <reference anchor="RFC2119" target="https://www.rfc-editor.org/info/rfc2119">
          <front>
            <title>Key words for use in RFCs to Indicate Requirement Levels</title>
            <author fullname="S. Bradner" initials="S." surname="Bradner"/>
            <date month="March" year="1997"/>
          </front>
          <seriesInfo name="RFC" value="2119"/>
          <seriesInfo name="DOI" value="10.17487/RFC2119"/>
        </reference>

        <reference anchor="RFC4086" target="https://www.rfc-editor.org/info/rfc4086">
          <front>
            <title>Randomness Requirements for Security</title>
            <author fullname="D. Eastlake 3rd" initials="D." surname="Eastlake 3rd"/>
            <author fullname="J. Schiller" initials="J." surname="Schiller"/>
            <author fullname="S. Crocker" initials="S." surname="Crocker"/>
            <date month="June" year="2005"/>
          </front>
          <seriesInfo name="RFC" value="4086"/>
          <seriesInfo name="DOI" value="10.17487/RFC4086"/>
        </reference>

        <reference anchor="RFC4648" target="https://www.rfc-editor.org/info/rfc4648">
          <front>
            <title>The Base16, Base32, and Base64 Data Encodings</title>
            <author fullname="S. Josefsson" initials="S." surname="Josefsson"/>
            <date month="October" year="2006"/>
          </front>
          <seriesInfo name="RFC" value="4648"/>
          <seriesInfo name="DOI" value="10.17487/RFC4648"/>
        </reference>

        <reference anchor="RFC6234" target="https://www.rfc-editor.org/info/rfc6234">
          <front>
            <title>US Secure Hash Algorithms (SHA and SHA-based HMAC and HKDF)</title>
            <author fullname="D. Eastlake 3rd" initials="D." surname="Eastlake 3rd"/>
            <author fullname="T. Hansen" initials="T." surname="Hansen"/>
            <date month="May" year="2011"/>
          </front>
          <seriesInfo name="RFC" value="6234"/>
          <seriesInfo name="DOI" value="10.17487/RFC6234"/>
        </reference>

        <reference anchor="RFC8174" target="https://www.rfc-editor.org/info/rfc8174">
          <front>
            <title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
            <author fullname="B. Leiba" initials="B." surname="Leiba"/>
            <date month="May" year="2017"/>
          </front>
          <seriesInfo name="RFC" value="8174"/>
          <seriesInfo name="DOI" value="10.17487/RFC8174"/>
        </reference>

        <reference anchor="FIPS180-4" target="https://csrc.nist.gov/publications/detail/fips/180/4/final">
          <front>
            <title>Secure Hash Standard (SHS)</title>
            <author>
              <organization>National Institute of Standards and Technology (NIST)</organization>
            </author>
            <date month="August" year="2015"/>
          </front>
          <seriesInfo name="FIPS PUB" value="180-4"/>
        </reference>

      </references>

      <references>
        <name>Informative References</name>

        <reference anchor="RFC6749" target="https://www.rfc-editor.org/info/rfc6749">
          <front>
            <title>The OAuth 2.0 Authorization Framework</title>
            <author fullname="D. Hardt" initials="D." surname="Hardt" role="editor"/>
            <date month="October" year="2012"/>
          </front>
          <seriesInfo name="RFC" value="6749"/>
          <seriesInfo name="DOI" value="10.17487/RFC6749"/>
        </reference>

        <reference anchor="RFC6750" target="https://www.rfc-editor.org/info/rfc6750">
          <front>
            <title>The OAuth 2.0 Authorization Framework: Bearer Token Usage</title>
            <author fullname="M. Jones" initials="M." surname="Jones"/>
            <author fullname="D. Hardt" initials="D." surname="Hardt"/>
            <date month="October" year="2012"/>
          </front>
          <seriesInfo name="RFC" value="6750"/>
          <seriesInfo name="DOI" value="10.17487/RFC6750"/>
        </reference>

        <reference anchor="RFC6819" target="https://www.rfc-editor.org/info/rfc6819">
          <front>
            <title>OAuth 2.0 Threat Model and Security Considerations</title>
            <author fullname="T. Lodderstedt" initials="T." surname="Lodderstedt" role="editor"/>
            <author fullname="M. McGloin" initials="M." surname="McGloin"/>
            <author fullname="P. Hunt" initials="P." surname="Hunt"/>
            <date month="January" year="2013"/>
          </front>
          <seriesInfo name="RFC" value="6819"/>
          <seriesInfo name="DOI" value="10.17487/RFC6819"/>
        </reference>

        <reference anchor="RFC7519" target="https://www.rfc-editor.org/info/rfc7519">
          <front>
            <title>JSON Web Token (JWT)</title>
            <author fullname="M. Jones" initials="M." surname="Jones"/>
            <author fullname="J. Bradley" initials="J." surname="Bradley"/>
            <author fullname="N. Sakimura" initials="N." surname="Sakimura"/>
            <date month="May" year="2015"/>
          </front>
          <seriesInfo name="RFC" value="7519"/>
          <seriesInfo name="DOI" value="10.17487/RFC7519"/>
        </reference>

        <reference anchor="RFC8126" target="https://www.rfc-editor.org/info/rfc8126">
          <front>
            <title>Guidelines for Writing an IANA Considerations Section in RFCs</title>
            <author fullname="M. Cotton" initials="M." surname="Cotton"/>
            <author fullname="B. Leiba" initials="B." surname="Leiba"/>
            <author fullname="T. Narten" initials="T." surname="Narten"/>
            <date month="June" year="2017"/>
          </front>
          <seriesInfo name="RFC" value="8126"/>
          <seriesInfo name="DOI" value="10.17487/RFC8126"/>
        </reference>

        <reference anchor="RFC8179" target="https://www.rfc-editor.org/info/rfc8179">
          <front>
            <title>Intellectual Property Rights in IETF Technology</title>
            <author fullname="S. Bradner" initials="S." surname="Bradner"/>
            <author fullname="J. Contreras" initials="J." surname="Contreras"/>
            <date month="May" year="2017"/>
          </front>
          <seriesInfo name="RFC" value="8179"/>
          <seriesInfo name="DOI" value="10.17487/RFC8179"/>
        </reference>

        <reference anchor="RFC9421" target="https://www.rfc-editor.org/info/rfc9421">
          <front>
            <title>HTTP Message Signatures</title>
            <author fullname="A. Backman" initials="A." surname="Backman" role="editor"/>
            <author fullname="J. Richer" initials="J." surname="Richer" role="editor"/>
            <author fullname="M. Sporny" initials="M." surname="Sporny"/>
            <date month="February" year="2024"/>
          </front>
          <seriesInfo name="RFC" value="9421"/>
          <seriesInfo name="DOI" value="10.17487/RFC9421"/>
        </reference>

        <reference anchor="RFC6238" target="https://www.rfc-editor.org/info/rfc6238">
          <front>
            <title>TOTP: Time-Based One-Time Password Algorithm</title>
            <author fullname="D. M'Raihi" initials="D." surname="M'Raihi"/>
            <author fullname="S. Machani" initials="S." surname="Machani"/>
            <author fullname="M. Pei" initials="M." surname="Pei"/>
            <author fullname="J. Rydell" initials="J." surname="Rydell"/>
            <date month="May" year="2011"/>
          </front>
          <seriesInfo name="RFC" value="6238"/>
          <seriesInfo name="DOI" value="10.17487/RFC6238"/>
        </reference>

      </references>

    </references>

    <!-- ============================================================ -->
    <section anchor="test-vectors" numbered="true" toc="include">
      <name>Test Vectors</name>

      <section anchor="test-ctx" numbered="true" toc="include">
        <name>Context Fingerprint</name>
        <t>Input parameters:</t>
        <artwork type="ascii-art"><![CDATA[
method   = "GET"
path     = "/api/v1/findings"
orgID    = "acme-corp"
scope    = "api:read"
]]></artwork>
        <t>Concatenated input (hex):</t>
        <artwork type="ascii-art"><![CDATA[
474554                         # "GET"
00                             # separator
2f6170692f76312f66696e64696e6773 # "/api/v1/findings"
00                             # separator
61636d652d636f7270             # "acme-corp"
00                             # separator
6170693a72656164               # "api:read"
]]></artwork>
        <t>Context fingerprint:</t>
        <artwork type="ascii-art"><![CDATA[
ctx = SHA-256(above) =
  e3b7a0... (implementors: compute from the above byte sequence)
]]></artwork>
      </section>

      <section anchor="test-full" numbered="true" toc="include">
        <name>Full Token Generation</name>
        <t>Input parameters:</t>
        <artwork type="ascii-art"><![CDATA[
seed      = 0x0102030405060708090a0b0c0d0e0f10
            1112131415161718191a1b1c1d1e1f20   (32 bytes)
clientID  = "ci-runner-01"
orgID     = "acme-corp"
method    = "GET"
path      = "/api/v1/findings"
scope     = "api:read"
body      = ""  (empty)
unix_ts   = 1709769600
TBandSeconds = 30
R         = 0xdeadbeefdeadbeefdeadbeefdeadbeef  (16 bytes, fixed for test)
]]></artwork>
        <t>Derivation steps:</t>
        <artwork type="ascii-art"><![CDATA[
T          = floor(1709769600 / 30) = 56992320
T_bytes    = 0x0000000003660000  (8-byte big-endian)
ctx        = SHA-256("GET" || 0x00 || "/api/v1/findings" || 0x00
             || "acme-corp" || 0x00 || "api:read")
K1         = HMAC-SHA-256(seed, T_bytes || ctx)
K          = HMAC-SHA-256(K1, R)
sig        = HMAC-SHA-256(K, "")
token      = base64url_no_pad(R || sig)
]]></artwork>
        <t>
          Implementors <bcp14>MUST</bcp14> verify that their
          implementation produces identical intermediate values at each
          step.
        </t>
      </section>

      <section anchor="test-cross-lang" numbered="true" toc="include">
        <name>Cross-Language Byte Identity</name>
        <t>
          Implementations in different languages <bcp14>MUST</bcp14>
          produce byte-identical output for identical inputs.  In
          particular:
        </t>
        <ul>
          <li>
            String-to-bytes conversion <bcp14>MUST</bcp14> use UTF-8
            encoding.
          </li>
          <li>
            The time band T <bcp14>MUST</bcp14> be encoded as an 8-byte
            big-endian unsigned integer with leading zeros.
          </li>
          <li>
            The base64url encoding <bcp14>MUST NOT</bcp14> include padding
            characters.
          </li>
        </ul>
      </section>
    </section>

    <!-- ============================================================ -->
    <section anchor="comparison" numbered="true" toc="include">
      <name>Comparison with Existing Schemes</name>

      <section anchor="attack-matrix" numbered="true" toc="include">
        <name>Attack Resistance Matrix</name>

        <table anchor="attack-matrix-table">
          <name>Attack Resistance: Bearer vs JWT vs JWT+JTI vs SURADAR</name>
          <thead>
            <tr>
              <th>Attack</th>
              <th>Bearer</th>
              <th>JWT</th>
              <th>JWT+JTI</th>
              <th>SURADAR</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>Token Replay</td>
              <td>VULN</td>
              <td>VULN</td>
              <td>RESIST</td>
              <td>RESIST</td>
            </tr>
            <tr>
              <td>Scope Escalation</td>
              <td>VULN</td>
              <td>VULN</td>
              <td>VULN</td>
              <td>RESIST</td>
            </tr>
            <tr>
              <td>Cross-Org Abuse</td>
              <td>VULN</td>
              <td>VULN</td>
              <td>VULN</td>
              <td>RESIST</td>
            </tr>
            <tr>
              <td>Body Tampering</td>
              <td>VULN</td>
              <td>VULN</td>
              <td>VULN</td>
              <td>RESIST</td>
            </tr>
            <tr>
              <td>Method Swap</td>
              <td>VULN</td>
              <td>VULN</td>
              <td>VULN</td>
              <td>RESIST</td>
            </tr>
            <tr>
              <td>Path Swap</td>
              <td>VULN</td>
              <td>VULN</td>
              <td>VULN</td>
              <td>RESIST</td>
            </tr>
            <tr>
              <td>Stolen Token Blast</td>
              <td>VULN</td>
              <td>VULN</td>
              <td>VULN</td>
              <td>RESIST</td>
            </tr>
            <tr>
              <td>Score</td>
              <td>0/7</td>
              <td>0/7</td>
              <td>2/7</td>
              <td>7/7</td>
            </tr>
          </tbody>
        </table>
      </section>

      <section anchor="performance" numbered="true" toc="include">
        <name>Performance Comparison</name>
        <t>
          Benchmarks measured on Apple M3, Go 1.26.1, single-threaded,
          1000 iterations, median values:
        </t>

        <table anchor="performance-table">
          <name>Performance: Bearer vs JWT vs JWT+JTI vs SURADAR</name>
          <thead>
            <tr>
              <th>Metric</th>
              <th>Bearer</th>
              <th>JWT</th>
              <th>JWT+JTI</th>
              <th>SURADAR</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>Generation</td>
              <td>729 ns</td>
              <td>1024 ns</td>
              <td>1024 ns</td>
              <td>1298 ns</td>
            </tr>
            <tr>
              <td>Verification</td>
              <td>52 ns</td>
              <td>1107 ns</td>
              <td>1868 ns</td>
              <td>1630 ns</td>
            </tr>
            <tr>
              <td>Token size</td>
              <td>32 B</td>
              <td>195 B</td>
              <td>236 B</td>
              <td>82 B</td>
            </tr>
            <tr>
              <td>Security score</td>
              <td>0/7</td>
              <td>0/7</td>
              <td>2/7</td>
              <td>7/7</td>
            </tr>
          </tbody>
        </table>
      </section>
    </section>

  </back>

</rfc>
