Class TlsSession

java.lang.Object
io.github.mahmoudimus.quic.tls.TlsSession

public final class TlsSession extends Object
A TLS 1.3 handshake for QUIC (RFC 9001), as a sans-I/O state machine modelled on quinn's crypto::Session. There is no record layer: handshake messages travel in CRYPTO frames at an EncryptionLevel, and the traffic secrets for each level are handed to the connection as Secrets.

The connection layer drives a session like this:

  1. Create it with client(TlsConfig, byte[]) or server(TlsConfig, byte[]), passing the encoded local transport parameters. A client has its ClientHello ready at once.
  2. Feed each level's in-order CRYPTO stream bytes to readHandshake(io.github.mahmoudimus.quic.EncryptionLevel, java.nio.ByteBuffer). Messages may be split or coalesced arbitrarily.
  3. After each call (and after creating a client), install any keys nextSecrets() returns, then send what writeHandshake(EncryptionLevel) returns for each level.
  4. When the handshake is complete, use nextOneRttSecrets() for key updates.

Secrets arrive in this order. A client gets the EncryptionLevel.HANDSHAKE secrets after the ServerHello, and the EncryptionLevel.ONE_RTT secrets once it has checked the server's Finished and queued its own; the handshake is then complete for it. A server gets both as soon as it has processed the ClientHello and queued its whole flight, ending with its Finished: it may send 1-RTT (0.5-RTT) data at once but, as RFC 9001 section 5.7 requires, must not process 1-RTT packets from the client until isHandshakeComplete(), which happens when the client's Finished has been verified. Confirming the handshake (HANDSHAKE_DONE) and discarding keys are left to the connection layer.

Errors are QuicExceptions: TLS alerts become CRYPTO_ERROR codes ( 0x0100 + alert, see TlsAlert), a message longer than the configured limit becomes CRYPTO_BUFFER_EXCEEDED, and a non-empty legacy_session_id sent to a server becomes PROTOCOL_VIOLATION. After an error the session is dead and every further readHandshake throws the same error.

A session is not thread-safe and takes no locks: a handshake is a single-threaded state machine, driven by one connection at a time. Its certificate and ALPN selectors run on the thread that calls readHandshake(io.github.mahmoudimus.quic.EncryptionLevel, java.nio.ByteBuffer).

