Class PqcTransportInterceptor

java.lang.Object
co.ankatech.ankasecure.sdk.transport.PqcTransportInterceptor
All Implemented Interfaces:
okhttp3.Interceptor

public final class PqcTransportInterceptor extends Object implements okhttp3.Interceptor
OkHttp Interceptor that transparently encrypts request bodies and decrypts response bodies when PQC transport encryption is enabled.

Not wrapped, and passed through unchanged: every request while the transport is disabled, the handshake operations (HANDSHAKE_PATH_PREFIX), /api/v3/crypto/stream/, /api/v3/interoperability/, requests without a body (GET, DELETE, ...) and requests whose body is duplex. Every other request is wrapped, the token grant included.

How a response is classified

  • application/problem+json on a 4xx/5xx status: a refusal the service wrote in plaintext before the transport layer could encrypt anything (session not found, undecryptable body, transport error, session store unavailable), or one a filter ahead of the transport layer wrote (401, 403, 413, 421). It is not decrypted; it is passed on unchanged, so the generated client and ExceptionTranslator surface it as the typed SDK error carrying the problem.
  • application/problem+json on any other status: refused with PqcTransportException (UNENCRYPTED_RESPONSE), unread. A refusal is never a success, so a plaintext body with a success status is exactly what a forging intermediary would send.
  • application/octet-stream: an encrypted body, decrypted whatever its status; an encrypted 4xx/5xx reaches the caller as JSON exactly like a 2xx. An empty encrypted body is refused (UNDECRYPTABLE_RESPONSE): the services never encrypt an empty body, and an empty body carries no authentication tag to check.
  • Anything else, or no media type: refused with PqcTransportException (UNENCRYPTED_RESPONSE). This includes an empty 2xx or 204 without a media type, by intent: the services skip encryption only for an empty body, and an empty unauthenticated answer cannot be told apart from a forged one. An unencrypted body is never handed to the caller as if it had been decrypted.
A network failure while reading an encrypted body propagates as the IOException it is (a read timeout stays a timeout); only a body that was read in full and does not open is UNDECRYPTABLE_RESPONSE.

A lost session is re-established once

A plaintext 400 problem whose extensions.code is pqc.session.notfound means the server no longer holds the session. The interceptor then evicts it (destroying its key), establishes a new one through PqcSessionManager's single-flight replacement (concurrent requests on the same lost session cause one handshake), re-encrypts the original body under the new key and sends it once more. A second refusal, of any kind, is returned as the typed refusal. The decision reads the code member only, never the localized detail. A failed handshake is an error; the request never falls back to plaintext.
  • Nested Class Summary

    Nested classes/interfaces inherited from interface okhttp3.Interceptor

    okhttp3.Interceptor.Chain, okhttp3.Interceptor.Companion
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final String
    The path prefix of the handshake operations the generated PqcHandshakeApi calls (/init, /supported-algorithms).
    static final String
    The header carrying this interceptor's positive mark: set on a response only after its body was decrypted under the session key in open(), and removed from every other response.
    static final String
    Header that tells the server which KEM algorithm to use.
    static final String
    Header that carries the PQC session identifier.
    static final String
    Header that indicates the payload is PQC-encrypted.

    Fields inherited from interface okhttp3.Interceptor

    Companion
  • Constructor Summary

    Constructors
    Constructor
    Description
     
  • Method Summary

    Modifier and Type
    Method
    Description
    okhttp3.Response
    intercept(okhttp3.Interceptor.Chain chain)
     
    static boolean
    Whether a header value is this process's authenticated mark, compared in constant time.

    Methods inherited from class java.lang.Object

    equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

    • HEADER_TRANSPORT

      public static final String HEADER_TRANSPORT
      Header that indicates the payload is PQC-encrypted.
      See Also:
    • HEADER_SESSION

      public static final String HEADER_SESSION
      Header that carries the PQC session identifier.
      See Also:
    • HEADER_KEM

      public static final String HEADER_KEM
      Header that tells the server which KEM algorithm to use.
      See Also:
    • HANDSHAKE_PATH_PREFIX

      public static final String HANDSHAKE_PATH_PREFIX
      The path prefix of the handshake operations the generated PqcHandshakeApi calls (/init, /supported-algorithms). They run before a session exists, so they are never wrapped: wrapping /init would ask for the session it is creating. The prefix is the destination itself, not a setting, so the exemption cannot drift from where the handshake goes.
      See Also:
    • HEADER_AUTHENTICATED

      public static final String HEADER_AUTHENTICATED
      The header carrying this interceptor's positive mark: set on a response only after its body was decrypted under the session key in open(), and removed from every other response. Its value is a random token drawn once per process (isAuthenticatedMark(String)), so a server-sent copy, with or without PQC transport, is never taken for the mark. ExceptionTranslator turns it into the failure's PqcRoutePolicy context entry, the one signal that admits a fallback to the streaming route under PQC transport.
      See Also:
  • Constructor Details

  • Method Details

    • isAuthenticatedMark

      public static boolean isAuthenticatedMark(String value)
      Whether a header value is this process's authenticated mark, compared in constant time.
      Parameters:
      value - the value of HEADER_AUTHENTICATED on a response, or null
      Returns:
      true only for the value this interceptor sets after a successful decrypt
    • intercept

      @NotNull public okhttp3.Response intercept(okhttp3.Interceptor.Chain chain) throws IOException
      Specified by:
      intercept in interface okhttp3.Interceptor
      Throws:
      IOException