All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.hapjava.server.impl.pairing.HomekitSRP6ServerSession Maven / Gradle / Ivy

There is a newer version: 2.0.7
Show newest version
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