![JAR search and dependency download from the Maven repository](/logo.png)
io.github.hapjava.server.impl.pairing.HomekitSRP6ServerSession Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hap Show documentation
Show all versions of hap Show documentation
Homekit Accessory Protocol for Java
package io.github.hapjava.server.impl.pairing;
import com.nimbusds.srp6.SRP6ClientEvidenceContext;
import com.nimbusds.srp6.SRP6CryptoParams;
import com.nimbusds.srp6.SRP6Exception;
import com.nimbusds.srp6.SRP6Routines;
import com.nimbusds.srp6.SRP6ServerEvidenceContext;
import com.nimbusds.srp6.SRP6Session;
import com.nimbusds.srp6.URoutineContext;
import java.math.BigInteger;
/**
* This is a slightly modified version of the SRP6ServerSession class included with nimbus. The only
* change made for homekit compatability is a change to the size of the b key. HomeKit pairing fails
* if b is not 3072 bytes.
*
* Stateful server-side Secure Remote Password (SRP-6a) authentication session. Handles the
* computing and storing of SRP-6a variables between the protocol steps as well as timeouts.
*
*
Usage:
*
*
* - Create a new SRP-6a server session for each client authentication attempt.
*
- If you wish to use custom routines for the server evidence message 'M1' and / or the client
* evidence message 'M2' specify them at this point.
*
- Proceed to {@link #step1 step one} on receiving a valid user identity 'I' from the
* authenticating client. Respond with the server public value 'B' and password salt 's'. If
* the SRP-6a crypto parameters 'N', 'g' and 'H' were not agreed in advance between server and
* client append them to the response.
*
- Proceed to {@link #step2 step two} on receiving the public client value 'A' and evidence
* message 'M1'. If the client credentials are valid signal success and return the server
* evidence message 'M2'. The established session key 'S' may be {@link #getSessionKey
* retrieved} to encrypt further communication with the client. Else signal an authentication
* failure to the client.
*
*
* @author Vladimir Dzhuvinov
*/
public class HomekitSRP6ServerSession extends SRP6Session {
/** Enumerates the states of a server-side SRP-6a authentication session. */
public static enum State {
/**
* The session is initialised and ready to begin authentication, by proceeding to {@link
* #STEP_1}.
*/
INIT,
/**
* The user identity 'I' is received from the client and the server has returned its public
* value 'B' based on the matching password verifier 'v'. The session is ready to proceed to
* {@link #STEP_2}.
*/
STEP_1,
/**
* The client public key 'A' and evidence message 'M1' are received and the server has replied
* with its own evidence message 'M2'. The session is finished (authentication was successful or
* failed).
*/
STEP_2
}
/** Indicates a non-existing use identity and implies mock salt 's' and verifier 'v' values. */
private boolean noSuchUserIdentity = false;
/** The password verifier 'v'. */
private BigInteger v = null;
/** The server private value 'b'. */
private BigInteger b = null;
/** The current SRP-6a auth state. */
private State state;
/**
* Creates a new server-side SRP-6a authentication session and sets its state to {@link
* State#INIT}.
*
* @param config The SRP-6a crypto parameters configuration. Must not be {@code null}.
* @param timeout The SRP-6a authentication session timeout in seconds. If the authenticating
* counterparty (server or client) fails to respond within the specified time the session will
* be closed. If zero timeouts are disabled.
*/
public HomekitSRP6ServerSession(final SRP6CryptoParams config, final int timeout) {
super(timeout);
if (config == null)
throw new IllegalArgumentException("The SRP-6a crypto parameters must not be null");
this.config = config;
digest = config.getMessageDigestInstance();
if (digest == null)
throw new IllegalArgumentException("Unsupported hash algorithm 'H': " + config.H);
state = State.INIT;
updateLastActivityTime();
}
/**
* Creates a new server-side SRP-6a authentication session and sets its state to {@link
* State#INIT}. Session timeouts are disabled.
*
* @param config The SRP-6a crypto parameters configuration. Must not be {@code null}.
*/
public HomekitSRP6ServerSession(final SRP6CryptoParams config) {
this(config, 0);
}
/**
* Increments this SRP-6a authentication session to {@link State#STEP_1}.
*
* Argument origin:
*
*
* - From client: user identity 'I'.
*
- From server database: matching salt 's' and password verifier 'v' values.
*
*
* @param userID The identity 'I' of the authenticating user. Must not be {@code null} or empty.
* @param s The password salt 's'. Must not be {@code null}.
* @param v The password verifier 'v'. Must not be {@code null}.
* @return The server public value 'B'.
* @throws IllegalStateException If the mehod is invoked in a state other than {@link State#INIT}.
*/
public BigInteger step1(final String userID, final BigInteger s, final BigInteger v) {
// Check arguments
if (userID == null || userID.trim().isEmpty())
throw new IllegalArgumentException("The user identity 'I' must not be null or empty");
this.userID = userID;
if (s == null) throw new IllegalArgumentException("The salt 's' must not be null");
this.s = s;
if (v == null) throw new IllegalArgumentException("The verifier 'v' must not be null");
this.v = v;
// Check current state
if (state != State.INIT)
throw new IllegalStateException("State violation: Session must be in INIT state");
// Generate server private and public values
k = SRP6Routines.computeK(digest, config.N, config.g);
digest.reset();
b = HomekitSRP6Routines.generatePrivateValue(config.N, random);
digest.reset();
B = SRP6Routines.computePublicServerValue(config.N, config.g, k, v, b);
state = State.STEP_1;
updateLastActivityTime();
return B;
}
/**
* Increments this SRP-6a authentication session to {@link State#STEP_1} indicating a non-existing
* user identity 'I' with mock (simulated) salt 's' and password verifier 'v' values.
*
* This method can be used to avoid informing the client at step one that the user identity is
* bad and throw instead a guaranteed general "bad credentials" SRP-6a exception at step two.
*
*
Argument origin:
*
*
* - From client: user identity 'I'.
*
- Simulated by server, preferably consistently for the specified identity 'I': salt 's' and
* password verifier 'v' values.
*
*
* @param userID The identity 'I' of the authenticating user. Must not be {@code null} or empty.
* @param s The password salt 's'. Must not be {@code null}.
* @param v The password verifier 'v'. Must not be {@code null}.
* @return The server public value 'B'.
* @throws IllegalStateException If the method is invoked in a state other than {@link
* State#INIT}.
*/
public BigInteger mockStep1(final String userID, final BigInteger s, final BigInteger v) {
noSuchUserIdentity = true;
return step1(userID, s, v);
}
/**
* Increments this SRP-6a authentication session to {@link State#STEP_2}.
*
* Argument origin:
*
*
* - From client: public value 'A' and evidence message 'M1'.
*
*
* @param A The client public value. Must not be {@code null}.
* @param M1 The client evidence message. Must not be {@code null}.
* @return The server evidence message 'M2'.
* @throws SRP6Exception If the session has timed out, the client public value 'A' is invalid or
* the user credentials are invalid.
* @throws IllegalStateException If the method is invoked in a state other than {@link
* State#STEP_1}.
*/
public BigInteger step2(final BigInteger A, final BigInteger M1) throws SRP6Exception {
// Check arguments
if (A == null)
throw new IllegalArgumentException("The client public value 'A' must not be null");
this.A = A;
if (M1 == null)
throw new IllegalArgumentException("The client evidence message 'M1' must not be null");
this.M1 = M1;
// Check current state
if (state != State.STEP_1)
throw new IllegalStateException("State violation: Session must be in STEP_1 state");
// Check timeout
if (hasTimedOut()) throw new SRP6Exception("Session timeout", SRP6Exception.CauseType.TIMEOUT);
// Check A validity
if (!SRP6Routines.isValidPublicValue(config.N, A))
throw new SRP6Exception(
"Bad client public value 'A'", SRP6Exception.CauseType.BAD_PUBLIC_VALUE);
// Check for previous mock step 1
if (noSuchUserIdentity)
throw new SRP6Exception("Bad client credentials", SRP6Exception.CauseType.BAD_CREDENTIALS);
if (hashedKeysRoutine != null) {
URoutineContext hashedKeysContext = new URoutineContext(A, B);
u = hashedKeysRoutine.computeU(config, hashedKeysContext);
} else {
u = SRP6Routines.computeU(digest, config.N, A, B);
digest.reset();
}
S = SRP6Routines.computeSessionKey(config.N, v, u, A, b);
// Compute the own client evidence message 'M1'
BigInteger computedM1;
if (clientEvidenceRoutine != null) {
// With custom routine
SRP6ClientEvidenceContext ctx = new SRP6ClientEvidenceContext(userID, s, A, B, S);
computedM1 = clientEvidenceRoutine.computeClientEvidence(config, ctx);
} else {
// With default routine
computedM1 = SRP6Routines.computeClientEvidence(digest, A, B, S);
digest.reset();
}
if (!computedM1.equals(M1))
throw new SRP6Exception("Bad client credentials", SRP6Exception.CauseType.BAD_CREDENTIALS);
state = State.STEP_2;
if (serverEvidenceRoutine != null) {
// With custom routine
SRP6ServerEvidenceContext ctx = new SRP6ServerEvidenceContext(A, M1, S);
M2 = serverEvidenceRoutine.computeServerEvidence(config, ctx);
}
updateLastActivityTime();
return M2;
}
/**
* Returns the current state of this SRP-6a authentication session.
*
* @return The current state.
*/
public State getState() {
return state;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy