forked from hap-java/HAP-Java
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathHomekitSRP6ServerSession.java
299 lines (236 loc) · 10.1 KB
/
HomekitSRP6ServerSession.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
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;
import java.security.MessageDigest;
/**
* 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.
*
* <p>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.
*
* <p>Usage:
*
* <ul>
* <li>Create a new SRP-6a server session for each client authentication attempt.
* <li>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.
* <li>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.
* <li>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.
* </ul>
*
* @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;
private MessageDigest digest;
/**
* 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;
this.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}.
*
* <p>Argument origin:
*
* <ul>
* <li>From client: user identity 'I'.
* <li>From server database: matching salt 's' and password verifier 'v' values.
* </ul>
*
* @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 = new SRP6Routines().computeK(digest, config.N, config.g);
digest.reset();
b = HomekitSRP6Routines.generatePrivateValue(config.N, random);
digest.reset();
B = new 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.
*
* <p>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.
*
* <p>Argument origin:
*
* <ul>
* <li>From client: user identity 'I'.
* <li>Simulated by server, preferably consistently for the specified identity 'I': salt 's' and
* password verifier 'v' values.
* </ul>
*
* @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}.
*
* <p>Argument origin:
*
* <ul>
* <li>From client: public value 'A' and evidence message 'M1'.
* </ul>
*
* @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 (!new 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 = new SRP6Routines().computeU(digest, config.N, A, B);
digest.reset();
}
S = new 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 = new 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;
}
}