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}