Class Ht2SaslServer

java.lang.Object
org.jivesoftware.openfire.sasl.Ht2SaslServer
All Implemented Interfaces:
SaslServer

public class Ht2SaslServer extends Object
Implementation of the HT2-* family of SASL mechanisms for FAST (XEP-0484), as defined in draft-ietf-kitten-sasl-ht-02, supporting all hash (SHA-256, SHA-512, SHA3-512) and channel-binding (NONE, UNIQ, ENDP, EXPR) variants.

The initiator message format (NUL-byte separated) is:

authcid NUL extra-initiator-values NUL initiator-hashed-token
where initiator-hashed-token = HMAC(token, "Initiator" || cb-data || extra-initiator-values) and the HMAC algorithm is derived from the mechanism name (e.g. HmacSHA256 for SHA-256 variants and HmacSHA3-512 for SHA3-512 variants). For the NONE (no channel binding) variant, cb-data is an empty byte sequence.

On success the server sends back a success message:

NUL extra-responder-values NUL responder-hashed-token
where responder-hashed-token = HMAC(token, "Responder" || cb-data || extra-responder-values). This provides mutual authentication.

This is a two-message mechanism: the client sends one message; the server evaluates it and returns the responder proof via SaslServer.evaluateResponse(byte[]).

Channel-binding data is resolved by the base class AbstractHtSaslServer before doEvaluateResponse(byte[], byte[]) is called. Unlike HT-*, the channel-binding bytes are incorporated into the HMAC computation. Authentication is rejected if channel-binding data cannot be retrieved — matching the SCRAM-SHA-1-PLUS behaviour.

See Also:
  • Field Details

    • mechanismName

      protected final String mechanismName
      The SASL mechanism name (e.g. HT-SHA-256-NONE or HT2-SHA-512-EXPR).
    • props

      protected final Map<String,?> props
      The SASL properties map; must contain a LocalSession under LocalSession.class.getCanonicalName()
    • tokenValidator

      protected final org.jivesoftware.openfire.sasl.AbstractHtSaslServer.HashedTokenValidator tokenValidator
    • complete

      protected boolean complete
    • authorizationId

      protected String authorizationId
    • rotatedToken

      protected FastToken rotatedToken
  • Constructor Details

    • Ht2SaslServer

      public Ht2SaslServer(@Nonnull String mechanismName, @Nonnull Map<String,?> props)
      Constructs an Ht2SaslServer for the given mechanism name.

      The mechanism name must follow the pattern HT2-{HASH}-{CBTYPE}, e.g. HT2-SHA-256-NONE, HT2-SHA-512-UNIQ, or HT2-SHA3-512-EXPR.

      Parameters:
      mechanismName - the SASL mechanism name (cannot be null)
      props - the SASL properties map, which must contain the LocalSession instance under LocalSession.class.getCanonicalName() for UNIQ/ENDP/EXPR channel-binding variants (cannot be null)
  • Method Details

    • doEvaluateResponse

      protected byte[] doEvaluateResponse(byte[] response, byte[] channelBindingData) throws SaslException
      Evaluates the client's initiator message (mechanism-specific part).

      Called by SaslServer.evaluateResponse(byte[]) after guard checks and channel-binding resolution. The channelBindingData bytes are incorporated into the HMAC computation performed by FastTokenManager.validateTokenHt2(java.lang.String, java.lang.String, java.lang.String, byte[], byte[], java.lang.String, java.lang.String).

      Expected format: authcid NUL extra-initiator-values NUL initiator-hashed-token

      Parameters:
      response - the client's initiator message bytes (never null or empty)
      channelBindingData - the resolved channel-binding bytes (empty for NONE variants)
      Returns:
      the responder success message: NUL extra-responder-values NUL responder-hashed-token
      Throws:
      SaslException - if authentication fails
    • dispose

      public void dispose() throws SaslException
      Specified by:
      dispose in interface SaslServer
      Throws:
      SaslException
    • recordAuthenticatedClient

      protected final void recordAuthenticatedClient(String clientId)
    • getMechanismName

      public String getMechanismName()
      Specified by:
      getMechanismName in interface SaslServer
    • evaluateResponse

      public final byte[] evaluateResponse(byte[] response) throws SaslException
      Evaluates the client's response.

      This method handles the common guard checks (already-complete, null/empty response) and resolves channel-binding data for the mechanism's CB variant before delegating to doEvaluateResponse(byte[], byte[]).

      Specified by:
      evaluateResponse in interface SaslServer
      Parameters:
      response - the client response bytes
      Returns:
      mechanism-specific success bytes (the responder HMAC for HT-*, or the framed responder proof for HT2-*)
      Throws:
      SaslException - if authentication fails
    • resolveChannelBindingData

      protected byte[] resolveChannelBindingData() throws SaslException
      Resolves the channel-binding data for this mechanism.

      The channel-binding type is derived from the mechanism name suffix: -UNIQtls-unique, -ENDPtls-server-end-point, -EXPRtls-exporter, -NONE → no channel binding (empty array).

      For non-NONE variants the server verifies that the required binding type is available, retrieves the actual bytes from the live TLS session, and throws SaslException if they cannot be obtained — matching the SCRAM-SHA-1-PLUS behaviour.

      Returns:
      the channel-binding bytes (never null; empty array for NONE variants)
      Throws:
      SaslException - if channel-binding data is required but cannot be retrieved
    • isComplete

      public boolean isComplete()
      Specified by:
      isComplete in interface SaslServer
    • getAuthorizationID

      public String getAuthorizationID()
      Specified by:
      getAuthorizationID in interface SaslServer
    • getRotatedToken

      public FastToken getRotatedToken()
      Returns the rotated FAST token produced after successful authentication, or null if authentication has not completed successfully.
      Returns:
      the rotated FastToken, or null
    • unwrap

      public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException
      Specified by:
      unwrap in interface SaslServer
      Throws:
      SaslException
    • wrap

      public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException
      Specified by:
      wrap in interface SaslServer
      Throws:
      SaslException
    • getNegotiatedProperty

      public Object getNegotiatedProperty(String propName)
      Specified by:
      getNegotiatedProperty in interface SaslServer
    • indexOf

      protected static int indexOf(byte[] array, byte target, int fromIndex)
      Returns the index of the first occurrence of target in array starting at fromIndex, or -1 if not found.
    • decodeUtf8

      protected final String decodeUtf8(byte[] value, int offset, int length, String field) throws SaslException
      Throws:
      SaslException
    • decodeAuthcId

      protected String decodeAuthcId(@Nonnull String value) throws SaslException
      Converts the authcid from a FAST initiator message into a normalized local username. The value is expected to be a bare username, but a domain-qualified form (username@domain) is also accepted, provided that the domain matches the domain of this server. In both cases the returned value is the stringprep'ed node, which is the form that the caller compares against the username claimed in the stream's 'from' attribute.
      Parameters:
      value - the raw authcid as sent by the client (cannot be null)
      Returns:
      the normalized local username (never null)
      Throws:
      SaslException - if the value cannot be prepared as a username, or names another domain