diff --git a/docs-core/pom.xml b/docs-core/pom.xml
index ed0673dc..eeb23ba9 100644
--- a/docs-core/pom.xml
+++ b/docs-core/pom.xml
@@ -77,7 +77,6 @@
jbcrypt
-
org.apache.lucene
lucene-core
diff --git a/docs-core/src/main/java/com/sismics/util/totp/GoogleAuthenticator.java b/docs-core/src/main/java/com/sismics/util/totp/GoogleAuthenticator.java
new file mode 100644
index 00000000..c37147f9
--- /dev/null
+++ b/docs-core/src/main/java/com/sismics/util/totp/GoogleAuthenticator.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright (c) 2014-2016 Enrico M. Crisostomo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sismics.util.totp;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.commons.codec.binary.Base32;
+import org.apache.commons.codec.binary.Base64;
+
+/**
+ * This class implements the functionality described in RFC 6238 (TOTP: Time
+ * based one-time password algorithm) and has been tested again Google's
+ * implementation of such algorithm in its Google Authenticator application.
+ *
+ * This class lets users create a new 16-bit base32-encoded secret key with the
+ * validation code calculated at {@code time = 0} (the UNIX epoch) and the URL
+ * of a Google-provided QR barcode to let an user load the generated information
+ * into Google Authenticator.
+ *
+ * The random number generator used by this class uses the default algorithm and
+ * provider. Users can override them by setting the following system properties
+ * to the algorithm and provider name of their choice:
+ *
+ * - {@link #RNG_ALGORITHM}.
+ * - {@link #RNG_ALGORITHM_PROVIDER}.
+ *
+ *
+ * This class does not store in any way either the generated keys nor the keys
+ * passed during the authorization process.
+ *
+ * Java Server side class for Google Authenticator's TOTP generator was inspired
+ * by an author's blog post.
+ *
+ * @author Enrico M. Crisostomo
+ * @author Warren Strange
+ * @version 0.5.0
+ * @see
+ * @see
+ * @see
+ * @since 0.3.0
+ */
+public final class GoogleAuthenticator {
+ /**
+ * The system property to specify the random number generator algorithm to
+ * use.
+ *
+ * @since 0.5.0
+ */
+ public static final String RNG_ALGORITHM = "com.warrenstrange.googleauth.rng.algorithm";
+
+ /**
+ * The system property to specify the random number generator provider to
+ * use.
+ *
+ * @since 0.5.0
+ */
+ public static final String RNG_ALGORITHM_PROVIDER = "com.warrenstrange.googleauth.rng.algorithmProvider";
+
+ /**
+ * The number of bits of a secret key in binary form. Since the Base32
+ * encoding with 8 bit characters introduces an 160% overhead, we just need
+ * 80 bits (10 bytes) to generate a 16 bytes Base32-encoded secret key.
+ */
+ private static final int SECRET_BITS = 80;
+
+ /**
+ * Number of scratch codes to generate during the key generation. We are
+ * using Google's default of providing 5 scratch codes.
+ */
+ private static final int SCRATCH_CODES = 5;
+
+ /**
+ * Number of digits of a scratch code represented as a decimal integer.
+ */
+ private static final int SCRATCH_CODE_LENGTH = 8;
+
+ /**
+ * Modulus used to truncate the scratch code.
+ */
+ public static final int SCRATCH_CODE_MODULUS = (int) Math.pow(10, SCRATCH_CODE_LENGTH);
+
+ /**
+ * Magic number representing an invalid scratch code.
+ */
+ private static final int SCRATCH_CODE_INVALID = -1;
+
+ /**
+ * Length in bytes of each scratch code. We're using Google's default of
+ * using 4 bytes per scratch code.
+ */
+ private static final int BYTES_PER_SCRATCH_CODE = 4;
+
+ /**
+ * The default SecureRandom algorithm to use if none is specified.
+ *
+ * @see java.security.SecureRandom#getInstance(String)
+ * @since 0.5.0
+ */
+ private static final String DEFAULT_RANDOM_NUMBER_ALGORITHM = "SHA1PRNG";
+
+ /**
+ * The default random number algorithm provider to use if none is specified.
+ *
+ * @see java.security.SecureRandom#getInstance(String)
+ * @since 0.5.0
+ */
+ private static final String DEFAULT_RANDOM_NUMBER_ALGORITHM_PROVIDER = "SUN";
+
+ /**
+ * Cryptographic hash function used to calculate the HMAC (Hash-based
+ * Message Authentication Code). This implementation uses the SHA1 hash
+ * function.
+ */
+ private static final String HMAC_HASH_FUNCTION = "HmacSHA1";
+
+ /**
+ * The configuration used by the current instance.
+ */
+ private final GoogleAuthenticatorConfig config;
+
+ /**
+ * The internal SecureRandom instance used by this class. Since Java 7
+ * {@link Random} instances are required to be thread-safe, no
+ * synchronisation is required in the methods of this class using this
+ * instance. Thread-safety of this class was a de-facto standard in previous
+ * versions of Java so that it is expected to work correctly in previous
+ * versions of the Java platform as well.
+ */
+ private ReseedingSecureRandom secureRandom = new ReseedingSecureRandom(getRandomNumberAlgorithm(), getRandomNumberAlgorithmProvider());
+
+ public GoogleAuthenticator() {
+ config = new GoogleAuthenticatorConfig();
+ }
+
+ public GoogleAuthenticator(GoogleAuthenticatorConfig config) {
+ if (config == null) {
+ throw new IllegalArgumentException("Configuration cannot be null.");
+ }
+
+ this.config = config;
+ }
+
+ /**
+ * @return the random number generator algorithm.
+ * @since 0.5.0
+ */
+ private String getRandomNumberAlgorithm() {
+ return System.getProperty(RNG_ALGORITHM, DEFAULT_RANDOM_NUMBER_ALGORITHM);
+ }
+
+ /**
+ * @return the random number generator algorithm provider.
+ * @since 0.5.0
+ */
+ private String getRandomNumberAlgorithmProvider() {
+ return System.getProperty(RNG_ALGORITHM_PROVIDER, DEFAULT_RANDOM_NUMBER_ALGORITHM_PROVIDER);
+ }
+
+ public int calculateCode(String secret, long tm) {
+ return calculateCode(decodeKey(secret), tm);
+ }
+
+ /**
+ * Decode a secret key in raw bytes.
+ *
+ * @param secret Secret key
+ * @return Raw bytes
+ */
+ private byte[] decodeKey(String secret) {
+ switch (config.getKeyRepresentation()) {
+ case BASE32:
+ Base32 codec32 = new Base32();
+ return codec32.decode(secret);
+ case BASE64:
+ Base64 codec64 = new Base64();
+ return codec64.decode(secret);
+ default:
+ throw new IllegalArgumentException("Unknown key representation type.");
+ }
+ }
+
+ /**
+ * Calculates the verification code of the provided key at the specified
+ * instant of time using the algorithm specified in RFC 6238.
+ *
+ * @param key the secret key in binary format.
+ * @param tm the instant of time.
+ * @return the validation code for the provided key at the specified instant
+ * of time.
+ */
+ int calculateCode(byte[] key, long tm) {
+ // Allocating an array of bytes to represent the specified instant
+ // of time.
+ byte[] data = new byte[8];
+ long value = tm;
+
+ // Converting the instant of time from the long representation to a
+ // big-endian array of bytes (RFC4226, 5.2. Description).
+ for (int i = 8; i-- > 0; value >>>= 8) {
+ data[i] = (byte) value;
+ }
+
+ // Building the secret key specification for the HmacSHA1 algorithm.
+ SecretKeySpec signKey = new SecretKeySpec(key, HMAC_HASH_FUNCTION);
+
+ try {
+ // Getting an HmacSHA1 algorithm implementation from the JCE.
+ Mac mac = Mac.getInstance(HMAC_HASH_FUNCTION);
+
+ // Initializing the MAC algorithm.
+ mac.init(signKey);
+
+ // Processing the instant of time and getting the encrypted data.
+ byte[] hash = mac.doFinal(data);
+
+ // Building the validation code performing dynamic truncation
+ // (RFC4226, 5.3. Generating an HOTP value)
+ int offset = hash[hash.length - 1] & 0xF;
+
+ // We are using a long because Java hasn't got an unsigned integer
+ // type
+ // and we need 32 unsigned bits).
+ long truncatedHash = 0;
+
+ for (int i = 0; i < 4; ++i) {
+ truncatedHash <<= 8;
+
+ // Java bytes are signed but we need an unsigned integer:
+ // cleaning off all but the LSB.
+ truncatedHash |= (hash[offset + i] & 0xFF);
+ }
+
+ // Clean bits higher than the 32nd (inclusive) and calculate the
+ // module with the maximum validation code value.
+ truncatedHash &= 0x7FFFFFFF;
+ truncatedHash %= config.getKeyModulus();
+
+ // Returning the validation code to the caller.
+ return (int) truncatedHash;
+ } catch (NoSuchAlgorithmException | InvalidKeyException ex) {
+ // We're not disclosing internal error details to our clients.
+ throw new GoogleAuthenticatorException("The operation cannot be performed now.", ex);
+ }
+ }
+
+ /**
+ * This method implements the algorithm specified in RFC 6238 to check if a
+ * validation code is valid in a given instant of time for the given secret
+ * key.
+ *
+ * @param secret the Base32 encoded secret key.
+ * @param code the code to validate.
+ * @param timestamp the instant of time to use during the validation process.
+ * @param window the window size to use during the validation process.
+ * @return true
if the validation code is valid,
+ * false
otherwise.
+ */
+ private boolean checkCode(String secret, long code, long timestamp, int window) {
+ // Decoding the secret key to get its raw byte representation.
+ byte[] decodedKey = decodeKey(secret);
+
+ // convert unix time into a 30 second "window" as specified by the
+ // TOTP specification. Using Google's default interval of 30 seconds.
+ final long timeWindow = timestamp / this.config.getTimeStepSizeInMillis();
+
+ // Calculating the verification code of the given key in each of the
+ // time intervals and returning true if the provided code is equal to
+ // one of them.
+ for (int i = -((window - 1) / 2); i <= window / 2; ++i) {
+ // Calculating the verification code for the current time interval.
+ long hash = calculateCode(decodedKey, timeWindow + i);
+
+ // Checking if the provided code is equal to the calculated one.
+ if (hash == code) {
+ // The verification code is valid.
+ return true;
+ }
+ }
+
+ // The verification code is invalid.
+ return false;
+ }
+
+ public GoogleAuthenticatorKey createCredentials() {
+ // Allocating a buffer sufficiently large to hold the bytes required by
+ // the secret key and the scratch codes.
+ byte[] buffer = new byte[SECRET_BITS / 8 + SCRATCH_CODES * BYTES_PER_SCRATCH_CODE];
+
+ secureRandom.nextBytes(buffer);
+
+ // Extracting the bytes making up the secret key.
+ byte[] secretKey = Arrays.copyOf(buffer, SECRET_BITS / 8);
+ String generatedKey = calculateSecretKey(secretKey);
+
+ // Generating the verification code at time = 0.
+ int validationCode = calculateValidationCode(secretKey);
+
+ // Calculate scratch codes
+ List scratchCodes = calculateScratchCodes(buffer);
+
+ return new GoogleAuthenticatorKey(generatedKey, validationCode, scratchCodes);
+ }
+
+ private List calculateScratchCodes(byte[] buffer) {
+ List scratchCodes = new ArrayList<>();
+
+ while (scratchCodes.size() < SCRATCH_CODES) {
+ byte[] scratchCodeBuffer = Arrays.copyOfRange(buffer, SECRET_BITS / 8 + BYTES_PER_SCRATCH_CODE * scratchCodes.size(), SECRET_BITS / 8 + BYTES_PER_SCRATCH_CODE * scratchCodes.size() + BYTES_PER_SCRATCH_CODE);
+
+ int scratchCode = calculateScratchCode(scratchCodeBuffer);
+
+ if (scratchCode != SCRATCH_CODE_INVALID) {
+ scratchCodes.add(scratchCode);
+ } else {
+ scratchCodes.add(generateScratchCode());
+ }
+ }
+
+ return scratchCodes;
+ }
+
+ /**
+ * This method calculates a scratch code from a random byte buffer of
+ * suitable size #BYTES_PER_SCRATCH_CODE
.
+ *
+ * @param scratchCodeBuffer a random byte buffer whose minimum size is #BYTES_PER_SCRATCH_CODE
.
+ * @return the scratch code.
+ */
+ private int calculateScratchCode(byte[] scratchCodeBuffer) {
+ if (scratchCodeBuffer.length < BYTES_PER_SCRATCH_CODE) {
+ throw new IllegalArgumentException(String.format("The provided random byte buffer is too small: %d.", scratchCodeBuffer.length));
+ }
+
+ int scratchCode = 0;
+
+ for (int i = 0; i < BYTES_PER_SCRATCH_CODE; ++i) {
+ scratchCode = (scratchCode << 8) + (scratchCodeBuffer[i] & 0xff);
+ }
+
+ scratchCode = (scratchCode & 0x7FFFFFFF) % SCRATCH_CODE_MODULUS;
+
+ // Accept the scratch code only if it has exactly
+ // SCRATCH_CODE_LENGTH digits.
+ if (scratchCode >= SCRATCH_CODE_MODULUS / 10) {
+ return scratchCode;
+ } else {
+ return SCRATCH_CODE_INVALID;
+ }
+ }
+
+ /**
+ * This method creates a new random byte buffer from which a new scratch
+ * code is generated. This function is invoked if a scratch code generated
+ * from the main buffer is invalid because it does not satisfy the scratch
+ * code restrictions.
+ *
+ * @return A valid scratch code.
+ */
+ private int generateScratchCode() {
+ while (true) {
+ byte[] scratchCodeBuffer = new byte[BYTES_PER_SCRATCH_CODE];
+ secureRandom.nextBytes(scratchCodeBuffer);
+
+ int scratchCode = calculateScratchCode(scratchCodeBuffer);
+
+ if (scratchCode != SCRATCH_CODE_INVALID) {
+ return scratchCode;
+ }
+ }
+ }
+
+ /**
+ * This method calculates the validation code at time 0.
+ *
+ * @param secretKey The secret key to use.
+ * @return the validation code at time 0.
+ */
+ private int calculateValidationCode(byte[] secretKey) {
+ return calculateCode(secretKey, 0);
+ }
+
+ /**
+ * This method calculates the secret key given a random byte buffer.
+ *
+ * @param secretKey a random byte buffer.
+ * @return the secret key.
+ */
+ private String calculateSecretKey(byte[] secretKey) {
+ switch (config.getKeyRepresentation()) {
+ case BASE32:
+ return new Base32().encodeToString(secretKey);
+ case BASE64:
+ return new Base64().encodeToString(secretKey);
+ default:
+ throw new IllegalArgumentException("Unknown key representation type.");
+ }
+ }
+
+ public boolean authorize(String secret, int verificationCode) throws GoogleAuthenticatorException {
+ return authorize(secret, verificationCode, new Date().getTime());
+ }
+
+ public boolean authorize(String secret, int verificationCode, long time) throws GoogleAuthenticatorException {
+ // Checking user input and failing if the secret key was not provided.
+ if (secret == null) {
+ throw new IllegalArgumentException("Secret cannot be null.");
+ }
+
+ // Checking if the verification code is between the legal bounds.
+ if (verificationCode <= 0 || verificationCode >= this.config.getKeyModulus()) {
+ return false;
+ }
+
+ // Checking the validation code using the current UNIX time.
+ return checkCode(secret, verificationCode, time, this.config.getWindowSize());
+ }
+}
diff --git a/docs-core/src/main/java/com/sismics/util/totp/GoogleAuthenticatorConfig.java b/docs-core/src/main/java/com/sismics/util/totp/GoogleAuthenticatorConfig.java
new file mode 100644
index 00000000..cabc7f63
--- /dev/null
+++ b/docs-core/src/main/java/com/sismics/util/totp/GoogleAuthenticatorConfig.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2014-2015 Enrico M. Crisostomo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sismics.util.totp;
+
+import java.util.concurrent.TimeUnit;
+
+public class GoogleAuthenticatorConfig {
+ private long timeStepSizeInMillis = TimeUnit.SECONDS.toMillis(30);
+ private int windowSize = 3;
+ private int codeDigits = 6;
+ private int keyModulus = (int) Math.pow(10, codeDigits);
+ private KeyRepresentation keyRepresentation = KeyRepresentation.BASE32;
+
+ /**
+ * Returns the key module.
+ *
+ * @return the key module.
+ */
+ public int getKeyModulus() {
+ return keyModulus;
+ }
+
+ /**
+ * Returns the key representation.
+ *
+ * @return the key representation.
+ */
+ public KeyRepresentation getKeyRepresentation() {
+ return keyRepresentation;
+ }
+
+ /**
+ * Returns the number of digits in the generated code.
+ *
+ * @return the number of digits in the generated code.
+ */
+ public int getCodeDigits() {
+ return codeDigits;
+ }
+
+ /**
+ * Returns the time step size, in milliseconds, as specified by RFC 6238.
+ * The default value is 30.000.
+ *
+ * @return the time step size in milliseconds.
+ */
+ public long getTimeStepSizeInMillis() {
+ return timeStepSizeInMillis;
+ }
+
+ /**
+ * Returns an integer value representing the number of windows of size
+ * timeStepSizeInMillis that are checked during the validation process, to
+ * account for differences between the server and the client clocks. The
+ * bigger the window, the more tolerant the library code is about clock
+ * skews.
+ *
+ * We are using Google's default behaviour of using a window size equal to
+ * 3. The limit on the maximum window size, present in older versions of
+ * this library, has been removed.
+ *
+ * @return the window size.
+ * @see #timeStepSizeInMillis
+ */
+ public int getWindowSize() {
+ return windowSize;
+ }
+
+ public static class GoogleAuthenticatorConfigBuilder {
+ private GoogleAuthenticatorConfig config = new GoogleAuthenticatorConfig();
+
+ public GoogleAuthenticatorConfig build() {
+ return config;
+ }
+
+ public GoogleAuthenticatorConfigBuilder setCodeDigits(int codeDigits) {
+ if (codeDigits <= 0) {
+ throw new IllegalArgumentException("Code digits must be positive.");
+ }
+
+ if (codeDigits < 6) {
+ throw new IllegalArgumentException("The minimum number of digits is 6.");
+ }
+
+ if (codeDigits > 8) {
+ throw new IllegalArgumentException("The maximum number of digits is 8.");
+ }
+
+ config.codeDigits = codeDigits;
+ config.keyModulus = (int) Math.pow(10, codeDigits);
+ return this;
+ }
+
+ public GoogleAuthenticatorConfigBuilder setTimeStepSizeInMillis(long timeStepSizeInMillis) {
+ if (timeStepSizeInMillis <= 0) {
+ throw new IllegalArgumentException("Time step size must be positive.");
+ }
+
+ config.timeStepSizeInMillis = timeStepSizeInMillis;
+ return this;
+ }
+
+ public GoogleAuthenticatorConfigBuilder setWindowSize(int windowSize) {
+ if (windowSize <= 0) {
+ throw new IllegalArgumentException("Window number must be positive.");
+ }
+
+ config.windowSize = windowSize;
+ return this;
+ }
+
+ public GoogleAuthenticatorConfigBuilder setKeyRepresentation(KeyRepresentation keyRepresentation) {
+ if (keyRepresentation == null) {
+ throw new IllegalArgumentException("Key representation cannot be null.");
+ }
+
+ config.keyRepresentation = keyRepresentation;
+ return this;
+ }
+ }
+}
diff --git a/docs-core/src/main/java/com/sismics/util/totp/GoogleAuthenticatorException.java b/docs-core/src/main/java/com/sismics/util/totp/GoogleAuthenticatorException.java
new file mode 100644
index 00000000..77a742b8
--- /dev/null
+++ b/docs-core/src/main/java/com/sismics/util/totp/GoogleAuthenticatorException.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2014-2015 Enrico M. Crisostomo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sismics.util.totp;
+
+/**
+ * Date: 12/02/14
+ * Time: 13:36
+ *
+ * @author Enrico M. Crisostomo
+ */
+public class GoogleAuthenticatorException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Builds an exception with the provided error message.
+ *
+ * @param message the error message.
+ */
+ public GoogleAuthenticatorException(String message) {
+ super(message);
+ }
+
+ /**
+ * Builds an exception with the provided error mesasge and
+ * the provided cuase.
+ *
+ * @param message the error message.
+ * @param cause the cause.
+ */
+ public GoogleAuthenticatorException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/docs-core/src/main/java/com/sismics/util/totp/GoogleAuthenticatorKey.java b/docs-core/src/main/java/com/sismics/util/totp/GoogleAuthenticatorKey.java
new file mode 100644
index 00000000..ee687153
--- /dev/null
+++ b/docs-core/src/main/java/com/sismics/util/totp/GoogleAuthenticatorKey.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2014-2015 Enrico M. Crisostomo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sismics.util.totp;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is a JavaBean used by the GoogleAuthenticator library to represent
+ * a secret key.
+ *
+ * This class is immutable.
+ *
+ * Instance of this class should only be constructed by the GoogleAuthenticator
+ * library.
+ *
+ * @author Enrico M. Crisostomo
+ * @version 1.0
+ * @see GoogleAuthenticator
+ * @since 1.0
+ */
+public final class GoogleAuthenticatorKey {
+ /**
+ * The secret key in Base32 encoding.
+ */
+ private final String key;
+
+ /**
+ * The verification code at time = 0 (the UNIX epoch).
+ */
+ private final int verificationCode;
+
+ /**
+ * The list of scratch codes.
+ */
+ private final List scratchCodes;
+
+ /**
+ * The constructor with package visibility.
+ *
+ * @param secretKey the secret key in Base32 encoding.
+ * @param code the verification code at time = 0 (the UNIX epoch).
+ * @param scratchCodes the list of scratch codes.
+ */
+ GoogleAuthenticatorKey(String secretKey, int code, List scratchCodes) {
+ key = secretKey;
+ verificationCode = code;
+ this.scratchCodes = new ArrayList<>(scratchCodes);
+ }
+
+ /**
+ * Get the list of scratch codes.
+ *
+ * @return the list of scratch codes.
+ */
+ public List getScratchCodes() {
+ return scratchCodes;
+ }
+
+ /**
+ * Returns the secret key in Base32 encoding.
+ *
+ * @return the secret key in Base32 encoding.
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /**
+ * Returns the verification code at time = 0 (the UNIX epoch).
+ *
+ * @return the verificationCode at time = 0 (the UNIX epoch).
+ */
+ public int getVerificationCode() {
+ return verificationCode;
+ }
+}
diff --git a/docs-core/src/main/java/com/sismics/util/totp/KeyRepresentation.java b/docs-core/src/main/java/com/sismics/util/totp/KeyRepresentation.java
new file mode 100644
index 00000000..9461ba08
--- /dev/null
+++ b/docs-core/src/main/java/com/sismics/util/totp/KeyRepresentation.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2014-2015 Enrico M. Crisostomo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sismics.util.totp;
+
+public enum KeyRepresentation {
+ BASE32,
+ BASE64
+}
diff --git a/docs-core/src/main/java/com/sismics/util/totp/ReseedingSecureRandom.java b/docs-core/src/main/java/com/sismics/util/totp/ReseedingSecureRandom.java
new file mode 100644
index 00000000..fd135d6f
--- /dev/null
+++ b/docs-core/src/main/java/com/sismics/util/totp/ReseedingSecureRandom.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2014-2015 Enrico M. Crisostomo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sismics.util.totp;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Date: 08/04/14 Time: 15:21
+ *
+ * @author Enrico M. Crisostomo
+ */
+class ReseedingSecureRandom {
+ private static final int MAX_OPERATIONS = 1_000_000;
+ private final String provider;
+ private final String algorithm;
+ private final AtomicInteger count = new AtomicInteger(0);
+ private SecureRandom secureRandom;
+
+ ReseedingSecureRandom() {
+ this.algorithm = null;
+ this.provider = null;
+
+ buildSecureRandom();
+ }
+
+ ReseedingSecureRandom(String algorithm) {
+ if (algorithm == null) {
+ throw new IllegalArgumentException("Algorithm cannot be null.");
+ }
+
+ this.algorithm = algorithm;
+ this.provider = null;
+
+ buildSecureRandom();
+ }
+
+ ReseedingSecureRandom(String algorithm, String provider) {
+ if (algorithm == null) {
+ throw new IllegalArgumentException("Algorithm cannot be null.");
+ }
+
+ if (provider == null) {
+ throw new IllegalArgumentException("Provider cannot be null.");
+ }
+
+ this.algorithm = algorithm;
+ this.provider = provider;
+
+ buildSecureRandom();
+ }
+
+ private void buildSecureRandom() {
+ try {
+ if (this.algorithm == null && this.provider == null) {
+ this.secureRandom = new SecureRandom();
+ } else if (this.provider == null) {
+ this.secureRandom = SecureRandom.getInstance(this.algorithm);
+ } else {
+ this.secureRandom = SecureRandom.getInstance(this.algorithm, this.provider);
+ }
+ } catch (NoSuchAlgorithmException e) {
+ throw new GoogleAuthenticatorException(String.format("Could not initialise SecureRandom with the specified algorithm: %s. "
+ + "Another provider can be chosen setting the %s system property.", this.algorithm, GoogleAuthenticator.RNG_ALGORITHM), e);
+ } catch (NoSuchProviderException e) {
+ throw new GoogleAuthenticatorException(String.format("Could not initialise SecureRandom with the specified provider: %s. "
+ + "Another provider can be chosen setting the %s system property.", this.provider, GoogleAuthenticator.RNG_ALGORITHM_PROVIDER), e);
+ }
+ }
+
+ void nextBytes(byte[] bytes) {
+ if (count.incrementAndGet() > MAX_OPERATIONS) {
+ synchronized (this) {
+ if (count.get() > MAX_OPERATIONS) {
+ buildSecureRandom();
+ count.set(0);
+ }
+ }
+ }
+
+ this.secureRandom.nextBytes(bytes);
+ }
+}
diff --git a/docs-core/src/test/java/com/sismics/util/TestGoogleAuthenticator.java b/docs-core/src/test/java/com/sismics/util/TestGoogleAuthenticator.java
new file mode 100644
index 00000000..37dddb00
--- /dev/null
+++ b/docs-core/src/test/java/com/sismics/util/TestGoogleAuthenticator.java
@@ -0,0 +1,26 @@
+package com.sismics.util;
+
+import java.util.Date;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.sismics.util.totp.GoogleAuthenticator;
+import com.sismics.util.totp.GoogleAuthenticatorKey;
+
+/**
+ * Test of {@link GoogleAuthenticator}
+ *
+ * @author bgamard
+ */
+public class TestGoogleAuthenticator {
+ @Test
+ public void testGoogleAuthenticator() {
+ GoogleAuthenticator gAuth = new GoogleAuthenticator();
+ GoogleAuthenticatorKey key = gAuth.createCredentials();
+ Assert.assertNotNull(key.getVerificationCode());
+ Assert.assertEquals(5, key.getScratchCodes().size());
+ int validationCode = gAuth.calculateCode(key.getKey(), new Date().getTime() / 30000);
+ Assert.assertTrue(gAuth.authorize(key.getKey(), validationCode));
+ }
+}
diff --git a/docs-parent/pom.xml b/docs-parent/pom.xml
index c86fa7e0..81362ea6 100644
--- a/docs-parent/pom.xml
+++ b/docs-parent/pom.xml
@@ -40,7 +40,6 @@
3.2.1
1.6.5
1.3.1
- 0.6.0
9.2.13.v20150730
9.2.13.v20150730
@@ -410,13 +409,6 @@
${com.github.jai-imageio.jai-imageio-core.version}
-
-
- com.warrenstrange
- googleauth
- ${com.warrenstrange.googleauth}
-
-
diff --git a/docs-web/pom.xml b/docs-web/pom.xml
index 5b072a9c..048cc060 100644
--- a/docs-web/pom.xml
+++ b/docs-web/pom.xml
@@ -89,11 +89,6 @@
servlet
-
- com.warrenstrange
- googleauth
-
-
com.sismics.docs
diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java
index 960846ff..16cf7593 100644
--- a/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java
+++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java
@@ -55,8 +55,8 @@ import com.sismics.rest.util.JsonUtil;
import com.sismics.rest.util.ValidationUtil;
import com.sismics.security.UserPrincipal;
import com.sismics.util.filter.TokenBasedSecurityFilter;
-import com.warrenstrange.googleauth.GoogleAuthenticator;
-import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
+import com.sismics.util.totp.GoogleAuthenticator;
+import com.sismics.util.totp.GoogleAuthenticatorKey;
/**
* User REST resources.
@@ -649,7 +649,6 @@ public class UserResource extends BaseResource {
}
// Create a new TOTP key and scratch codes
- // TODO Copy library sources here to scrap useless dependencies and make verification code generation public for testing
GoogleAuthenticator gAuth = new GoogleAuthenticator();
final GoogleAuthenticatorKey key = gAuth.createCredentials();