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