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}