KEYCLOAK-18880 TimeBasedOTP should use look-around to mitigate clock skew
Previously the TimeBasedOTP only looked behind to mitigate clock skew. We now look around (look ahead + look behind) to better accommodate clock skew.
This commit is contained in:
parent
1c2752300b
commit
5898f9c390
1 changed files with 20 additions and 8 deletions
|
@ -42,10 +42,10 @@ public class TimeBasedOTP extends HmacOTP {
|
||||||
* @param algorithm the encryption algorithm
|
* @param algorithm the encryption algorithm
|
||||||
* @param numberDigits the number of digits for tokens
|
* @param numberDigits the number of digits for tokens
|
||||||
* @param timeIntervalInSeconds the number of seconds a token is valid
|
* @param timeIntervalInSeconds the number of seconds a token is valid
|
||||||
* @param lookAheadWindow the number of previous intervals that should be used to validate tokens.
|
* @param lookAroundWindow the number of previous and following intervals that should be used to validate tokens.
|
||||||
*/
|
*/
|
||||||
public TimeBasedOTP(String algorithm, int numberDigits, int timeIntervalInSeconds, int lookAheadWindow) {
|
public TimeBasedOTP(String algorithm, int numberDigits, int timeIntervalInSeconds, int lookAroundWindow) {
|
||||||
super(numberDigits, algorithm, lookAheadWindow);
|
super(numberDigits, algorithm, lookAroundWindow);
|
||||||
this.clock = new Clock(timeIntervalInSeconds);
|
this.clock = new Clock(timeIntervalInSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,8 +60,9 @@ public class TimeBasedOTP extends HmacOTP {
|
||||||
String steps = Long.toHexString(T).toUpperCase();
|
String steps = Long.toHexString(T).toUpperCase();
|
||||||
|
|
||||||
// Just get a 16 digit string
|
// Just get a 16 digit string
|
||||||
while (steps.length() < 16)
|
while (steps.length() < 16) {
|
||||||
steps = "0" + steps;
|
steps = "0" + steps;
|
||||||
|
}
|
||||||
|
|
||||||
return generateOTP(secretKey, steps, this.numberDigits, this.algorithm);
|
return generateOTP(secretKey, steps, this.numberDigits, this.algorithm);
|
||||||
}
|
}
|
||||||
|
@ -71,17 +72,21 @@ public class TimeBasedOTP extends HmacOTP {
|
||||||
*
|
*
|
||||||
* @param token OTP string to validate
|
* @param token OTP string to validate
|
||||||
* @param secret Shared secret
|
* @param secret Shared secret
|
||||||
* @return
|
* @return true of the token is valid
|
||||||
*/
|
*/
|
||||||
public boolean validateTOTP(String token, byte[] secret) {
|
public boolean validateTOTP(String token, byte[] secret) {
|
||||||
long currentInterval = this.clock.getCurrentInterval();
|
long currentInterval = this.clock.getCurrentInterval();
|
||||||
|
|
||||||
for (int i = this.lookAheadWindow; i >= 0; --i) {
|
for (int i = 0; i <= (lookAheadWindow * 2); i++) {
|
||||||
String steps = Long.toHexString(currentInterval - i).toUpperCase();
|
long delta = clockSkewIndexToDelta(i);
|
||||||
|
long adjustedInterval = currentInterval + delta;
|
||||||
|
|
||||||
|
String steps = Long.toHexString(adjustedInterval).toUpperCase();
|
||||||
|
|
||||||
// Just get a 16 digit string
|
// Just get a 16 digit string
|
||||||
while (steps.length() < 16)
|
while (steps.length() < 16) {
|
||||||
steps = "0" + steps;
|
steps = "0" + steps;
|
||||||
|
}
|
||||||
|
|
||||||
String candidate = generateOTP(new String(secret), steps, this.numberDigits, this.algorithm);
|
String candidate = generateOTP(new String(secret), steps, this.numberDigits, this.algorithm);
|
||||||
|
|
||||||
|
@ -93,6 +98,13 @@ public class TimeBasedOTP extends HmacOTP {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* maps 0, 1, 2, 3, 4, 5, 6, 7, ... to 0, -1, 1, -2, 2, -3, 3, ...
|
||||||
|
*/
|
||||||
|
public static long clockSkewIndexToDelta(int idx) {
|
||||||
|
return (idx + 1) / 2 * (1 - (idx % 2) * 2);
|
||||||
|
}
|
||||||
|
|
||||||
public void setCalendar(Calendar calendar) {
|
public void setCalendar(Calendar calendar) {
|
||||||
this.clock.setCalendar(calendar);
|
this.clock.setCalendar(calendar);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue