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