Support reading base32 encoded OTP secret

Closes #9434
Closes #11561
This commit is contained in:
Pedro Igor
2023-06-20 17:47:10 -03:00
parent 0ecdebc000
commit eb5edb3a9b
9 changed files with 152 additions and 20 deletions
@@ -30,11 +30,11 @@ public class CredentialValidation {
TimeBasedOTP validator = new TimeBasedOTP(credentialModel.getOTPCredentialData().getAlgorithm(),
credentialModel.getOTPCredentialData().getDigits(), credentialModel.getOTPCredentialData().getPeriod(),
lookAheadWindow);
return validator.validateTOTP(token, credentialModel.getOTPSecretData().getValue().getBytes());
return validator.validateTOTP(token, credentialModel.getDecodedSecret());
} else {
HmacOTP validator = new HmacOTP(credentialModel.getOTPCredentialData().getDigits(),
credentialModel.getOTPCredentialData().getAlgorithm(), lookAheadWindow);
int c = validator.validateHOTP(token, credentialModel.getOTPSecretData().getValue(),
int c = validator.validateHOTP(token, credentialModel.getDecodedSecret(),
credentialModel.getOTPCredentialData().getCounter());
return c > -1;
}
@@ -230,7 +230,7 @@ public class RepresentationToModel {
cred.setSecretData("{\"value\":\"" + cred.getHashedSaltedValue() + "\",\"salt\":\"" + cred.getSalt() + "\"}");
cred.setPriority(10);
} else if (OTPCredentialModel.TOTP.equals(cred.getType()) || OTPCredentialModel.HOTP.equals(cred.getType())) {
OTPCredentialData credentialData = new OTPCredentialData(cred.getType(), cred.getDigits(), cred.getCounter(), cred.getPeriod(), cred.getAlgorithm());
OTPCredentialData credentialData = new OTPCredentialData(cred.getType(), cred.getDigits(), cred.getCounter(), cred.getPeriod(), cred.getAlgorithm(), null);
OTPSecretData secretData = new OTPSecretData(cred.getHashedSaltedValue());
cred.setCredentialData(JsonSerialization.writeValueAsString(credentialData));
cred.setSecretData(JsonSerialization.writeValueAsString(secretData));
@@ -54,7 +54,7 @@ public class TimeBasedOTP extends HmacOTP {
*
* @param secretKey the secret key to derive the token from.
*/
public String generateTOTP(String secretKey) {
public String generateTOTP(byte[] secretKey) {
long T = this.clock.getCurrentInterval();
String steps = Long.toHexString(T).toUpperCase();
@@ -67,6 +67,10 @@ public class TimeBasedOTP extends HmacOTP {
return generateOTP(secretKey, steps, this.numberDigits, this.algorithm);
}
public String generateTOTP(String secretKey) {
return generateTOTP(secretKey.getBytes());
}
/**
* <p>Validates a token using a secret key.</p>
*
@@ -88,7 +92,7 @@ public class TimeBasedOTP extends HmacOTP {
steps = "0" + steps;
}
String candidate = generateOTP(new String(secret), steps, this.numberDigits, this.algorithm);
String candidate = generateOTP(secret, steps, this.numberDigits, this.algorithm);
if (candidate.equals(token)) {
return true;
@@ -18,6 +18,9 @@ package org.keycloak.models;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.models.credential.OTPCredentialModel;
import org.keycloak.models.credential.OTPCredentialModel.SecretEncoding;
import org.keycloak.models.utils.Base32;
import org.keycloak.models.utils.TimeBasedOTP;
import java.nio.charset.StandardCharsets;
@@ -55,4 +58,32 @@ public class TotpTest {
Assert.assertTrue("Should accept code with skew offset " + i,totp.validateTOTP(otp, secret.getBytes(StandardCharsets.UTF_8)));
}
}
@Test
public void testBase32EncodedSecret() {
TimeBasedOTP totp = new TimeBasedOTP("HmacSHA1", 8, 60, 1);
String rawSecret = "JNSVMMTEKZCUGSKJIVGHMNSQOZBDA5JT";
String otp = totp.generateTOTP(Base32.decode(rawSecret));
OTPCredentialModel credentialModel = OTPCredentialModel.createTOTP(rawSecret, 8, 30, "HmacSHA1");
Assert.assertFalse(totp.validateTOTP(otp, credentialModel.getDecodedSecret()));
OTPCredentialModel encodedCredential = OTPCredentialModel.createTOTP(rawSecret, 8, 30, "HmacSHA1", SecretEncoding.BASE32.name());
Assert.assertTrue(totp.validateTOTP(otp, encodedCredential.getDecodedSecret()));
}
@Test
public void testBase32BinaryEncodedSecret() {
TimeBasedOTP totp = new TimeBasedOTP("HmacSHA1", 8, 60, 1);
String rawSecret = "CDLYAYRJ73ORTU4PUWWATWSYQCP4H2QL";
String otp = totp.generateTOTP(Base32.decode(rawSecret));
OTPCredentialModel credentialModel = OTPCredentialModel.createTOTP(rawSecret, 8, 30, "HmacSHA1");
Assert.assertFalse(totp.validateTOTP(otp, credentialModel.getDecodedSecret()));
OTPCredentialModel encodedCredential = OTPCredentialModel.createTOTP(rawSecret, 8, 30, "HmacSHA1", SecretEncoding.BASE32.name());
Assert.assertTrue(totp.validateTOTP(otp, encodedCredential.getDecodedSecret()));
}
}