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