Class ScramSaslServer

java.lang.Object
org.jivesoftware.openfire.sasl.ScramSaslServer
All Implemented Interfaces:
SaslServer
Direct Known Subclasses:
ScramSha1SaslServer, ScramSha256SaslServer, ScramSha512SaslServer

public abstract class ScramSaslServer extends Object implements SaslServer
Implements the server side of the SCRAM SASL exchange as defined in RFC 5802, including the channel binding (-PLUS) variants defined there and profiled for other hash functions in RFC 7677. The exchange logic in this class is hash-function agnostic. Concrete subclasses bind a specific hash function by implementing the small set of abstract methods: the base mechanism name (which doubles as the credential storage key), the HMAC and message digest algorithm names, the default iteration count, and the server-side secret used to derive indistinguishable fake credentials for non-existent users. Instances are session-specific and must not be reused across sessions or users. The available SASL mechanisms are established when the instance is created and are used, in particular, to correctly process the GS2 header and enforce channel-binding downgrade protection.
Author:
Richard Midwinter, Guus der Kinderen
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final String
    The name of the negotiated property through which the channel binding type that was used during authentication is exposed.
  • Constructor Summary

    Constructors
    Modifier
    Constructor
    Description
    protected
    ScramSaslServer(boolean isPlusMechanism, Map<String,?> props, ChannelBindingProviderManager channelBindingProviderManager, Set<String> availableMechanismsForSession)
    Creates a new, client-specific, instance.
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    Disposes of any system resources or security-sensitive information the SaslServer might be using.
    byte[]
    evaluateResponse(byte[] response)
    Evaluates a single client response and advances the SCRAM exchange one step.
    protected static byte[]
    extractRawGS2Header(byte[] data)
    Extracts the raw GS2 header from a SCRAM client-first-message byte array.
    Reports the authorization ID in effect for the client of this session.
    protected abstract int
    The iteration count to advertise when no per-user value is available.
    protected abstract String
    The JCA name of the message digest that corresponds to this mechanism's hash function, for example SHA-1.
    protected abstract String
    The JCA name of the HMAC algorithm that corresponds to this mechanism's hash function, for example HmacSHA1.
    protected abstract String
    The IANA-registered name of the base (non-PLUS) mechanism implemented by this server, for example SCRAM-SHA-1.
    Returns the IANA-registered mechanism name of this SASL server, which is the base mechanism name with a -PLUS suffix for the channel binding variant.
    Retrieves the negotiated property.
    protected abstract String
    A server-side secret from which deterministic fake credentials are derived for non-existent users, so that authentication processing for non-existing users is indistinguishable from that of existing users.
    protected byte[]
    Retrieve the salt for a given username.
    protected byte[]
    Retrieve the server key from the database for a given username, but returns a fake key if none is found.
    protected byte[]
    Retrieve the stored key from the database for a given username, but returns a fake key if none is found.
    boolean
    Determines whether the authentication exchange has completed.
    byte[]
    unwrap(byte[] incoming, int offset, int len)
    Unwraps a byte array received from the client.
    byte[]
    wrap(byte[] outgoing, int offset, int len)
    Wraps a byte array to be sent to the client.

    Methods inherited from class java.lang.Object

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

    • PROPNAME_CHANNELBINDINGTYPE

      public static final String PROPNAME_CHANNELBINDINGTYPE
      The name of the negotiated property through which the channel binding type that was used during authentication is exposed.
      See Also:
  • Constructor Details

    • ScramSaslServer

      protected ScramSaslServer(boolean isPlusMechanism, Map<String,?> props, ChannelBindingProviderManager channelBindingProviderManager, @Nonnull Set<String> availableMechanismsForSession)
      Creates a new, client-specific, instance.
      Parameters:
      isPlusMechanism - Denotes if this instance supports channel-binding (true) or not (false).
      props - The possibly null set of properties used to select the SASL mechanism and to configure the authentication exchange of the selected mechanism.
      channelBindingProviderManager - Manages a set of providers that can extract channel binding data of various types from SSL engines. Must be set for plus-mechanisms.
      availableMechanismsForSession - The names of SASL mechanisms that are available to this particular session (as opposed to the set of globally available mechanism names).
  • Method Details

    • getMechanismBaseName

      protected abstract String getMechanismBaseName()
      The IANA-registered name of the base (non-PLUS) mechanism implemented by this server, for example SCRAM-SHA-1. This value is also the key under which SCRAM credentials for this mechanism are stored: the -PLUS variant shares the credential of the base mechanism.
      Returns:
      A non-null string representing the IANA-registered (base) mechanism name.
    • getHmacAlgorithmName

      protected abstract String getHmacAlgorithmName()
      The JCA name of the HMAC algorithm that corresponds to this mechanism's hash function, for example HmacSHA1.
      Returns:
      the HMAC algorithm name.
    • getDigestAlgorithmName

      protected abstract String getDigestAlgorithmName()
      The JCA name of the message digest that corresponds to this mechanism's hash function, for example SHA-1. Used to compute H(ClientKey) when verifying the client proof.
      Returns:
      the message digest algorithm name.
    • getDefaultIterationCount

      protected abstract int getDefaultIterationCount()
      The iteration count to advertise when no per-user value is available.
      Returns:
      the default iteration count for this mechanism.
    • getNonExistentUserSecret

      protected abstract String getNonExistentUserSecret()
      A server-side secret from which deterministic fake credentials are derived for non-existent users, so that authentication processing for non-existing users is indistinguishable from that of existing users.
      Returns:
      the server secret for this mechanism.
      See Also:
    • getMechanismName

      public String getMechanismName()
      Returns the IANA-registered mechanism name of this SASL server, which is the base mechanism name with a -PLUS suffix for the channel binding variant.
      Specified by:
      getMechanismName in interface SaslServer
      Returns:
      A non-null string representing the IANA-registered mechanism name.
    • evaluateResponse

      public byte[] evaluateResponse(byte[] response) throws SaslException
      Evaluates a single client response and advances the SCRAM exchange one step. Dispatch is driven by state: ScramSaslServer.State.INITIAL treats the response as client-first-message (see generateServerFirstMessage(byte[])); ScramSaslServer.State.IN_PROGRESS treats it as client-final-message (see generateServerFinalMessage(byte[])); once ScramSaslServer.State.COMPLETE, an empty response is tolerated but a non-empty one is rejected. Any RuntimeException thrown while processing is re-wrapped as a SaslException, so an implementation defect surfaces as a failed authentication attempt rather than an unchecked exception.
      Specified by:
      evaluateResponse in interface SaslServer
      Parameters:
      response - The non-null (but possibly empty) response sent by the client.
      Returns:
      The possibly null challenge to send to the client; null only once the exchange has concluded.
      Throws:
      SaslException - if the response is invalid for the current state, or arrives after completion.
    • isComplete

      public boolean isComplete()
      Determines whether the authentication exchange has completed. This method is typically called after each invocation of evaluateResponse() to determine whether the authentication has completed successfully or should be continued.
      Specified by:
      isComplete in interface SaslServer
      Returns:
      true if the authentication exchange has completed; false otherwise.
    • getAuthorizationID

      public String getAuthorizationID()
      Reports the authorization ID in effect for the client of this session. This method can only be called if isComplete() returns true.
      Specified by:
      getAuthorizationID in interface SaslServer
      Returns:
      The authorization ID of the client.
      Throws:
      IllegalStateException - if this authentication session has not completed
    • unwrap

      public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException
      Unwraps a byte array received from the client. SCRAM supports no security layer.
      Specified by:
      unwrap in interface SaslServer
      Returns:
      the unwrapped byte array.
      Throws:
      SaslException - if attempted to use this method.
    • wrap

      public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException
      Wraps a byte array to be sent to the client. SCRAM supports no security layer.
      Specified by:
      wrap in interface SaslServer
      Throws:
      SaslException - if attempted to use this method.
    • getNegotiatedProperty

      public Object getNegotiatedProperty(String propName)
      Retrieves the negotiated property. This method can be called only after the authentication exchange has completed (i.e., when isComplete() returns true); otherwise, an IllegalStateException is thrown.
      Specified by:
      getNegotiatedProperty in interface SaslServer
      Parameters:
      propName - the property
      Returns:
      The value of the negotiated property. If null, the property was not negotiated or is not applicable to this mechanism.
      Throws:
      IllegalStateException - if this authentication exchange has not completed
    • dispose

      public void dispose() throws SaslException
      Disposes of any system resources or security-sensitive information the SaslServer might be using. Invoking this method invalidates the SaslServer instance. This method is idempotent.
      Specified by:
      dispose in interface SaslServer
      Throws:
      SaslException - If a problem was encountered while disposing the resources.
    • getOrCreateSalt

      protected byte[] getOrCreateSalt(String username)
      Retrieve the salt for a given username. When a salt does not currently exist for an existing user, but a password is set, that value is used to create and persist a new salt for that user. Returns a username-specific salt if the user doesn't exist to mimic an invalid password. This also guards against user enumeration attacks.
      See Also:
    • getOrFakeServerKey

      protected byte[] getOrFakeServerKey(String username)
      Retrieve the server key from the database for a given username, but returns a fake key if none is found.

      Returning a fake key helps guard against timing attacks: instead of short-circuiting the operation, a fake key is generated to ensure consistent response times and prevent potential timing attacks.

      Returns:
      The server key for the given username.
      See Also:
    • getOrFakeStoredKey

      protected byte[] getOrFakeStoredKey(String username)
      Retrieve the stored key from the database for a given username, but returns a fake key if none is found.

      Returning a fake key helps guard against timing attacks: instead of short-circuiting the operation, a fake key is generated to ensure consistent response times and prevent potential timing attacks.

      Returns:
      The stored key for the given username.
      See Also:
    • extractRawGS2Header

      protected static byte[] extractRawGS2Header(byte[] data) throws SaslException
      Extracts the raw GS2 header from a SCRAM client-first-message byte array. The GS2 header is defined in RFC 5802 as:
       gs2-header = gs2-cbind-flag "," [authzid] ","
       
      and always terminates with a trailing comma. This method performs a byte-level scan of the input and returns a copy of the original byte array from index 0 up to and including the second comma (i.e., the full GS2 header including its trailing comma). No character decoding or normalization is performed. This ensures that the returned GS2 header is byte-for- byte identical to the original input, which is required for correct -PLUS channel binding validation in SCRAM mechanisms.
      Parameters:
      data - the raw SCRAM client-first-message bytes
      Returns:
      a byte array containing the complete GS2 header including the trailing comma
      Throws:
      SaslException - if the input does not contain a valid GS2 header