001package com.nimbusds.srp6; 002 003 004import java.math.BigInteger; 005import java.security.MessageDigest; 006import java.security.SecureRandom; 007import java.util.HashMap; 008import java.util.Map; 009 010 011/** 012 * The base abstract class for client and server-side Secure Remote Password 013 * (SRP-6a) authentication sessions. 014 * 015 * @author Vladimir Dzhuvinov 016 * @author John Kim 017 */ 018public abstract class SRP6Session { 019 020 021 /** 022 * The crypto configuration. 023 */ 024 protected SRP6CryptoParams config; 025 026 027 /** 028 * Message digest (not thread-safe). 029 */ 030 protected MessageDigest digest; 031 032 033 /** 034 * Source of randomness. 035 */ 036 protected final SecureRandom random = new SecureRandom(); 037 038 039 /** 040 * The SRP-6a authentication session timeout in seconds. If the 041 * authenticating counterparty (server or client) fails to respond 042 * within the specified time the session will be closed. Zero implies 043 * no timeout. 044 */ 045 protected final int timeout; 046 047 048 /** 049 * The last activity timestamp, from System.currentTimeMillis(). 050 */ 051 protected long lastActivity; 052 053 054 /** 055 * The identity 'I' of the authenticating user. 056 */ 057 protected String userID = null; 058 059 060 /** 061 * The password salt 's'. 062 */ 063 protected BigInteger s = null; 064 065 066 /** 067 * The client public value 'A'. 068 */ 069 protected BigInteger A = null; 070 071 072 /** 073 * The server public value 'B'. 074 */ 075 protected BigInteger B = null; 076 077 078 /** 079 * The random scrambling parameter 'u'. 080 */ 081 protected BigInteger u = null; 082 083 084 /** 085 * The multiplier 'k'. 086 */ 087 protected BigInteger k = null; 088 089 090 /** 091 * The shared session key 'S'. 092 */ 093 protected BigInteger S = null; 094 095 096 /** 097 * The client evidence message 'M1'. 098 */ 099 protected BigInteger M1 = null; 100 101 102 /** 103 * The server evidence message 'M2'. 104 */ 105 protected BigInteger M2 = null; 106 107 108 /** 109 * Custom routine for the client evidence message 'M1' computation. 110 */ 111 protected ClientEvidenceRoutine clientEvidenceRoutine = null; 112 113 114 /** 115 * Custom routine for the server evidence message 'M2' computation. 116 */ 117 protected ServerEvidenceRoutine serverEvidenceRoutine = null; 118 119 120 /** 121 * Custom routine for the hashed keys 'u' computation. 122 */ 123 protected URoutine hashedKeysRoutine = null; 124 125 126 /** 127 * Optional storage of arbitrary session attributes. 128 */ 129 private Map<String,Object> attributes = null; 130 131 132 /** 133 * Creates a new SRP-6a authentication session. 134 * 135 * @param timeout The SRP-6a authentication session timeout in seconds. 136 * If the authenticating counterparty (server or client) 137 * fails to respond within the specified time the 138 * session will be closed. If zero timeouts are 139 * disabled. 140 */ 141 public SRP6Session(final int timeout) { 142 143 if (timeout < 0) 144 throw new IllegalArgumentException("The timeout must be zero (no timeout) or greater"); 145 146 this.timeout = timeout; 147 } 148 149 150 /** 151 * Creates a new SRP-6a authentication session, session timeouts are 152 * disabled. 153 */ 154 public SRP6Session() { 155 156 this(0); 157 } 158 159 160 /** 161 * Updates the last activity timestamp. 162 */ 163 protected void updateLastActivityTime() { 164 165 lastActivity = System.currentTimeMillis(); 166 } 167 168 169 /** 170 * Gets the last session activity timestamp, in milliseconds since 171 * midnight, January 1, 1970 UTC (see System.currentTimeMillis()). 172 * 173 * @return The last activity timestamp. 174 */ 175 public long getLastActivityTime() { 176 177 return lastActivity; 178 } 179 180 181 /** 182 * Returns {@code true} if the session has timed out, based on the 183 * timeout configuration and the last activity timestamp. 184 * 185 * @return {@code true} if the session has timed out, else 186 * {@code false}. 187 */ 188 public boolean hasTimedOut() { 189 190 if (timeout == 0) 191 return false; 192 193 final long now = System.currentTimeMillis(); 194 195 return now > lastActivity + (timeout * 1000); 196 } 197 198 199 /** 200 * Gets the SRP-6a crypto parameters for this session. 201 * 202 * @return The SRP-6a crypto parameters, {@code null} if undefined. 203 */ 204 public SRP6CryptoParams getCryptoParams() { 205 206 return config; 207 } 208 209 210 /** 211 * Gets the identity 'I' of the authenticating user. 212 * 213 * @return The user identity 'I', {@code null} if undefined. 214 */ 215 public String getUserID() { 216 217 return userID; 218 } 219 220 221 /** 222 * Gets the SRP-6a authentication session timeout. 223 * 224 * @return The SRP-6a authentication session timeout, in seconds. Zero 225 * implies to timeout. 226 */ 227 public int getTimeout() { 228 229 return timeout; 230 } 231 232 233 /** 234 * Sets a custom routine to compute the client evidence message 'M1'. 235 * Note that the custom routine must be set prior to 236 * {@link SRP6ClientSession.State#STEP_2} or 237 * {@link SRP6ServerSession.State#STEP_2}. 238 * 239 * @param routine The client evidence message 'M1' routine or 240 * {@code null} to use the default 241 * {@link SRP6Routines#computeClientEvidence}. 242 */ 243 public void setClientEvidenceRoutine(final ClientEvidenceRoutine routine) { 244 245 clientEvidenceRoutine = routine; 246 } 247 248 249 /** 250 * Gets the custom routine to compute the client evidence message 'M1'. 251 * 252 * @return The routine instance or {@code null} if the default 253 * {@link SRP6Routines#computeClientEvidence} is used. 254 */ 255 public ClientEvidenceRoutine getClientEvidenceRoutine() { 256 257 return clientEvidenceRoutine; 258 } 259 260 261 /** 262 * Sets a custom routine to compute the server evidence message 'M2'. 263 * Note that the custom routine must be set prior to 264 * {@link SRP6ClientSession.State#STEP_3} or 265 * {@link SRP6ServerSession.State#STEP_2}. 266 * 267 * @param routine The server evidence message 'M2' routine or 268 * {@code null} to use the default 269 * {@link SRP6Routines#computeServerEvidence}. 270 */ 271 public void setServerEvidenceRoutine(final ServerEvidenceRoutine routine) { 272 273 serverEvidenceRoutine = routine; 274 } 275 276 277 /** 278 * Gets the custom routine to compute the server evidence message 'M2'. 279 * 280 * @return The routine instance or {@code null} if the default 281 * {@link SRP6Routines#computeServerEvidence} is used. 282 */ 283 public ServerEvidenceRoutine getServerEvidenceRoutine() { 284 285 return serverEvidenceRoutine; 286 } 287 288 289 /** 290 * Gets the custom routine to compute hashed keys 'u' a 'H(A | B)'. 291 * 292 * @return The routine instance or {@code null} if the default 293 * {@link SRP6Routines#computeU} is to be used. 294 */ 295 public URoutine getHashedKeysRoutine() { 296 297 return hashedKeysRoutine; 298 } 299 300 301 /** 302 * Sets a custom routine to compute hashed keys 'u' a 'H(A | B)'. Note 303 * that the custom routine must be set prior to 304 * {@link SRP6ServerSession.State#STEP_2}. 305 * 306 * @param hashedKeysRoutine The hashed keys 'u' routine or {@code null} 307 * to use the default 308 * {@link SRP6Routines#computeU}. 309 */ 310 public void setHashedKeysRoutine(final URoutine hashedKeysRoutine) { 311 312 this.hashedKeysRoutine = hashedKeysRoutine; 313 } 314 315 316 /** 317 * Gets the password salt 's'. 318 * 319 * @return The salt 's' if available, else {@code null}. 320 */ 321 public BigInteger getSalt() { 322 323 return s; 324 } 325 326 327 /** 328 * Gets the public client value 'A'. 329 * 330 * @return The public client value 'A' if available, else {@code null}. 331 */ 332 public BigInteger getPublicClientValue() { 333 334 return A; 335 } 336 337 338 /** 339 * Gets the public server value 'B'. 340 * 341 * @return The public server value 'B' if available, else {@code null}. 342 */ 343 public BigInteger getPublicServerValue() { 344 345 return B; 346 } 347 348 349 /** 350 * Gets the client evidence message 'M1'. 351 * 352 * @return The client evidence message 'M1' if available, else 353 * {@code null}. 354 */ 355 public BigInteger getClientEvidenceMessage() { 356 357 return M1; 358 } 359 360 361 /** 362 * Gets the server evidence message 'M2'. 363 * 364 * @return The server evidence message 'M2' if available, else 365 * {@code null}. 366 */ 367 public BigInteger getServerEvidenceMessage() { 368 369 return M2; 370 } 371 372 373 /** 374 * Gets the shared session key 'S' or its hash H(S). 375 * 376 * @param doHash If {@code true} the hash H(S) of the session key will 377 * be returned instead of the raw value. 378 * 379 * @return The shared session key 'S' or its hash H(S). {@code null} 380 * will be returned if authentication failed or the method is 381 * invoked in a session state when the session key 'S' has not 382 * been computed yet. 383 */ 384 public BigInteger getSessionKey(final boolean doHash) { 385 386 if (S == null) 387 return null; 388 389 if (doHash) { 390 digest.reset(); 391 return new BigInteger(digest.digest(S.toByteArray())); 392 } 393 else { 394 return S; 395 } 396 } 397 398 399 /** 400 * Sets a session attribute. This method can be used to store arbitrary 401 * objects with this session and retrieve them later with 402 * {@link #getAttribute}. 403 * 404 * @param key The attribute key. Must not be {@code null}. 405 * @param value The attribute value. May be {@code null}. 406 */ 407 public void setAttribute(final String key, final Object value) { 408 409 if (key == null) 410 throw new IllegalArgumentException("The attribute key must not be null"); 411 412 // create new attribute map on demand 413 if (attributes == null) 414 attributes = new HashMap<>(); 415 416 attributes.put(key, value); 417 } 418 419 420 /** 421 * Gets a session attribute. This method can be used to retrieve 422 * arbitrary objects stored with this session with 423 * {@link #setAttribute}. 424 * 425 * @param key The attribute key. Must not be {@code null}. 426 * 427 * @return The attribute value, {@code null} if none was found by the 428 * specified key or its value is {@code null}. 429 */ 430 public Object getAttribute(final String key) { 431 432 if (key == null) 433 throw new IllegalArgumentException("The attribute key must not be null"); 434 435 if (attributes == null) 436 return null; 437 438 return attributes.get(key); 439 } 440}