001package com.bitbucket.thinbus.srp6.js; 002 003import static com.nimbusds.srp6.BigIntegerUtils.fromHex; 004import static com.nimbusds.srp6.BigIntegerUtils.toHex; 005 006import java.io.Serializable; 007import java.math.BigInteger; 008 009import com.nimbusds.srp6.SRP6CryptoParams; 010import com.nimbusds.srp6.SRP6Exception; 011import com.nimbusds.srp6.SRP6Routines; 012import com.nimbusds.srp6.SRP6ServerSession; 013import com.nimbusds.srp6.SRP6ServerSession.State; 014 015abstract public class SRP6JavascriptServerSession implements Serializable { 016 017 final SRP6Routines srp6Routines = new SRP6Routines(); 018 019 /** 020 * Serializable class version number 021 */ 022 private static final long serialVersionUID = -5998252135527603869L; 023 024 /** 025 * Returns the one-time server challenge `B` encoded as hex. 026 * Increments this SRP-6a authentication session to {@link State#STEP_1}. 027 * 028 * @param username 029 * The identity 'I' of the authenticating user. Must not be 030 * {@code null} or empty. 031 * @param salt 032 * The password salt 's' as hex string with no leading zeros. Must not be {@code null}. 033 * @param v 034 * The password verifier 'v' as hex string with no leading zeros. Must not be {@code null}. 035 * 036 * @return The server public value 'B' as hex encoded number. 037 * 038 * @throws IllegalStateException 039 * If the mehod is invoked in a state other than 040 * {@link State#INIT}. 041 */ 042 public String step1(final String username, final String salt, final String v) { 043 BigInteger B = session.step1(username, fromHex(salt), fromHex(v)); 044 return toHex(B); 045 } 046 047 /** 048 * Validates a password proof `M1` based on the client one-tiem public key `A`. 049 * Increments this SRP-6a authentication session to {@link State#STEP_2}. 050 * 051 * @param A 052 * The client public value. Must not be {@code null}. 053 * @param M1 054 * The client evidence message. Must not be {@code null}. 055 * 056 * @return The server evidence message 'M2' has hex encoded number with 057 * leading zero padding to match the 256bit hash length. 058 * 059 * @throws SRP6Exception 060 * If the client public value 'A' is invalid or the user 061 * credentials are invalid. 062 * 063 * @throws IllegalStateException 064 * If the mehod is invoked in a state other than 065 * {@link State#STEP_1}. 066 */ 067 public String step2(final String A, final String M1) throws Exception { 068 BigInteger M2 = session.step2(fromHex(A), fromHex(M1)); 069 String M2str = toHex(M2); 070 M2str = HexHashedRoutines.leadingZerosPad(M2str, HASH_HEX_LENGTH); 071 return M2str; 072 } 073 074 /** 075 * Returns the underlying session state as a String for JavaScript testing. 076 * 077 * @return The current state. 078 */ 079 public String getState() { 080 return session.getState().name(); 081 } 082 083 /** 084 * Gets the identity 'I' of the authenticating user. 085 * 086 * @return The user identity 'I', null if undefined. 087 */ 088 public String getUserID() { 089 return session.getUserID(); 090 } 091 092 /** 093 * The crypto parameters for the SRP-6a protocol. These must be agreed 094 * between client and server before authentication and consist of a large 095 * safe prime 'N', a corresponding generator 'g' and a hash function 096 * algorithm 'H'. You can generate your own with openssl using 097 * {@link OpenSSLCryptoConfigConverter} 098 * 099 */ 100 protected final SRP6CryptoParams config; 101 102 /** 103 * The underlying Nimbus session which will be configure for JavaScript 104 * interactions 105 */ 106 protected final SRP6ServerSession session; 107 108 /** 109 * Constructs a JavaScript compatible server session which configures an 110 * underlying Nimbus SRP6ServerSession. 111 * 112 * @param srp6CryptoParams 113 * cryptographic constants which must match those being used by 114 * the client. 115 */ 116 public SRP6JavascriptServerSession(SRP6CryptoParams srp6CryptoParams) { 117 this.config = srp6CryptoParams; 118 session = new SRP6ServerSession(config); 119 session.setHashedKeysRoutine(new HexHashedURoutine()); 120 session.setClientEvidenceRoutine(new HexHashedClientEvidenceRoutine()); 121 session.setServerEvidenceRoutine(new HexHashedServerEvidenceRoutine()); 122 } 123 124 /** 125 * k is actually fixed and done with hash padding routine which uses 126 * java.net.BigInteger byte array constructor so this is a convenience 127 * method to get at the Java generated value to use in the configuration of 128 * the Javascript 129 * 130 * @return 'k' calculated as H( N, g ) 131 */ 132 public String k() { 133 return toHex(this.srp6Routines.computeK(config.getMessageDigestInstance(), config.N, config.g)); 134 } 135 136 /** 137 * Turn a radix10 string into a java.net.BigInteger 138 * 139 * @param base10 140 * the radix10 string 141 * @return the BigInteger representation of the number 142 */ 143 public static BigInteger fromDecimal(String base10) { 144 return new BigInteger(base10, 10); 145 } 146 147 /** 148 * This must match the expected character length of the specified algorithm 149 */ 150 public static int HASH_HEX_LENGTH; 151 152 /** 153 * Outputs the configuration in the way which can be used to configure 154 * JavaScript. 155 * 156 * Note that 'k' is fixed but uses the byte array constructor of BigInteger 157 * which is not available in JavaScript to you must set it as configuration. 158 * 159 * @return Parameters required by JavaScript client. 160 */ 161 @Override 162 public String toString() { 163 StringBuilder builder = new StringBuilder(); 164 builder.append(String.format("g: %s\n", config.g.toString(10))); 165 builder.append(String.format("N: %s\n", config.N.toString(10))); 166 builder.append(String.format("k: %s\n", k())); 167 return builder.toString(); 168 } 169 170 /** 171 * Gets the password salt 's'. 172 * 173 * @deprecated This value is returned by step1 having a getter means holding onto more memory see issue #4 at https://bitbucket.org/simon_massey/thinbus-srp-js/issues/4 174 * 175 * @return The salt 's' if available, else {@code null}. 176 */ 177 @Deprecated 178 public String getSalt() { 179 return toHex(session.getSalt()); 180 } 181 182 /** 183 * Gets the public server value 'B'. 184 * 185 * @deprecated This value is returned by step1 having a getter means holding onto more memory see issue #4 at https://bitbucket.org/simon_massey/thinbus-srp-js/issues/4 186 * 187 * @return The public server value 'B' if available, else {@code null}. 188 */ 189 @Deprecated 190 public String getPublicServerValue() { 191 return toHex(session.getPublicServerValue()); 192 } 193 194 /** 195 * Gets the server evidence message 'M2'. 196 * 197 * @deprecated This value is returned by step2 having a getter means holding onto more memory see issue #4 at https://bitbucket.org/simon_massey/thinbus-srp-js/issues/4 198 * 199 * @return The server evidence message 'M2' if available, else {@code null}. 200 */ 201 @Deprecated 202 public String getServerEvidenceMessage() { 203 return toHex(session.getServerEvidenceMessage()); 204 } 205 206 /** 207 * Gets the shared session key 'S' or its hash H(S). 208 * 209 * @param doHash 210 * If {@code true} the hash H(S) of the session key will be 211 * returned instead of the raw value. 212 * 213 * @return The shared session key 'S' or its hash H(S). {@code null} will be 214 * returned if authentication failed or the method is invoked in a 215 * session state when the session key 'S' has not been computed yet. 216 */ 217 public String getSessionKey(boolean doHash) { 218 String S = toHex(session.getSessionKey()); 219 if (doHash) { 220 String K = HexHashedRoutines.toHexString(this.config 221 .getMessageDigestInstance().digest( 222 S.getBytes(HexHashedRoutines.utf8))); 223 return K; 224 } else { 225 return S; 226 } 227 } 228}