Resumption and 0-RTT. A server with a ticketer queues NewSessionTicket messages at EncryptionLevel.ONE_RTT when its handshake completes, and resumes the sessions they seal. A client hands each ticket it receives out through pollNewTicket() and resumes with one passed to client(TlsConfig, String, byte[], SessionTicket). When a client offers early data, its first secrets are the EncryptionLevel.ZERO_RTT ones, available at once: Secrets(ZERO_RTT, suite, client_early_traffic_secret, empty). A server that accepts the early data hands out the same secrets first, before the Handshake ones. Whether it was accepted is known to the client after EncryptedExtensions (isEarlyDataAccepted()).

  • Method Details

    • client

      public static TlsSession client(TlsConfig config, byte[] transportParameters)
      Starts a client handshake to the configuration's server name. The ClientHello is queued at EncryptionLevel.INITIAL before this returns.
      Parameters:
      config - the configuration, with ALPN protocols
      transportParameters - the encoded local QUIC transport parameters
      Returns:
      the session
      Throws:
      IllegalArgumentException - if the configuration has no ALPN protocols, or no server name while it verifies host names
    • client

      public static TlsSession client(TlsConfig config, String serverName, byte[] transportParameters)
      Starts a client handshake to a server name. The ClientHello is queued at EncryptionLevel.INITIAL before this returns.
      Parameters:
      config - the configuration, with ALPN protocols
      serverName - the host name (sent as SNI) or IP literal the certificate must match; may be null only if the configuration does not verify host names
      transportParameters - the encoded local QUIC transport parameters
      Returns:
      the session
      Throws:
      IllegalArgumentException - if the configuration has no ALPN protocols, or no server name while it verifies host names
    • client

      public static TlsSession client(TlsConfig config, String serverName, byte[] transportParameters, SessionTicket ticket)
      Starts a client handshake that offers to resume a session. The ticket is used only if it has not expired, is for the same server name and has a cipher suite the configuration offers; with earlyData and a ticket that allows it, the client also offers 0-RTT, and its EncryptionLevel.ZERO_RTT secrets are ready at once.
      Parameters:
      config - the configuration, with ALPN protocols
      serverName - the host name (sent as SNI) or IP literal the certificate must match; may be null only if the configuration does not verify host names
      transportParameters - the encoded local QUIC transport parameters
      ticket - a ticket from an earlier session with this server, used once; or null
      Returns:
      the session
      Throws:
      IllegalArgumentException - if the configuration has no ALPN protocols, or no server name while it verifies host names
    • server

      public static TlsSession server(TlsConfig config, byte[] transportParameters)
      Starts a server handshake, which waits for a ClientHello.
      Parameters:
      config - the configuration, with a certificate or certificate selector, and ALPN protocols or an ALPN selector
      transportParameters - the encoded local QUIC transport parameters
      Returns:
      the session
      Throws:
      IllegalArgumentException - if the configuration has no certificate or no ALPN
    • server

      public static TlsSession server(TlsConfig config, byte[] transportParameters, Predicate<byte[]> earlyDataParameters)
      Starts a server handshake with a check on the QUIC transport parameters a ticket remembers. Before accepting 0-RTT, the handshake passes the encoded parameters the server had when it issued the ticket to earlyDataParameters; returning false rejects the early data, as RFC 9000 section 7.4.1 requires when the current limits are smaller.
      Parameters:
      config - the configuration
      transportParameters - the encoded local QUIC transport parameters, also sealed in tickets
      earlyDataParameters - accepts the remembered parameters, or null to accept any
      Returns:
      the session
      Throws:
      IllegalArgumentException - if the configuration has no certificate or no ALPN
    • isClient

      public boolean isClient()
      Whether this is the client side.
      Returns:
      true for a client session
    • readHandshake

      public void readHandshake(EncryptionLevel level, ByteBuffer data) throws QuicException
      Feeds CRYPTO stream bytes received at an encryption level, in stream order. All of data is consumed. Messages may be split across calls or coalesced in one; each complete message is processed at once, which may queue output and secrets.
      Parameters:
      level - the encryption level the bytes arrived at
      data - the bytes; its position is advanced to its limit
      Throws:
      QuicException - if the bytes are at the wrong level, malformed, or fail the handshake
    • writeHandshake

      public byte[] writeHandshake(EncryptionLevel level)
      Takes the handshake bytes queued for sending at a level, in order.
      Parameters:
      level - the encryption level
      Returns:
      the bytes for CRYPTO frames at that level; empty if there are none
    • hasHandshakeData

      public boolean hasHandshakeData(EncryptionLevel level)
      Whether handshake bytes are queued at a level.
      Parameters:
      level - the encryption level
      Returns:
      true if writeHandshake(EncryptionLevel) would return bytes
    • nextSecrets

      public Secrets nextSecrets()
      Takes the next newly available secrets, in the order they became available.
      Returns:
      the secrets for one level, or null if there are no new ones
    • nextOneRttSecrets

      public Secrets nextOneRttSecrets()
      Derives the next generation of 1-RTT secrets for a key update (RFC 9001 section 6): HKDF-Expand-Label(secret, "quic ku", "", Hash.length) on both traffic secrets, with the suite's hash. Each call moves one generation on.
      Returns:
      the next 1-RTT secrets
      Throws:
      IllegalStateException - if the 1-RTT secrets are not available yet
    • isHandshaking

      public boolean isHandshaking()
      Whether the handshake is still in progress: true until it completes, and also after it has failed.
      Returns:
      true until isHandshakeComplete()
    • isHandshakeComplete

      public boolean isHandshakeComplete()
      Whether the handshake is complete (RFC 9001 section 4.1.1): for a client, once it has verified the server's Finished and queued its own; for a server, once it has verified the client's Finished.
      Returns:
      true when complete
    • peerTransportParameters

      public byte[] peerTransportParameters()
      The peer's encoded QUIC transport parameters, which the connection layer decodes.
      Returns:
      a copy of the parameters, or null until they have been received (a server has them after the ClientHello, a client after EncryptedExtensions)
    • negotiatedAlpn

      public String negotiatedAlpn()
      The application protocol agreed with ALPN.
      Returns:
      the protocol, or null until it has been agreed
    • serverName

      public String serverName()
      The server name: for a client, the one it connects to; for a server, the client's SNI.
      Returns:
      the name, or null if there is none (yet)
    • peerCertificates

      public List<X509Certificate> peerCertificates()
      The peer's certificate chain, end-entity first: the server's for a client, and the client's for a server that asked for one.
      Returns:
      the chain, empty until received or if the peer sent none
    • peerSignatureScheme

      public SignatureScheme peerSignatureScheme()
      The signature scheme of the peer's CertificateVerify.
      Returns:
      the scheme, or null until it has been verified or if the peer signed nothing
    • cipherSuite

      public CipherSuite cipherSuite()
      The negotiated cipher suite.
      Returns:
      the suite, or null until it is known
    • negotiatedGroup

      public NamedGroup negotiatedGroup()
      The key exchange group used.
      Returns:
      the group, or null until it is known
    • usedHelloRetryRequest

      public boolean usedHelloRetryRequest()
      Whether the server sent a HelloRetryRequest, asking for a key share in another group.
      Returns:
      true after a retry
    • isResumed

      public boolean isResumed()
      Whether the handshake resumed a session with a PSK from a ticket. A client knows after the ServerHello; a server after the ClientHello.
      Returns:
      true if resumed
    • isEarlyDataOffered

      public boolean isEarlyDataOffered()
      Whether the client offered 0-RTT data in its first ClientHello.
      Returns:
      true if offered
    • isEarlyDataAccepted

      public boolean isEarlyDataAccepted()
      Whether the server accepted 0-RTT data. A client knows after EncryptedExtensions; until then, and if the early data was rejected, this is false.
      Returns:
      true if accepted
    • pollNewTicket

      public SessionTicket pollNewTicket()
      Client: takes the next ticket received in a NewSessionTicket message, to keep for a later connection.
      Returns:
      the ticket, or null if there is none
    • clientRandom

      public byte[] clientRandom()
      The ClientHello's random value, which names the secrets of this handshake in the SSLKEYLOGFILE format that packet analyzers read.
      Returns:
      a copy of the 32 bytes, or null until known (a server learns it from the ClientHello)
    • exportKeyingMaterial

      public byte[] exportKeyingMaterial(String label, byte[] context, int length)
      Keying material from the TLS exporter (RFC 8446 section 7.5).
      Parameters:
      label - the exporter label, ASCII
      context - the context; null and empty are the same in TLS 1.3
      length - the number of bytes, at most 255 times the suite's hash length
      Returns:
      the keying material
      Throws:
      IllegalStateException - if the 1-RTT secrets are not available yet
      IllegalArgumentException - if the label or length is too long