Skip to content

Commit

Permalink
Update code with encryption and decryption method
Browse files Browse the repository at this point in the history
  • Loading branch information
Sae126V committed Nov 30, 2023
1 parent 4a91f9a commit b5a2540
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import java.util.Optional;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
Expand All @@ -31,6 +33,7 @@
import it.infn.mw.iam.audit.events.account.multi_factor_authentication.AuthenticatorAppEnabledEvent;
import it.infn.mw.iam.audit.events.account.multi_factor_authentication.RecoveryCodeVerifiedEvent;
import it.infn.mw.iam.audit.events.account.multi_factor_authentication.TotpVerifiedEvent;
import it.infn.mw.iam.config.mfa.IamTotpMfaProperties;
import it.infn.mw.iam.core.user.IamAccountService;
import it.infn.mw.iam.core.user.exception.MfaSecretAlreadyBoundException;
import it.infn.mw.iam.core.user.exception.MfaSecretNotFoundException;
Expand All @@ -39,30 +42,39 @@
import it.infn.mw.iam.persistence.model.IamTotpMfa;
import it.infn.mw.iam.persistence.model.IamTotpRecoveryCode;
import it.infn.mw.iam.persistence.repository.IamTotpMfaRepository;
import it.infn.mw.iam.util.mfa.IamTotpMfaEncryptionAndDecryptionHelper;
import it.infn.mw.iam.util.mfa.IamTotpMfaEncryptionAndDecryptionUtil;
import it.infn.mw.iam.util.mfa.IamTotpMfaInvalidArgumentError;

