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;
007import java.security.MessageDigest;
008
009import com.nimbusds.srp6.SRP6ClientCredentials;
010import com.nimbusds.srp6.SRP6ClientSession;
011import com.nimbusds.srp6.SRP6ClientSession.State;
012import com.nimbusds.srp6.SRP6CryptoParams;
013import com.nimbusds.srp6.SRP6Exception;
014import com.nimbusds.srp6.SRP6Routines;
015
016/**
017 * If you want to have both Java clients and JavaScript clients authenticate to
018 * the same Java server then this class is a workalike to the JavaScript client
019 * session. This class is a thin wrapper to a Nimbus SRP6ClientSession which is
020 * configured to work with the Thinbus server session.
021 */
022abstract public class SRP6JavaClientSession {
023
024        /**
025         * The crypto parameters for the SRP-6a protocol. These must be agreed
026         * between client and server before authentication and consist of a large
027         * safe prime 'N', a corresponding generator 'g' and a hash function
028         * algorithm 'H'. You can generate your own with openssl using
029         * {@link OpenSSLCryptoConfig}
030         * 
031         */
032        protected final SRP6CryptoParams config;
033
034        /**
035         * The underlying Nimbus session which will be configure for JavaScript
036         * interactions
037         */
038        protected final SRP6ClientSession session;
039
040        /**
041         * Records the identity 'I' and password 'P' of the authenticating user. The
042         * session is incremented to {@link State#STEP_1}.
043         * 
044         * <p>
045         * Argument origin:
046         * 
047         * <ul>
048         * <li>From user: user identity 'I' and password 'P'.
049         * </ul>
050         * 
051         * @param userID
052         *            The identity 'I' of the authenticating user, UTF-8 encoded.
053         *            Must not be {@code null} or empty.
054         * @param password
055         *            The user password 'P', UTF-8 encoded. Must not be {@code null}
056         *            .
057         * 
058         * @throws IllegalStateException
059         *             If the method is invoked in a state other than
060         *             {@link State#INIT}.
061         */
062        public void step1(String userID, String password) {
063                session.step1(userID, password);
064        }
065
066        /**
067         * Receives the password salt 's' and public value 'B' from the server. The
068         * SRP-6a crypto parameters are also set. The session is incremented to
069         * {@link State#STEP_2}.
070         *
071         * <p>
072         * Argument origin:
073         * 
074         * <ul>
075         * <li>From server: password salt 's', public value 'B'.
076         * <li>From server or pre-agreed: crypto parameters prime 'N', generator 'g'
077         * <li>Pre-agreed: crypto parameters prime 'H'
078         * </ul>
079         *
080         * @param s
081         *            The password salt 's'. Must not be {@code null}.
082         * @param B
083         *            The public server value 'B'. Must not be {@code null}.
084         *
085         * @return The client credentials consisting of the client public key 'A'
086         *         and the client evidence message 'M1'.
087         *
088         * @throws IllegalStateException
089         *             If the method is invoked in a state other than
090         *             {@link State#STEP_1}.
091         * @throws SRP6Exception
092         *             If the session has timed out or the public server value 'B'
093         *             is invalid.
094         */
095        public SRP6ClientCredentials step2(String s, String B) throws SRP6Exception {
096                return session.step2(config, fromHex(s), fromHex(B));
097        }
098
099        /**
100         * Receives the server evidence message 'M1'. The session is incremented to
101         * {@link State#STEP_3}.
102         * 
103         * <p>
104         * Argument origin:
105         * 
106         * <ul>
107         * <li>From server: evidence message 'M2'.
108         * </ul>
109         * 
110         * @param M2
111         *            The server evidence message 'M2'. Must not be {@code null}.
112         * 
113         * @throws IllegalStateException
114         *             If the method is invoked in a state other than
115         *             {@link State#STEP_2}.
116         * @throws SRP6Exception
117         *             If the session has timed out or the server evidence message
118         *             'M2' is invalid.
119         */
120        public void step3(String M2) throws SRP6Exception {
121                session.step3(fromHex(M2));
122        }
123
124        /**
125         * Constructs a Java client session compatible with the server session which
126         * words with Java. underlying Nimbus SRP6ClientSession.
127         * 
128         * @param srp6CryptoParams
129         *            cryptographic constants which must match those being used by
130         *            the client.
131         */
132        public SRP6JavaClientSession(SRP6CryptoParams srp6CryptoParams) {
133                this.config = srp6CryptoParams;
134                session = new SRP6ClientSession();
135                session.setHashedKeysRoutine(new HexHashedURoutine());
136                session.setClientEvidenceRoutine(new HexHashedClientEvidenceRoutine());
137                session.setServerEvidenceRoutine(new HexHashedServerEvidenceRoutine());
138                session.setXRoutine(new HexHashedXRoutine());
139        }
140
141        /**
142         * Generates a salt value 's'. The salt s is a public value in the protocol
143         * which is fixed per user and would be stored in the user database. The
144         * desired property is that it is unique for every user in your system. This
145         * can be ensured by adding a uniqueness constraint to a not null salt
146         * column within the database which is strongly recommended. Then it does
147         * not matter whether this public value has been generated using a good
148         * secure random number at the server or using a weaker random number
149         * generator at the browser. You simply reduce the probability of database
150         * constraint exceptions if you use a better random number. The Thinbus
151         * Javascript client session provides a method generateRandomSalt to run at
152         * the browser to create 's' which can be invoked with, or without, passing
153         * a sever generated secure random number or avoided entirely by generating
154         * the salt at the server. This method is the server version which you can
155         * use exclusively else mix with a client generated value.
156         * 
157         * @param numBytes
158         *            Number of random bytes. Recommended is greater than the bit
159         *            length of the chosen hash e.g. HASH_HEX_LENGTH constant of
160         *            server session is x2 hash length so a reasonable choice.
161         * 
162         * @return A hex encoded random salt value.
163         */
164        public String generateRandomSalt(final int numBytes) {
165                byte[] bytes = SRP6Routines.generateRandomSalt(numBytes);
166                MessageDigest digest = config.getMessageDigestInstance();
167                digest.reset();
168                digest.update(bytes, 0, bytes.length);
169                BigInteger bi = new BigInteger(1, digest.digest());
170                return toHex(bi);
171        }
172
173}