Skip to content

Commit

Permalink
Improved MfaAuthenticator interface and API
Browse files Browse the repository at this point in the history
Signed-off-by: Alberto Codutti <alberto.codutti@eurotech.com>
  • Loading branch information
Coduz committed Nov 4, 2022
1 parent 3b100a1 commit 837bfe2
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 40 deletions.
Expand Up @@ -12,48 +12,63 @@
*******************************************************************************/
package org.eclipse.kapua.service.authentication.mfa;

import org.eclipse.kapua.KapuaException;

import java.util.List;

/**
* {@link MfaAuthenticator} interface
* {@link MfaAuthenticator} definition.
*
* @since 1.3.0
*/
public interface MfaAuthenticator {

/**
* @return true if the {@link MfaAuthenticator} is enabled, false otherwise
* Whether the {@link MfaAuthenticator} service is enabled or not.
*
* @return {@code true} if the {@link MfaAuthenticator} is enabled, {@code false} otherwise.
* @throws KapuaException
* @since 1.3.0
*/
boolean isEnabled();

/**
* Validates a code generated with the authenticator app
* Validates a code generated with the authenticator app.
*
* @param encryptedSecret the encoded secret key
* @param verificationCode the verification code
* @return true if the verficationCode is valid, false otherwise
* @param encryptedSecret The encoded secret key
* @param verificationCode The verification code
* @return {@code true} if the verficationCode is valid, {@code false} otherwise
* @throws KapuaException
* @since 1.3.0
*/
boolean authorize(String encryptedSecret, int verificationCode);
boolean authorize(String encryptedSecret, int verificationCode) throws KapuaException;

/**
* Validates a scratch code
* Validates a scratch code.
*
* @param hasedScratchCode the hashed scratch code
* @param authCode the plaintext authentication code
* @return true if the code match, false otherwise
* @param hashedScratchCode The hashed scratch code
* @param authCode The plaintext authentication code
* @return {@code true} if the code match, {@code false} otherwise
* @throws KapuaException
* @since 1.3.0
*/
boolean authorize(String hasedScratchCode, String authCode);
boolean authorize(String hashedScratchCode, String authCode) throws KapuaException;

/**
* Generates the secret key
*
* @return the secret key in the form of a String
* @return The secret key in the form of a {@link String}
* @throws KapuaException
* @since 1.3.0
*/
String generateKey();
String generateKey() throws KapuaException;

/**
* Generates the scratch codes
* Generates the {@link List} of scratch codes.
*
* @return the list of scratch codes in the form of Strings
* @throws KapuaException
* @since 1.3.0
*/
List<String> generateCodes();

List<String> generateCodes() throws KapuaException;
}
Expand Up @@ -17,6 +17,7 @@
import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
import org.eclipse.kapua.KapuaException;
import org.eclipse.kapua.commons.util.ArgumentValidator;
import org.eclipse.kapua.commons.util.log.ConfigurationPrinter;
import org.eclipse.kapua.service.authentication.mfa.MfaAuthenticator;
import org.eclipse.kapua.service.authentication.shiro.setting.KapuaAuthenticationSetting;
import org.eclipse.kapua.service.authentication.shiro.setting.KapuaAuthenticationSettingKeys;
Expand All @@ -30,23 +31,24 @@
import java.util.concurrent.TimeUnit;