@Service
public class DefaultIamTotpMfaService implements IamTotpMfaService, ApplicationEventPublisherAware {

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

public static final int RECOVERY_CODE_QUANTITY = 6;
public static final IamTotpMfaEncryptionAndDecryptionHelper defaultModel = IamTotpMfaEncryptionAndDecryptionHelper
.getInstance();

private final IamAccountService iamAccountService;
private final IamTotpMfaRepository totpMfaRepository;
private final SecretGenerator secretGenerator;
private final RecoveryCodeGenerator recoveryCodeGenerator;
private final CodeVerifier codeVerifier;
private ApplicationEventPublisher eventPublisher;
private final IamTotpMfaProperties iamTotpMfaProperties;

@Autowired
public DefaultIamTotpMfaService(IamAccountService iamAccountService,
IamTotpMfaRepository totpMfaRepository, SecretGenerator secretGenerator,
RecoveryCodeGenerator recoveryCodeGenerator, CodeVerifier codeVerifier,
ApplicationEventPublisher eventPublisher) {
ApplicationEventPublisher eventPublisher, IamTotpMfaProperties iamTotpMfaProperties) {
this.iamAccountService = iamAccountService;
this.totpMfaRepository = totpMfaRepository;
this.secretGenerator = secretGenerator;
this.recoveryCodeGenerator = recoveryCodeGenerator;
this.codeVerifier = codeVerifier;
this.eventPublisher = eventPublisher;
this.iamTotpMfaProperties = iamTotpMfaProperties;
}

private void authenticatorAppEnabledEvent(IamAccount account, IamTotpMfa totpMfa) {
Expand Down Expand Up @@ -108,13 +120,26 @@ public IamTotpMfa addTotpMfaSecret(IamAccount account) {

// Generate secret
IamTotpMfa totpMfa = new IamTotpMfa(account);
totpMfa.setSecret(secretGenerator.generate());
totpMfa.setAccount(account);
String sharedSecretToken = secretGenerator.generate();

Set<IamTotpRecoveryCode> recoveryCodes = generateRecoveryCodes(totpMfa);
totpMfa.setRecoveryCodes(recoveryCodes);
totpMfaRepository.save(totpMfa);
return totpMfa;
try {
String cipherText = IamTotpMfaEncryptionAndDecryptionUtil.encryptSecretOrRecoveryCode(
defaultModel.getModeOfOperation(),
sharedSecretToken,
iamTotpMfaProperties.getPasswordToEncryptOrDecrypt(),
IamTotpMfaEncryptionAndDecryptionUtil.getIVSecureRandom(defaultModel.getModeOfOperation()));

totpMfa.setSecret(cipherText);
totpMfa.setAccount(account);

Set<IamTotpRecoveryCode> recoveryCodes = generateRecoveryCodes(totpMfa);
totpMfa.setRecoveryCodes(recoveryCodes);
totpMfaRepository.save(totpMfa);

return totpMfa;
} catch (Exception exp) {
throw new IamTotpMfaInvalidArgumentError("Please ensure that you either use the same password or set the password", exp);
}
}

/**
Expand Down Expand Up @@ -205,12 +230,18 @@ public boolean verifyTotp(IamAccount account, String totp) {
}

IamTotpMfa totpMfa = totpMfaOptional.get();
String mfaSecret = totpMfa.getSecret();

// Verify provided TOTP
if (codeVerifier.isValidCode(mfaSecret, totp)) {
totpVerifiedEvent(account, totpMfa);
return true;
try {
String mfaSecret = IamTotpMfaEncryptionAndDecryptionUtil.decryptSecretOrRecoveryCode(
defaultModel.getModeOfOperation(),
totpMfa.getSecret(), iamTotpMfaProperties.getPasswordToEncryptOrDecrypt());

// Verify provided TOTP
if (codeVerifier.isValidCode(mfaSecret, totp)) {
totpVerifiedEvent(account, totpMfa);
return true;
}
} catch (Exception exp) {
LOG.error("Please ensure that you either use the same password or set the password", exp);
}

return false;
Expand All @@ -237,25 +268,45 @@ public boolean verifyRecoveryCode(IamAccount account, String recoveryCode) {

// Check for a matching recovery code
Set<IamTotpRecoveryCode> accountRecoveryCodes = totpMfa.getRecoveryCodes();
for (IamTotpRecoveryCode recoveryCodeObject : accountRecoveryCodes) {
String recoveryCodeString = recoveryCodeObject.getCode();
if (recoveryCode.equals(recoveryCodeString)) {
recoveryCodeVerifiedEvent(account, totpMfa);
return true;

try {
for (IamTotpRecoveryCode recoveryCodeObject : accountRecoveryCodes) {
String recoveryCodeEncrypted = recoveryCodeObject.getCode();
String recoveryCodeString = IamTotpMfaEncryptionAndDecryptionUtil
.decryptSecretOrRecoveryCode(defaultModel.getModeOfOperation(), recoveryCodeEncrypted,
iamTotpMfaProperties.getPasswordToEncryptOrDecrypt());

if (recoveryCode.equals(recoveryCodeString)) {
recoveryCodeVerifiedEvent(account, totpMfa);
return true;
}
}
} catch (Exception exp) {
LOG.error("Please ensure that you either use the same password or set the password", exp);
}

return false;
}


private Set<IamTotpRecoveryCode> generateRecoveryCodes(IamTotpMfa totpMfa) {
String[] recoveryCodeStrings = recoveryCodeGenerator.generateCodes(RECOVERY_CODE_QUANTITY);
Set<IamTotpRecoveryCode> recoveryCodes = new HashSet<>();
for (String code : recoveryCodeStrings) {
IamTotpRecoveryCode recoveryCode = new IamTotpRecoveryCode(totpMfa);
recoveryCode.setCode(code);
recoveryCodes.add(recoveryCode);
try {
for (String code : recoveryCodeStrings) {
IamTotpRecoveryCode recoveryCode = new IamTotpRecoveryCode(totpMfa);
code = IamTotpMfaEncryptionAndDecryptionUtil.encryptSecretOrRecoveryCode(
defaultModel.getModeOfOperation(),
code,
iamTotpMfaProperties.getPasswordToEncryptOrDecrypt(),
IamTotpMfaEncryptionAndDecryptionUtil.getIVSecureRandom(defaultModel.getModeOfOperation()));
recoveryCode.setCode(code);
recoveryCodes.add(recoveryCode);
}
} catch (Exception exp) {
LOG.error("Please ensure that you either use the same password or set the password", exp);
}

return recoveryCodes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@

import dev.samstevens.totp.recovery.RecoveryCodeGenerator;
import it.infn.mw.iam.audit.events.account.multi_factor_authentication.RecoveryCodesResetEvent;
import it.infn.mw.iam.config.mfa.IamTotpMfaProperties;
import it.infn.mw.iam.core.user.exception.MfaSecretNotFoundException;
import it.infn.mw.iam.persistence.model.IamAccount;
import it.infn.mw.iam.persistence.model.IamTotpMfa;
import it.infn.mw.iam.persistence.model.IamTotpRecoveryCode;
import it.infn.mw.iam.persistence.repository.IamAccountRepository;
import it.infn.mw.iam.persistence.repository.IamTotpMfaRepository;
import it.infn.mw.iam.util.mfa.IamTotpMfaEncryptionAndDecryptionHelper;
import it.infn.mw.iam.util.mfa.IamTotpMfaEncryptionAndDecryptionUtil;
import it.infn.mw.iam.util.mfa.IamTotpMfaInvalidArgumentError;

@Service
public class DefaultIamTotpRecoveryCodeResetService
Expand All @@ -42,14 +46,20 @@ public class DefaultIamTotpRecoveryCodeResetService
private final IamAccountRepository accountRepository;
private final IamTotpMfaRepository totpMfaRepository;
private final RecoveryCodeGenerator recoveryCodeGenerator;
private final IamTotpMfaProperties iamTotpMfaProperties;
private ApplicationEventPublisher eventPublisher;

private static final IamTotpMfaEncryptionAndDecryptionHelper model = IamTotpMfaEncryptionAndDecryptionHelper
.getInstance();

@Autowired
public DefaultIamTotpRecoveryCodeResetService(IamAccountRepository accountRepository,
IamTotpMfaRepository totpMfaRepository, RecoveryCodeGenerator recoveryCodeGenerator) {
IamTotpMfaRepository totpMfaRepository, RecoveryCodeGenerator recoveryCodeGenerator,
IamTotpMfaProperties iamTotpMfaProperties) {
this.accountRepository = accountRepository;
this.totpMfaRepository = totpMfaRepository;
this.recoveryCodeGenerator = recoveryCodeGenerator;
this.iamTotpMfaProperties = iamTotpMfaProperties;
}

private void recoveryCodesResetEvent(IamAccount account, IamTotpMfa totpMfa) {
Expand All @@ -76,21 +86,30 @@ public IamAccount resetRecoveryCodes(IamAccount account) {
IamTotpMfa totpMfa = totpMfaOptional.get();
String[] recoveryCodeStrings = recoveryCodeGenerator.generateCodes(RECOVERY_CODE_QUANTITY);
Set<IamTotpRecoveryCode> recoveryCodes = new HashSet<>();
for (String code : recoveryCodeStrings) {
IamTotpRecoveryCode recoveryCode = new IamTotpRecoveryCode(totpMfa);
recoveryCode.setCode(code);
recoveryCodes.add(recoveryCode);
}

// Attach to account
totpMfa.setRecoveryCodes(recoveryCodes);
totpMfa.touch();
account.touch();
accountRepository.save(account);
totpMfaRepository.save(totpMfa);
recoveryCodesResetEvent(account, totpMfa);
try {
for (String code : recoveryCodeStrings) {
IamTotpRecoveryCode recoveryCode = new IamTotpRecoveryCode(totpMfa);

return account;
}
recoveryCode.setCode(
IamTotpMfaEncryptionAndDecryptionUtil.encryptSecretOrRecoveryCode(
model.getModeOfOperation(), code, iamTotpMfaProperties.getPasswordToEncryptOrDecrypt(),
IamTotpMfaEncryptionAndDecryptionUtil.getIVSecureRandom(model.getModeOfOperation())));
recoveryCodes.add(recoveryCode);
}

// Attach to account
totpMfa.setRecoveryCodes(recoveryCodes);
totpMfa.touch();
account.touch();
accountRepository.save(account);
totpMfaRepository.save(totpMfa);
recoveryCodesResetEvent(account, totpMfa);

return account;
} catch (Exception exp) {
throw new IamTotpMfaInvalidArgumentError(
"Please ensure that you either use the same password or set the password");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,16 @@
import it.infn.mw.iam.api.account.multi_factor_authentication.authenticator_app.error.BadMfaCodeError;
import it.infn.mw.iam.api.common.ErrorDTO;
import it.infn.mw.iam.api.common.NoSuchAccountError;
import it.infn.mw.iam.config.mfa.IamTotpMfaProperties;
import it.infn.mw.iam.core.user.exception.MfaSecretAlreadyBoundException;
import it.infn.mw.iam.core.user.exception.MfaSecretNotFoundException;
import it.infn.mw.iam.core.user.exception.TotpMfaAlreadyEnabledException;
import it.infn.mw.iam.persistence.model.IamAccount;
import it.infn.mw.iam.persistence.model.IamTotpMfa;
import it.infn.mw.iam.persistence.repository.IamAccountRepository;
import it.infn.mw.iam.util.mfa.IamTotpMfaEncryptionAndDecryptionHelper;
import it.infn.mw.iam.util.mfa.IamTotpMfaEncryptionAndDecryptionUtil;
import it.infn.mw.iam.util.mfa.IamTotpMfaInvalidArgumentError;

/**
* Controller for customising user's authenticator app MFA settings Can enable or disable the
Expand All @@ -66,13 +70,19 @@ public class AuthenticatorAppSettingsController {
private final IamTotpMfaService service;
private final IamAccountRepository accountRepository;
private final QrGenerator qrGenerator;
private final IamTotpMfaProperties iamTotpMfaProperties;

public static final IamTotpMfaEncryptionAndDecryptionHelper defaultModel = IamTotpMfaEncryptionAndDecryptionHelper
.getInstance();

@Autowired
public AuthenticatorAppSettingsController(IamTotpMfaService service,
IamAccountRepository accountRepository, QrGenerator qrGenerator) {
IamAccountRepository accountRepository, QrGenerator qrGenerator,
IamTotpMfaProperties iamTotpMfaProperties) {
this.service = service;
this.accountRepository = accountRepository;
this.qrGenerator = qrGenerator;
this.iamTotpMfaProperties = iamTotpMfaProperties;
}


Expand All @@ -92,19 +102,29 @@ public SecretAndDataUriDTO addSecret() {
.orElseThrow(() -> NoSuchAccountError.forUsername(username));

IamTotpMfa totpMfa = service.addTotpMfaSecret(account);
SecretAndDataUriDTO dto = new SecretAndDataUriDTO(totpMfa.getSecret());
String mfaSecret = "";

try {
String dataUri = generateQRCodeFromSecret(totpMfa.getSecret(), account.getUsername());
mfaSecret = IamTotpMfaEncryptionAndDecryptionUtil.decryptSecretOrRecoveryCode(
defaultModel.getModeOfOperation(),
totpMfa.getSecret(), iamTotpMfaProperties.getPasswordToEncryptOrDecrypt());
} catch (Exception exp) {
throw new IamTotpMfaInvalidArgumentError(
"Please use the same password which is used for encryption");
}

try {
SecretAndDataUriDTO dto = new SecretAndDataUriDTO(mfaSecret);

String dataUri = generateQRCodeFromSecret(mfaSecret, account.getUsername());
dto.setDataUri(dataUri);

return dto;
} catch (QrGenerationException e) {
throw new BadMfaCodeError("Could not generate QR code");
}

return dto;
}


/**
* Enable authenticator app MFA on account User sends a TOTP through POST which we verify before
* enabling
Expand Down Expand Up @@ -194,7 +214,6 @@ private String getUsernameFromSecurityContext() {
return auth.getName();
}


/**
* Constructs a data URI for displaying a QR code of the TOTP secret for the user to scan Takes in
* details about the issuer, length of TOTP and period of expiry from application properties
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,16 @@
import it.infn.mw.iam.api.common.ErrorDTO;
import it.infn.mw.iam.authn.multi_factor_authentication.error.MultiFactorAuthenticationError;
import it.infn.mw.iam.authn.multi_factor_authentication.error.NoMultiFactorSecretError;
import it.infn.mw.iam.config.mfa.IamTotpMfaProperties;
import it.infn.mw.iam.core.user.exception.MfaSecretNotFoundException;
import it.infn.mw.iam.persistence.model.IamAccount;
import it.infn.mw.iam.persistence.model.IamTotpMfa;
import it.infn.mw.iam.persistence.model.IamTotpRecoveryCode;
import it.infn.mw.iam.persistence.repository.IamTotpMfaRepository;
import it.infn.mw.iam.util.mfa.IamTotpMfaEncryptionAndDecryptionHelper;
import it.infn.mw.iam.util.mfa.IamTotpMfaEncryptionAndDecryptionUtil;
import it.infn.mw.iam.util.mfa.IamTotpMfaInvalidArgumentError;


/**
* Provides webpages related to recovery codes. Most of this appears if the user chooses to use a
Expand All @@ -57,14 +62,20 @@ public class RecoveryCodeManagementController {
private final AccountUtils accountUtils;
private final IamTotpMfaRepository totpMfaRepository;
private final IamTotpRecoveryCodeResetService recoveryCodeResetService;
private final IamTotpMfaProperties iamTotpMfaProperties;

public static final IamTotpMfaEncryptionAndDecryptionHelper defaultModel = IamTotpMfaEncryptionAndDecryptionHelper
.getInstance();

@Autowired
public RecoveryCodeManagementController(AccountUtils accountUtils,
IamTotpMfaRepository totpMfaRepository,
IamTotpRecoveryCodeResetService recoveryCodeResetService) {
IamTotpRecoveryCodeResetService recoveryCodeResetService,
IamTotpMfaProperties iamTotpMfaProperties) {
this.accountUtils = accountUtils;
this.totpMfaRepository = totpMfaRepository;
this.recoveryCodeResetService = recoveryCodeResetService;
this.iamTotpMfaProperties = iamTotpMfaProperties;
}

/**
Expand Down Expand Up @@ -116,11 +127,17 @@ public String viewRecoveryCodes(ModelMap model) {
List<IamTotpRecoveryCode> recs = new ArrayList<>(totpMfa.getRecoveryCodes());
String[] codes = new String[recs.size()];

for (int i = 0; i < recs.size(); i++) {
codes[i] = recs.get(i).getCode();
}
try {
for (int i = 0; i < recs.size(); i++) {
codes[i] = IamTotpMfaEncryptionAndDecryptionUtil.decryptSecretOrRecoveryCode(
defaultModel.getModeOfOperation(), recs.get(i).getCode(),
iamTotpMfaProperties.getPasswordToEncryptOrDecrypt());
}

return codes;
return codes;
} catch (Exception exp) {
throw new IamTotpMfaInvalidArgumentError("Please use the same password which is used for encryption");
}
}

/**
Expand Down
Loading

0 comments on commit b5a2540

Please sign in to comment.