001package com.bitbucket.thinbus.srp6.js;
002
003import static com.nimbusds.srp6.BigIntegerUtils.fromHex;
004import static com.nimbusds.srp6.BigIntegerUtils.toHex;
005
006import java.math.BigInteger;
007
008import com.nimbusds.srp6.SRP6CryptoParams;
009import com.nimbusds.srp6.SRP6Exception;
010import com.nimbusds.srp6.SRP6Routines;
011import com.nimbusds.srp6.SRP6ServerSession;
012import com.nimbusds.srp6.SRP6ServerSession.State;
013
014abstract public class SRP6JavascriptServerSession {
015
016        /**
017         * Increments this SRP-6a authentication session to {@link State#STEP_1}.
018         * 
019         * @param username
020         *            The identity 'I' of the authenticating user. Must not be
021         *            {@code null} or empty.
022         * @param salt
023         *            The password salt 's'. Must not be {@code null}.
024         * @param v
025         *            The password verifier 'v'. Must not be {@code null}.
026         * 
027         * @return The server public value 'B' as hex encoded number.
028         * 
029         * @throws IllegalStateException
030         *             If the mehod is invoked in a state other than
031         *             {@link State#INIT}.
032         */
033        public String step1(final String username, final String salt, final String v) {
034                BigInteger B = session.step1(username, fromHex(salt), fromHex(v));
035                return toHex(B);
036        }
037
038        /**
039         * Increments this SRP-6a authentication session to {@link State#STEP_2}.
040         * 
041         * @param A
042         *            The client public value. Must not be {@code null}.
043         * @param M1
044         *            The client evidence message. Must not be {@code null}.
045         * 
046         * @return The server evidence message 'M2' has hex encoded number with
047         *         leading zero padding to match the 256bit hash length.
048         * 
049         * @throws SRP6Exception
050         *             If the client public value 'A' is invalid or the user
051         *             credentials are invalid.
052         * 
053         * @throws IllegalStateException
054         *             If the mehod is invoked in a state other than
055         *             {@link State#STEP_1}.
056         */
057        public String step2(final String A, final String M1) throws Exception {
058                BigInteger M2 = session.step2(fromHex(A), fromHex(M1));
059                String M2str = toHex(M2);
060                M2str = HexHashedRoutines.leadingZerosPad(M2str, HASH_HEX_LENGTH);
061                return M2str;
062        }
063
064        /**
065         * Returns the underlying session state as a String for JavaScript testing.
066         * 
067         * @return The current state.
068         */
069        public String getState() {
070                return session.getState().name();
071        }
072
073        /**
074         * Gets the identity 'I' of the authenticating user.
075         *
076         * @return The user identity 'I', null if undefined.
077         */
078        public String getUserID() {
079                return session.getUserID();
080        }
081
082        /**
083         * The crypto parameters for the SRP-6a protocol. These must be agreed
084         * between client and server before authentication and consist of a large
085         * safe prime 'N', a corresponding generator 'g' and a hash function
086         * algorithm 'H'. You can generate your own with openssl using
087         * {@link OpenSSLCryptoConfig}
088         * 
089         */
090        protected final SRP6CryptoParams config;
091
092        /**
093         * The underlying Nimbus session which will be configure for JavaScript
094         * interactions
095         */
096        protected final SRP6ServerSession session;
097        
098        /**
099         * Constructs a JavaScript compatible server session which configures an
100         * underlying Nimbus SRP6ServerSession.
101         * 
102         * @param srp6CryptoParams
103         *            cryptographic constants which must match those being used by
104         *            the client.
105         */
106        public SRP6JavascriptServerSession(SRP6CryptoParams srp6CryptoParams) {
107                this.config = srp6CryptoParams;
108                session = new SRP6ServerSession(config);
109                session.setHashedKeysRoutine(new HexHashedURoutine());
110                session.setClientEvidenceRoutine(new HexHashedClientEvidenceRoutine());
111                session.setServerEvidenceRoutine(new HexHashedServerEvidenceRoutine());
112        }
113
114        /**
115         * k is actually fixed and done with hash padding routine which uses
116         * java.net.BigInteger byte array constructor so this is a convenience
117         * method to get at the Java generated value to use in the configuration of
118         * the Javascript
119         * 
120         * @return 'k' calculated as H( N, g )
121         */
122        public String k() {
123                return toHex(SRP6Routines.computeK(config.getMessageDigestInstance(), config.N, config.g));
124        }
125
126        /**
127         * Turn a radix10 string into a java.net.BigInteger
128         * 
129         * @param base10
130         *            the radix10 string
131         * @return the BigInteger representation of the number
132         */
133        public static BigInteger fromDecimal(String base10) {
134                return new BigInteger(base10, 10);
135        }
136
137        /**
138         * This must match the expected character length of the specified algorithm
139         */
140        public static int HASH_HEX_LENGTH;
141
142        /**
143         * Outputs the configuration in the way which can be used to configure
144         * JavaScript.
145         * 
146         * Note that 'k' is fixed but uses the byte array constructor of BigInteger
147         * which is not available in JavaScript to you must set it as configuration.
148         * 
149         * @return Parameters required by JavaScript client.
150         */
151        @Override
152        public String toString() {
153                StringBuilder builder = new StringBuilder();
154                builder.append(String.format("g: %s\n", config.g.toString(10)));
155                builder.append(String.format("N: %s\n", config.N.toString(10)));
156                builder.append(String.format("k: %s\n", k()));
157                return builder.toString();
158        }
159}