From c1205218f32fc1a9953abfd64e216a97a3e7a35b Mon Sep 17 00:00:00 2001 From: Jason Pell Date: Fri, 14 Jan 2022 11:24:44 +1100 Subject: [PATCH] add sample code generator to assist with setup verification work flow --- README.md | 15 ++++++++ totp/pom.xml | 12 ++++++ .../totp/code/SampleCodeGenerator.java | 38 +++++++++++++++++++ .../totp/code/SampleCodeGeneratorTest.java | 34 +++++++++++++++++ 4 files changed, 99 insertions(+) create mode 100644 totp/src/main/java/dev/samstevens/totp/code/SampleCodeGenerator.java create mode 100644 totp/src/test/java/dev/samstevens/totp/code/SampleCodeGeneratorTest.java diff --git a/README.md b/README.md index 1716c3c..727321c 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ dependencies { - [Generating QR codes](#generating-a-qr-code) - [Verifying one time passwords](#verifying-one-time-passwords) - [Using different time providers](#using-different-time-providers) +- [Sample codes](#sample-codes) - [Recovery codes](#recovery-codes) @@ -228,6 +229,20 @@ dependencies { } ``` +### Sample Codes + +Sample codes can be used to help verify that a totp device has been setup correctly. + +```java +import dev.samstevens.totp.code.SampleCodeGenerator; +... +// Generate sample codes +final SampleCodeGenerator sampleGenerator = new SampleCodeGenerator( + new DefaultCodeGenerator(), timeProvider); +List sampleCodes = sampleGenerator.generateCodes(secret, 2); +// codes = ["tf8i-exmo-3lcb-slkm", "boyv-yq75-z99k-r308", "w045-mq6w-mg1i-q12o", ...] +``` + ### Recovery Codes Recovery codes can be used to allow users to gain access to their MFA protected account without providing a TOTP, bypassing the MFA process. This is usually given as an option to the user so that in the event of losing access to the device which they have registered the MFA secret with, they are still able to log in. diff --git a/totp/pom.xml b/totp/pom.xml index 3f3b915..9566cff 100644 --- a/totp/pom.xml +++ b/totp/pom.xml @@ -1,6 +1,18 @@ 4.0.0 + + + + org.apache.maven.plugins + maven-compiler-plugin + + 9 + 9 + + + + dev.samstevens.totp diff --git a/totp/src/main/java/dev/samstevens/totp/code/SampleCodeGenerator.java b/totp/src/main/java/dev/samstevens/totp/code/SampleCodeGenerator.java new file mode 100644 index 0000000..fe1d53d --- /dev/null +++ b/totp/src/main/java/dev/samstevens/totp/code/SampleCodeGenerator.java @@ -0,0 +1,38 @@ +package dev.samstevens.totp.code; + +import dev.samstevens.totp.exceptions.CodeGenerationException; +import dev.samstevens.totp.time.TimeProvider; + +public class SampleCodeGenerator { + private final CodeGenerator codeGenerator; + private final TimeProvider timeProvider; + private int timePeriod = 30; + private int allowedTimePeriodDiscrepancy = 1; + + public SampleCodeGenerator(final CodeGenerator codeGenerator, + final TimeProvider timeProvider) { + this.codeGenerator = codeGenerator; + this.timeProvider = timeProvider; + } + + public void setTimePeriod(int timePeriod) { + this.timePeriod = timePeriod; + } + + public void setAllowedTimePeriodDiscrepancy(int allowedTimePeriodDiscrepancy) { + this.allowedTimePeriodDiscrepancy = allowedTimePeriodDiscrepancy; + } + + /** + * Generate sample codes based on the current time. + */ + public String[] generateCodes(final String secret) throws CodeGenerationException { + final String[] sampleCodes = new String[allowedTimePeriodDiscrepancy + allowedTimePeriodDiscrepancy + 1]; + long currentBucket = Math.floorDiv(timeProvider.getTime(), timePeriod); + + for (int x = 0, i = -allowedTimePeriodDiscrepancy; i <= allowedTimePeriodDiscrepancy; i++, x++) { + sampleCodes[x] = codeGenerator.generate(secret, currentBucket + i); + } + return sampleCodes; + } +} diff --git a/totp/src/test/java/dev/samstevens/totp/code/SampleCodeGeneratorTest.java b/totp/src/test/java/dev/samstevens/totp/code/SampleCodeGeneratorTest.java new file mode 100644 index 0000000..7165e8c --- /dev/null +++ b/totp/src/test/java/dev/samstevens/totp/code/SampleCodeGeneratorTest.java @@ -0,0 +1,34 @@ +package dev.samstevens.totp.code; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + +import dev.samstevens.totp.exceptions.CodeGenerationException; +import dev.samstevens.totp.secret.DefaultSecretGenerator; +import dev.samstevens.totp.time.SystemTimeProvider; +import dev.samstevens.totp.time.TimeProvider; + +public class SampleCodeGeneratorTest { + @Test + public void testGenerateSampleCodes() throws CodeGenerationException { + final DefaultSecretGenerator generator = new DefaultSecretGenerator(); + final String secret = generator.generate(); + + final TimeProvider timeProvider = new SystemTimeProvider(); + + final SampleCodeGenerator sampleGenerator = new SampleCodeGenerator( + new DefaultCodeGenerator(), timeProvider); + sampleGenerator.setTimePeriod(30); + sampleGenerator.setAllowedTimePeriodDiscrepancy(2); + + final DefaultCodeVerifier verifier = new DefaultCodeVerifier(new DefaultCodeGenerator(), timeProvider); + verifier.setTimePeriod(30); + verifier.setAllowedTimePeriodDiscrepancy(2); + + final String[] sampleCodes = sampleGenerator.generateCodes(secret); + assertEquals(5, sampleCodes.length); + for (String sampleCode: sampleCodes) { + assertTrue(verifier.isValidCode(secret, sampleCode)); + } + } +}