/**
* An {@link MfaAuthenticator} implementation that wraps secret code generation and authentication methods.
* {@link MfaAuthenticator} implementation.
*
* @since 1.3.0
*/
public class MfaAuthenticatorImpl implements MfaAuthenticator {

private static final Logger LOGGER = LoggerFactory.getLogger(MfaAuthenticatorImpl.class);
private static final Logger LOG = LoggerFactory.getLogger(MfaAuthenticatorImpl.class);

private static final KapuaAuthenticationSetting AUTHENTICATION_SETTING = KapuaAuthenticationSetting.getInstance();
private static final GoogleAuthenticatorConfig GOOGLE_AUTHENTICATOR_CONFIG;

static {
// settings
KapuaAuthenticationSetting setting = KapuaAuthenticationSetting.getInstance();
int timeStepSize = setting.getInt(KapuaAuthenticationSettingKeys.AUTHENTICATION_MFA_TIME_STEP_SIZE);
// Setup of Google Authenticator Configs
int timeStepSize = AUTHENTICATION_SETTING.getInt(KapuaAuthenticationSettingKeys.AUTHENTICATION_MFA_TIME_STEP_SIZE);
long timeStepSizeInMillis = TimeUnit.SECONDS.toMillis(timeStepSize);
int windowSize = setting.getInt(KapuaAuthenticationSettingKeys.AUTHENTICATION_MFA_WINDOW_SIZE);
int scratchCodeNumber = setting.getInt(KapuaAuthenticationSettingKeys.AUTHENTICATION_MFA_SCRATCH_CODES_NUMBER);
int codeDigitsNumber = setting.getInt(KapuaAuthenticationSettingKeys.AUTHENTICATION_MFA_CODE_DIGITS_NUMBER);
int windowSize = AUTHENTICATION_SETTING.getInt(KapuaAuthenticationSettingKeys.AUTHENTICATION_MFA_WINDOW_SIZE);
int scratchCodeNumber = AUTHENTICATION_SETTING.getInt(KapuaAuthenticationSettingKeys.AUTHENTICATION_MFA_SCRATCH_CODES_NUMBER);
int codeDigitsNumber = AUTHENTICATION_SETTING.getInt(KapuaAuthenticationSettingKeys.AUTHENTICATION_MFA_CODE_DIGITS_NUMBER);

try {
ArgumentValidator.notNegative(timeStepSizeInMillis, "timeStepSizeInMillis");
Expand All @@ -55,16 +57,30 @@ public class MfaAuthenticatorImpl implements MfaAuthenticator {
ArgumentValidator.numRange(codeDigitsNumber, 6, 8, "codeDigitsNumber");

GOOGLE_AUTHENTICATOR_CONFIG = new GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder()
.setTimeStepSizeInMillis(timeStepSizeInMillis) // the time step size, in milliseconds
.setWindowSize(windowSize) // the number of windows of size timeStepSizeInMillis checked during the validation
.setNumberOfScratchCodes(scratchCodeNumber) // number of scratch codes
.setCodeDigits(codeDigitsNumber) // the number of digits in the generated code
.setTimeStepSizeInMillis(timeStepSizeInMillis) // The time step size, in milliseconds
.setWindowSize(windowSize) // The number of windows of size timeStepSizeInMillis checked during the validation
.setNumberOfScratchCodes(scratchCodeNumber) // Number of scratch codes
.setCodeDigits(codeDigitsNumber) // The number of digits in the generated code
.build();

} catch (KapuaException e) {
LOGGER.error("Error while initializing the Google Authenticator configuration", e);
LOG.error("Error while initializing the Google Authenticator configuration", e);
throw new ExceptionInInitializerError(e);
}

// Print Configuration
ConfigurationPrinter configurationPrinter =
ConfigurationPrinter
.create()
.withLogger(LOG)
.withLogLevel(ConfigurationPrinter.LogLevel.INFO)
.withTitle("Google Authenticator Configuration")
.addParameter("Time step", timeStepSizeInMillis)
.addParameter("Window size", windowSize)
.addParameter("Scratch code number", scratchCodeNumber)
.addParameter("Scratch code digits", codeDigitsNumber);

configurationPrinter.printLog();
}

@Override
Expand All @@ -73,18 +89,31 @@ public boolean isEnabled() {
}

@Override
public boolean authorize(String encryptedSecret, int verificationCode) {
boolean isCodeValid;
public boolean authorize(String encryptedSecret, int verificationCode) throws KapuaException {
//
// Argument validation
ArgumentValidator.notNull(encryptedSecret, "encryptedSecret");
ArgumentValidator.notNegative(verificationCode, "verificationCode");

//
// Do check
String secret = AuthenticationUtils.decryptAes(encryptedSecret);

GoogleAuthenticator ga = new GoogleAuthenticator(GOOGLE_AUTHENTICATOR_CONFIG);
isCodeValid = ga.authorize(secret, verificationCode);
return isCodeValid;

return ga.authorize(secret, verificationCode);
}

@Override
public boolean authorize(String hasedScratchCode, String verificationCode) {
return BCrypt.checkpw(verificationCode, hasedScratchCode);
public boolean authorize(String hashedScratchCode, String verificationCode) throws KapuaException {
//
// Argument validation
ArgumentValidator.notEmptyOrNull(hashedScratchCode, "hashedScratchCode");
ArgumentValidator.notEmptyOrNull(verificationCode, "verificationCode");

//
// Do check
return BCrypt.checkpw(verificationCode, hashedScratchCode);
}

@Override
Expand All @@ -94,12 +123,15 @@ public String generateKey() {
return key.getKey();
}


/**
* We're not using the same secret as the secret key for the scratch code generation
* this allows re-generating scratch codes independently of the secret.
*
* @see MfaAuthenticator#generateCodes()
*/
@Override
public List<String> generateCodes() {

// we're not using the same secret as the secret key for the scratch code generation
// this allows re-generating scratch codes independently from the secret

GoogleAuthenticator gAuth = new GoogleAuthenticator(GOOGLE_AUTHENTICATOR_CONFIG);
GoogleAuthenticatorKey key = gAuth.createCredentials();

Expand Down

0 comments on commit 837bfe2

Please sign in to comment.