001package com.bitbucket.thinbus.srp6.js; 002 003import static com.nimbusds.srp6.BigIntegerUtils.fromHex; 004import static com.nimbusds.srp6.BigIntegerUtils.toHex; 005 006import java.math.BigInteger; 007import java.security.MessageDigest; 008 009import com.nimbusds.srp6.SRP6ClientCredentials; 010import com.nimbusds.srp6.SRP6ClientSession; 011import com.nimbusds.srp6.SRP6ClientSession.State; 012import com.nimbusds.srp6.SRP6CryptoParams; 013import com.nimbusds.srp6.SRP6Exception; 014import com.nimbusds.srp6.SRP6Routines; 015 016/** 017 * If you want to have both Java clients and JavaScript clients authenticate to 018 * the same Java server then this class is a workalike to the JavaScript client 019 * session. This class is a thin wrapper to a Nimbus SRP6ClientSession which is 020 * configured to work with the Thinbus server session. 021 */ 022abstract public class SRP6JavaClientSession { 023 024 /** 025 * The crypto parameters for the SRP-6a protocol. These must be agreed 026 * between client and server before authentication and consist of a large 027 * safe prime 'N', a corresponding generator 'g' and a hash function 028 * algorithm 'H'. You can generate your own with openssl using 029 * {@link OpenSSLCryptoConfig} 030 * 031 */ 032 protected final SRP6CryptoParams config; 033 034 /** 035 * The underlying Nimbus session which will be configure for JavaScript 036 * interactions 037 */ 038 protected final SRP6ClientSession session; 039 040 /** 041 * Records the identity 'I' and password 'P' of the authenticating user. The 042 * session is incremented to {@link State#STEP_1}. 043 * 044 * <p> 045 * Argument origin: 046 * 047 * <ul> 048 * <li>From user: user identity 'I' and password 'P'. 049 * </ul> 050 * 051 * @param userID 052 * The identity 'I' of the authenticating user, UTF-8 encoded. 053 * Must not be {@code null} or empty. 054 * @param password 055 * The user password 'P', UTF-8 encoded. Must not be {@code null} 056 * . 057 * 058 * @throws IllegalStateException 059 * If the method is invoked in a state other than 060 * {@link State#INIT}. 061 */ 062 public void step1(String userID, String password) { 063 session.step1(userID, password); 064 } 065 066 /** 067 * Receives the password salt 's' and public value 'B' from the server. The 068 * SRP-6a crypto parameters are also set. The session is incremented to 069 * {@link State#STEP_2}. 070 * 071 * <p> 072 * Argument origin: 073 * 074 * <ul> 075 * <li>From server: password salt 's', public value 'B'. 076 * <li>From server or pre-agreed: crypto parameters prime 'N', generator 'g' 077 * <li>Pre-agreed: crypto parameters prime 'H' 078 * </ul> 079 * 080 * @param s 081 * The password salt 's'. Must not be {@code null}. 082 * @param B 083 * The public server value 'B'. Must not be {@code null}. 084 * 085 * @return The client credentials consisting of the client public key 'A' 086 * and the client evidence message 'M1'. 087 * 088 * @throws IllegalStateException 089 * If the method is invoked in a state other than 090 * {@link State#STEP_1}. 091 * @throws SRP6Exception 092 * If the session has timed out or the public server value 'B' 093 * is invalid. 094 */ 095 public SRP6ClientCredentials step2(String s, String B) throws SRP6Exception { 096 return session.step2(config, fromHex(s), fromHex(B)); 097 } 098 099 /** 100 * Receives the server evidence message 'M1'. The session is incremented to 101 * {@link State#STEP_3}. 102 * 103 * <p> 104 * Argument origin: 105 * 106 * <ul> 107 * <li>From server: evidence message 'M2'. 108 * </ul> 109 * 110 * @param M2 111 * The server evidence message 'M2'. Must not be {@code null}. 112 * 113 * @throws IllegalStateException 114 * If the method is invoked in a state other than 115 * {@link State#STEP_2}. 116 * @throws SRP6Exception 117 * If the session has timed out or the server evidence message 118 * 'M2' is invalid. 119 */ 120 public void step3(String M2) throws SRP6Exception { 121 session.step3(fromHex(M2)); 122 } 123 124 /** 125 * Constructs a Java client session compatible with the server session which 126 * words with Java. underlying Nimbus SRP6ClientSession. 127 * 128 * @param srp6CryptoParams 129 * cryptographic constants which must match those being used by 130 * the client. 131 */ 132 public SRP6JavaClientSession(SRP6CryptoParams srp6CryptoParams) { 133 this.config = srp6CryptoParams; 134 session = new SRP6ClientSession(); 135 session.setHashedKeysRoutine(new HexHashedURoutine()); 136 session.setClientEvidenceRoutine(new HexHashedClientEvidenceRoutine()); 137 session.setServerEvidenceRoutine(new HexHashedServerEvidenceRoutine()); 138 session.setXRoutine(new HexHashedXRoutine()); 139 } 140 141 /** 142 * Generates a salt value 's'. The salt s is a public value in the protocol 143 * which is fixed per user and would be stored in the user database. The 144 * desired property is that it is unique for every user in your system. This 145 * can be ensured by adding a uniqueness constraint to a not null salt 146 * column within the database which is strongly recommended. Then it does 147 * not matter whether this public value has been generated using a good 148 * secure random number at the server or using a weaker random number 149 * generator at the browser. You simply reduce the probability of database 150 * constraint exceptions if you use a better random number. The Thinbus 151 * Javascript client session provides a method generateRandomSalt to run at 152 * the browser to create 's' which can be invoked with, or without, passing 153 * a sever generated secure random number or avoided entirely by generating 154 * the salt at the server. This method is the server version which you can 155 * use exclusively else mix with a client generated value. 156 * 157 * @param numBytes 158 * Number of random bytes. Recommended is greater than the bit 159 * length of the chosen hash e.g. HASH_HEX_LENGTH constant of 160 * server session is x2 hash length so a reasonable choice. 161 * 162 * @return A hex encoded random salt value. 163 */ 164 public String generateRandomSalt(final int numBytes) { 165 byte[] bytes = SRP6Routines.generateRandomSalt(numBytes); 166 MessageDigest digest = config.getMessageDigestInstance(); 167 digest.reset(); 168 digest.update(bytes, 0, bytes.length); 169 BigInteger bi = new BigInteger(1, digest.digest()); 170 return toHex(bi); 171 } 172 173 /** 174 * Gets the identity 'I' of the authenticating user. 175 * 176 * @return The user identity 'I', {@code null} if undefined. 177 */ 178 public String getUserID() { 179 return session.getUserID(); 180 } 181 182 /** 183 * Gets the password salt 's'. 184 * 185 * @return The salt 's' if available, else {@code null}. 186 */ 187 public String getSalt() { 188 return toHex(session.getSalt()); 189 } 190 191 /** 192 * Gets the public client value 'A'. 193 * 194 * @return The public client value 'A' if available, else {@code null}. 195 */ 196 public String getPublicClientValue() { 197 return toHex(session.getPublicClientValue()); 198 } 199 200 /** 201 * Gets the client evidence message 'M1'. 202 * 203 * @return The client evidence message 'M1' if available, else {@code null}. 204 */ 205 public String getClientEvidenceMessage() { 206 return toHex(session.getClientEvidenceMessage()); 207 } 208 209 /** 210 * Returns the current state of this SRP-6a authentication session. 211 * 212 * @return The current state. 213 */ 214 public State getState() { 215 return session.getState(); 216 } 217 218 /** 219 * Gets the shared session key 'S' or its hash H(S). 220 * 221 * @param doHash 222 * If {@code true} the hash H(S) of the session key will be 223 * returned instead of the raw value. 224 * 225 * @return The shared session key 'S' or its hash H(S). {@code null} will be 226 * returned if authentication failed or the method is invoked in a 227 * session state when the session key 'S' has not been computed yet. 228 */ 229 public String getSessionKey(boolean doHash) { 230 return toHex(session.getSessionKey(doHash)); 231 } 232}