From 1d28c6c70c23f43831c0229cf8cba282141960c8 Mon Sep 17 00:00:00 2001 From: Bastiaan Jansen Date: Wed, 4 Aug 2021 23:01:19 +0200 Subject: [PATCH 01/11] [maven-release-plugin] prepare release otp-java-1.2.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fa72a8d..2d3da9a 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.github.bastiaanjansen otp-java - 1.2.2-SNAPSHOT + 1.2.2 OTP-Java A small and easy-to-use one-time password generator for Java according to RFC 4226 (HOTP) and RFC 6238 (TOTP). From 131b5ae9f81d6a212ce1135eb96d4a2e4ddb2a3b Mon Sep 17 00:00:00 2001 From: Bastiaan Jansen Date: Wed, 4 Aug 2021 23:01:22 +0200 Subject: [PATCH 02/11] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2d3da9a..ea509a7 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.github.bastiaanjansen otp-java - 1.2.2 + 1.2.3-SNAPSHOT OTP-Java A small and easy-to-use one-time password generator for Java according to RFC 4226 (HOTP) and RFC 6238 (TOTP). From 9f3a835d10d643127ab6dd0f7774186f0e0d3281 Mon Sep 17 00:00:00 2001 From: Bastiaan Jansen <21118262+BastiaanJansen@users.noreply.github.com> Date: Tue, 10 Aug 2021 21:48:44 +0200 Subject: [PATCH 03/11] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 5995cb5..59e76ff 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ A small and easy-to-use one-time password generator for Java according to [RFC 4 * [Usage](#usage) * [HOTP (Counter-based one-time passwords)](#hotp-counter-based-one-time-passwords) * [TOTP (Time-based one-time passwords)](#totp-time-based-one-time-passwords) + * [Recovery codes](#recovery-codes) ## Features The following features are supported: @@ -183,6 +184,11 @@ URI uri = totp.getURI("issuer", "account"); // otpauth://totp/issuer:account?per ``` +## Recovery Codes +Often, services provide "backup codes" or "recovery codes" which can be used when the user cannot access the 2FA device anymore. Often because 2FA device is a mobile phone, which can be lost or stolen. + +Because recovery code generation is not part of the specifications of OTP, it is not possible to generate recovery codes with this library and should be implemented seperately. + ## Licence OTP-Java is available under the MIT licence. See the LICENCE for more info. From 376bfcf85d0e4dce203b9034646ae7ea2119e0d4 Mon Sep 17 00:00:00 2001 From: Bastiaan Jansen Date: Sun, 12 Dec 2021 17:45:20 +0100 Subject: [PATCH 04/11] rewrite totpgenerator tests --- pom.xml | 6 + .../com/bastiaanjansen/otp/TOTPGenerator.java | 9 +- .../bastiaanjansen/otp/TOTPGeneratorTest.java | 211 ++++++++++++++---- 3 files changed, 180 insertions(+), 46 deletions(-) diff --git a/pom.xml b/pom.xml index fa72a8d..59c93e4 100644 --- a/pom.xml +++ b/pom.xml @@ -63,6 +63,12 @@ commons-codec 1.11 + + org.hamcrest + java-hamcrest + 2.0.0.0 + test + diff --git a/src/main/java/com/bastiaanjansen/otp/TOTPGenerator.java b/src/main/java/com/bastiaanjansen/otp/TOTPGenerator.java index b5a5e30..f935288 100644 --- a/src/main/java/com/bastiaanjansen/otp/TOTPGenerator.java +++ b/src/main/java/com/bastiaanjansen/otp/TOTPGenerator.java @@ -255,11 +255,14 @@ public static TOTPGenerator fromOTPAuthURI(final URI uri) throws URISyntaxExcept TOTPGenerator.Builder builder = new TOTPGenerator.Builder(secret.getBytes()); try { - Optional.ofNullable(query.get(URIHelper.DIGITS)).map(Integer::valueOf) + Optional.ofNullable(query.get(URIHelper.DIGITS)) + .map(Integer::valueOf) .ifPresent(builder::withPasswordLength); - Optional.ofNullable(query.get(URIHelper.ALGORITHM)).map(HMACAlgorithm::valueOf) + Optional.ofNullable(query.get(URIHelper.ALGORITHM)) + .map(String::toUpperCase).map(HMACAlgorithm::valueOf) .ifPresent(builder::withAlgorithm); - Optional.ofNullable(query.get(URIHelper.PERIOD)).map(Long::parseLong).map(Duration::ofSeconds) + Optional.ofNullable(query.get(URIHelper.PERIOD)) + .map(Long::parseLong).map(Duration::ofSeconds) .ifPresent(builder::withPeriod); } catch (Exception e) { throw new URISyntaxException(uri.toString(), "URI could not be parsed"); diff --git a/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java b/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java index bbd0ceb..8144d35 100644 --- a/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java +++ b/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java @@ -10,101 +10,226 @@ import java.util.Date; import java.util.GregorianCalendar; +import static org.hamcrest.CoreMatchers.is; import static org.junit.jupiter.api.Assertions.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.CoreMatchers.instanceOf; class TOTPGeneratorTest { - private final byte[] secret = "vv3kox7uqj4kyakohmzpph3us4cjimh6f3zknb5c2oobq6v2kiyhm27q".getBytes(); + private final String secret = "vv3kox7uqj4kyakohmzpph3us4cjimh6f3zknb5c2oobq6v2kiyhm27q"; + + @Test + void constructor_instanceOfTOTPGenerator() { + TOTPGenerator generator = new TOTPGenerator.Builder(secret.getBytes()).build(); + + assertThat(generator, instanceOf(TOTPGenerator.class)); + } @Test void generateBasedOnSecondsPast1970() { - TOTPGenerator generator = new TOTPGenerator(6, Duration.ofSeconds(60), HMACAlgorithm.SHA1, secret); - assertEquals("455216", generator.generate(1)); + TOTPGenerator generator = new TOTPGenerator.Builder(secret.getBytes()).build(); + String expected = "455216"; + + String code = generator.generate(1); + + assertThat(code, is(expected)); } @Test void generateWithEightDigits() { - TOTPGenerator generator = new TOTPGenerator(8, Duration.ofSeconds(30), HMACAlgorithm.SHA1, secret); - assertEquals("17455216", generator.generate(1)); + TOTPGenerator generator = new TOTPGenerator.Builder(secret.getBytes()).withPasswordLength(8).build(); + String expected = "17455216"; + + String code = generator.generate(1); + + assertThat(code, is(expected)); } @Test void generateWithInstant() { - TOTPGenerator generator = new TOTPGenerator(6, Duration.ofSeconds(30), HMACAlgorithm.SHA1, secret); - assertEquals("455216", generator.generate(Instant.ofEpochSecond(1))); + TOTPGenerator generator = new TOTPGenerator.Builder(secret.getBytes()).build(); + String expected = "455216"; + + String code = generator.generate(Instant.ofEpochSecond(1)); + + assertThat(code, is(expected)); } @Test void generateWithDate() { - TOTPGenerator generator = new TOTPGenerator(6, Duration.ofSeconds(30), HMACAlgorithm.SHA1, secret); + TOTPGenerator generator = new TOTPGenerator.Builder(secret.getBytes()).build(); Date date = new GregorianCalendar(2014, Calendar.FEBRUARY, 11).getTime(); - assertEquals("019287", generator.generate(date)); + String expected = "019287"; + + String code = generator.generate(date); + + assertThat(code, is(expected)); } @Test - void generateWithCustomTimeInterval() { - TOTPGenerator generator = new TOTPGenerator(6, Duration.ofSeconds(60), HMACAlgorithm.SHA1, secret); - assertEquals("455216", generator.generate(1)); + void generateWithPeriodOfZero() { + assertThrows(IllegalArgumentException.class, () -> { + new TOTPGenerator.Builder(secret.getBytes()).withPeriod(Duration.ofSeconds(0)).build(); + }); } @Test - void generateWithPeriodOfZero() { + void builderDefaultValues_period() { + TOTPGenerator generator = TOTPGenerator.Builder.withDefaultValues(secret.getBytes()); + Duration expected = Duration.ofSeconds(30); + + assertThat(generator.getPeriod(), is(expected)); + } + + @Test + void builderDefaultValues_algorithm() { + TOTPGenerator generator = TOTPGenerator.Builder.withDefaultValues(secret.getBytes()); + HMACAlgorithm expected = HMACAlgorithm.SHA1; + + assertThat(generator.getAlgorithm(), is(expected)); + } + + @Test + void builderDefaultValues_passwordLength() { + TOTPGenerator generator = TOTPGenerator.Builder.withDefaultValues(secret.getBytes()); + int expected = 6; + + assertThat(generator.getPasswordLength(), is(expected)); + } + + @Test + void getURIWithIssuer_doesNotThrow() { + TOTPGenerator generator = new TOTPGenerator.Builder(secret.getBytes()).build(); + + assertDoesNotThrow(() -> { + generator.getURI("issuer"); + }); + } + + @Test + void getURIWithIssuer() throws URISyntaxException { + TOTPGenerator generator = new TOTPGenerator.Builder(secret.getBytes()).build(); + + URI uri = generator.getURI("issuer"); + assertThat(uri.toString(), is("otpauth://totp/issuer?period=30&digits=6&secret=" + secret + "&algorithm=SHA1")); + } + + @Test + void getURIWithIssuerAndAccount_doesNotThrow() { + TOTPGenerator generator = new TOTPGenerator.Builder(secret.getBytes()).build(); + + assertDoesNotThrow(() -> { + generator.getURI("issuer", "account"); + }); + } + + @Test + void getURIWithIssuerAndAccount() throws URISyntaxException { + TOTPGenerator generator = new TOTPGenerator.Builder(secret.getBytes()).build(); + + + URI uri = generator.getURI("issuer", "account"); + assertThat(uri.toString(), is("otpauth://totp/issuer:account?period=30&digits=6&secret=" + secret + "&algorithm=SHA1")); + } + + @Test + void fromURIWithPeriod() throws URISyntaxException { + URI uri = new URI("otpauth://totp/issuer:account?period=60&secret=" + secret); + + TOTPGenerator generator = TOTPGenerator.Builder.fromOTPAuthURI(uri); + Duration expected = Duration.ofSeconds(60); + + assertThat(generator.getPeriod(), is(expected)); + } + + @Test + void fromURIWithAlgorithmUppercase() throws URISyntaxException { + URI uri = new URI("otpauth://totp/issuer:account?algorithm=SHA1&secret=" + secret); + + TOTPGenerator generator = TOTPGenerator.Builder.fromOTPAuthURI(uri); + HMACAlgorithm expected = HMACAlgorithm.SHA1; + + assertThat(generator.getAlgorithm(), is(expected)); + } + + @Test + void fromURIWithAlgorithmLowercase() throws URISyntaxException { + URI uri = new URI("otpauth://totp/issuer:account?algorithm=sha1&secret=" + secret); + + TOTPGenerator generator = TOTPGenerator.Builder.fromOTPAuthURI(uri); + HMACAlgorithm expected = HMACAlgorithm.SHA1; + + assertThat(generator.getAlgorithm(), is(expected)); + } + + @Test + void fromURIWithDigits() throws URISyntaxException { + URI uri = new URI("otpauth://totp/issuer:account?digits=6&secret=" + secret); + + TOTPGenerator generator = TOTPGenerator.Builder.fromOTPAuthURI(uri); + int expected = 6; + + assertThat(generator.getPasswordLength(), is(expected)); + } + + @Test + void fromURIWithInvalidPeriod_throwsURISyntaxException() throws URISyntaxException { + URI uri = new URI("otpauth://totp/issuer:account?period=invalid&secret=" + secret); + + assertThrows(URISyntaxException.class, () -> { + TOTPGenerator.Builder.fromOTPAuthURI(uri); + }); + } + + @Test + void fromURIWithDigitsIs5_throwsIllegalArgumentException() throws URISyntaxException { + URI uri = new URI("otpauth://totp/issuer:account?digits=5&secret=" + secret); + assertThrows(IllegalArgumentException.class, () -> { - new TOTPGenerator(6, Duration.ofSeconds(0), HMACAlgorithm.SHA1, secret); + TOTPGenerator.Builder.fromOTPAuthURI(uri); }); } @Test - void builderDefaultValues() { - TOTPGenerator generator = TOTPGenerator.Builder.withDefaultValues(secret); - assertEquals(Duration.ofSeconds(30), generator.getPeriod()); - assertEquals(HMACAlgorithm.SHA1, generator.getAlgorithm()); - assertEquals(6, generator.getPasswordLength()); + void fromURIWithDigitsIs9_throwsIllegalArgumentException() throws URISyntaxException { + URI uri = new URI("otpauth://totp/issuer:account?digits=9&secret=" + secret); + + assertThrows(IllegalArgumentException.class, () -> { + TOTPGenerator.Builder.fromOTPAuthURI(uri); + }); } @Test - void getURIWithIssuer() { - TOTPGenerator generator = new TOTPGenerator.Builder(secret) - .withAlgorithm(HMACAlgorithm.SHA1) - .withPasswordLength(8) - .withPeriod(Duration.ofSeconds(30)) - .build(); + void fromURIWithDigitsIs6_doesNotThrow() throws URISyntaxException { + URI uri = new URI("otpauth://totp/issuer:account?digits=6&secret=" + secret); assertDoesNotThrow(() -> { - URI uri = generator.getURI("issuer"); - assertEquals("otpauth://totp/issuer?period=30&digits=8&secret=vv3kox7uqj4kyakohmzpph3us4cjimh6f3zknb5c2oobq6v2kiyhm27q&algorithm=SHA1", uri.toString()); + TOTPGenerator.Builder.fromOTPAuthURI(uri); }); } @Test - void getURIWithIssuerAndAccount() { - TOTPGenerator generator = new TOTPGenerator.Builder(secret) - .withAlgorithm(HMACAlgorithm.SHA256) - .withPasswordLength(6) - .withPeriod(Duration.ofSeconds(60)) - .build(); + void fromURIWithDigitsIs8_doesNotThrow() throws URISyntaxException { + URI uri = new URI("otpauth://totp/issuer:account?digits=8&secret=" + secret); assertDoesNotThrow(() -> { - URI uri = generator.getURI("issuer", "account"); - assertEquals("otpauth://totp/issuer:account?period=60&digits=6&secret=vv3kox7uqj4kyakohmzpph3us4cjimh6f3zknb5c2oobq6v2kiyhm27q&algorithm=SHA256", uri.toString()); + TOTPGenerator.Builder.fromOTPAuthURI(uri); }); } @Test - void fromURI() throws URISyntaxException { - URI uri = new URI("otpauth://totp/issuer:account?period=60&digits=8&secret=vv3kox7uqj4kyakohmzpph3us4cjimh6f3zknb5c2oobq6v2kiyhm27q&algorithm=SHA256"); + void fromURIWithDigitsIs7_doesNotThrow() throws URISyntaxException { + URI uri = new URI("otpauth://totp/issuer:account?digits=7&secret=" + secret); assertDoesNotThrow(() -> { - TOTPGenerator generator = TOTPGenerator.Builder.fromOTPAuthURI(uri); - assertEquals(HMACAlgorithm.SHA256, generator.getAlgorithm()); - assertEquals(8, generator.getPasswordLength()); - assertEquals(Duration.ofSeconds(60), generator.getPeriod()); + TOTPGenerator.Builder.fromOTPAuthURI(uri); }); } @Test - void fromURIThrows() throws URISyntaxException { - URI uri = new URI("otpauth://totp/issuer:account?period=sdsd&digits=sd&secret=vv3kox7uqj4kyakohmzpph3us4cjimh6f3zknb5c2oobq6v2kiyhm27q&algorithm=SHA256"); + void fromURIWithInvalidAlgorithm_throwsURISyntaxException() throws URISyntaxException { + URI uri = new URI("otpauth://totp/issuer:account?algorithm=invalid&secret=" + secret); assertThrows(URISyntaxException.class, () -> { TOTPGenerator.Builder.fromOTPAuthURI(uri); From f43a14015479a0fb52708ece010a42269991bf40 Mon Sep 17 00:00:00 2001 From: Bastiaan Jansen Date: Sun, 12 Dec 2021 17:58:20 +0100 Subject: [PATCH 05/11] secretgenerator test rewrite --- .../otp/SecretGeneratorTest.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/test/java/com/bastiaanjansen/otp/SecretGeneratorTest.java b/src/test/java/com/bastiaanjansen/otp/SecretGeneratorTest.java index 4176a77..d4787de 100644 --- a/src/test/java/com/bastiaanjansen/otp/SecretGeneratorTest.java +++ b/src/test/java/com/bastiaanjansen/otp/SecretGeneratorTest.java @@ -1,24 +1,21 @@ package com.bastiaanjansen.otp; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; class SecretGeneratorTest { - @BeforeEach - void setUp() { - } - - @AfterEach - void tearDown() { + @Test + void generate_defaultLengthIs32() { + int expected = 32; + assertThat(SecretGenerator.generate().length, is(expected)); } @Test - void generate() { - assertEquals(32, SecretGenerator.generate().length); - assertEquals(56, SecretGenerator.generate(256).length); + void generate_lengthIs56() { + int expected = 56; + assertThat(SecretGenerator.generate(256).length, is(expected)); } } \ No newline at end of file From d3bfccee12f9d244b3eec3aad1eebbae9061e91c Mon Sep 17 00:00:00 2001 From: Bastiaan Jansen Date: Mon, 13 Dec 2021 15:13:58 +0100 Subject: [PATCH 06/11] hotpgenerator tests rewrite --- .../com/bastiaanjansen/otp/HOTPGenerator.java | 9 +- .../com/bastiaanjansen/otp/OTPGenerator.java | 2 +- .../bastiaanjansen/otp/HOTPGeneratorTest.java | 158 ++++++++++++++---- .../bastiaanjansen/otp/TOTPGeneratorTest.java | 13 +- 4 files changed, 139 insertions(+), 43 deletions(-) diff --git a/src/main/java/com/bastiaanjansen/otp/HOTPGenerator.java b/src/main/java/com/bastiaanjansen/otp/HOTPGenerator.java index 75f2735..d00d28e 100644 --- a/src/main/java/com/bastiaanjansen/otp/HOTPGenerator.java +++ b/src/main/java/com/bastiaanjansen/otp/HOTPGenerator.java @@ -111,8 +111,13 @@ public static HOTPGenerator fromOTPAuthURI(final URI uri) throws URISyntaxExcept HOTPGenerator.Builder builder = new HOTPGenerator.Builder(secret.getBytes()); try { - Optional.ofNullable(query.get(URIHelper.DIGITS)).map(Integer::parseInt).ifPresent(builder::withPasswordLength); - Optional.ofNullable(query.get(URIHelper.ALGORITHM)).map(HMACAlgorithm::valueOf).ifPresent(builder::withAlgorithm); + Optional.ofNullable(query.get(URIHelper.DIGITS)) + .map(Integer::parseInt) + .ifPresent(builder::withPasswordLength); + Optional.ofNullable(query.get(URIHelper.ALGORITHM)) + .map(String::toUpperCase) + .map(HMACAlgorithm::valueOf) + .ifPresent(builder::withAlgorithm); } catch (Exception e) { throw new URISyntaxException(uri.toString(), "URI could not be parsed"); } diff --git a/src/main/java/com/bastiaanjansen/otp/OTPGenerator.java b/src/main/java/com/bastiaanjansen/otp/OTPGenerator.java index dc937db..e2bb2d2 100644 --- a/src/main/java/com/bastiaanjansen/otp/OTPGenerator.java +++ b/src/main/java/com/bastiaanjansen/otp/OTPGenerator.java @@ -234,7 +234,7 @@ private boolean validatePasswordLength(final int passwordLength) { * @author Bastiaan Jansen * @param concrete builder class */ - public abstract static class Builder { + protected abstract static class Builder { /** * Number of digits for generated code in range 6...8, defaults to 6 */ diff --git a/src/test/java/com/bastiaanjansen/otp/HOTPGeneratorTest.java b/src/test/java/com/bastiaanjansen/otp/HOTPGeneratorTest.java index e6b07fe..148e484 100644 --- a/src/test/java/com/bastiaanjansen/otp/HOTPGeneratorTest.java +++ b/src/test/java/com/bastiaanjansen/otp/HOTPGeneratorTest.java @@ -1,95 +1,179 @@ package com.bastiaanjansen.otp; +import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; import java.net.URI; import java.net.URISyntaxException; +import java.time.Duration; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; class HOTPGeneratorTest { - private final byte[] secret = "vv3kox7uqj4kyakohmzpph3us4cjimh6f3zknb5c2oobq6v2kiyhm27q".getBytes(); + private final String secret = "vv3kox7uqj4kyakohmzpph3us4cjimh6f3zknb5c2oobq6v2kiyhm27q"; + + @Test + void constructor_instanceOfHOTPGenerator() { + HOTPGenerator generator = new HOTPGenerator(6, HMACAlgorithm.SHA1, secret.getBytes()); + + assertThat(generator, instanceOf(HOTPGenerator.class)); + } + + @Test + void constructorWithInvalidPasswordLength_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> { + new HOTPGenerator(5, HMACAlgorithm.SHA1, secret.getBytes()); + }); + } @Test void generateWithSixDigits() { - HOTPGenerator generator = new HOTPGenerator(6, HMACAlgorithm.SHA1, secret); - assertEquals("560287", generator.generateCode(1)); + HOTPGenerator generator = new HOTPGenerator.Builder(secret.getBytes()).build(); + String expected = "560287"; + + assertThat(generator.generateCode(1), is(expected)); } @Test void generateWithSevenDigits() { - HOTPGenerator generator = new HOTPGenerator(7, HMACAlgorithm.SHA1, secret); - assertEquals("1560287", generator.generateCode(1)); + HOTPGenerator generator = new HOTPGenerator.Builder(secret.getBytes()).withPasswordLength(7).build(); + int expected = 7; + + assertThat(generator.generateCode(1).length(), is(expected)); } @Test void generateWithEightDigits() { - HOTPGenerator generator = new HOTPGenerator(8, HMACAlgorithm.SHA1, secret); - assertEquals("61560287", generator.generateCode(1)); + HOTPGenerator generator = new HOTPGenerator.Builder(secret.getBytes()).withPasswordLength(8).build(); + int expected = 8; + + assertThat(generator.generateCode(1).length(), is(expected)); } @Test - void builderDefaultValues() { - HOTPGenerator generator = HOTPGenerator.Builder.withDefaultValues(secret); - assertEquals(HMACAlgorithm.SHA1, generator.getAlgorithm()); - assertEquals(6, generator.getPasswordLength()); + void builderWithAlgorithm_isSHA256() { + HOTPGenerator generator = new HOTPGenerator.Builder(secret.getBytes()).withAlgorithm(HMACAlgorithm.SHA256).build(); + HMACAlgorithm expected = HMACAlgorithm.SHA256; + + assertThat(generator.getAlgorithm(), is(expected)); } @Test - void getURIWithIssuer() { - HOTPGenerator generator = new HOTPGenerator.Builder(secret) - .withAlgorithm(HMACAlgorithm.SHA1) - .withPasswordLength(8) - .build(); + void builderWithDigits_is7() { + HOTPGenerator generator = new HOTPGenerator.Builder(secret.getBytes()).withPasswordLength(7).build(); + int expected = 7; - assertDoesNotThrow(() -> { - URI uri = generator.getURI(10, "issuer"); - assertEquals("otpauth://hotp/issuer?digits=8&counter=10&secret=vv3kox7uqj4kyakohmzpph3us4cjimh6f3zknb5c2oobq6v2kiyhm27q&algorithm=SHA1", uri.toString()); - }); + assertThat(generator.getPasswordLength(), is(expected)); } @Test - void getURIWithIssuerAndAccount() { - HOTPGenerator generator = new HOTPGenerator.Builder(secret) - .withAlgorithm(HMACAlgorithm.SHA256) - .withPasswordLength(6) - .build(); + void builderDefaultValues_algorithm() { + HOTPGenerator generator = HOTPGenerator.Builder.withDefaultValues(secret.getBytes()); + HMACAlgorithm expected = HMACAlgorithm.SHA1; + + assertThat(generator.getAlgorithm(), is(expected)); + } + + @Test + void builderDefaultValues_passwordLength() { + HOTPGenerator generator = HOTPGenerator.Builder.withDefaultValues(secret.getBytes()); + int expected = 6; + + assertThat(generator.getPasswordLength(), is(expected)); + } + + @Test + void getURIWithIssuer_doesNotThrow() { + HOTPGenerator generator = new HOTPGenerator.Builder(secret.getBytes()).build(); assertDoesNotThrow(() -> { - URI uri = generator.getURI(100, "issuer", "account"); - assertEquals("otpauth://hotp/issuer:account?digits=6&counter=100&secret=vv3kox7uqj4kyakohmzpph3us4cjimh6f3zknb5c2oobq6v2kiyhm27q&algorithm=SHA256", uri.toString()); + generator.getURI(10, "issuer"); }); } @Test - void fromURI() throws URISyntaxException { - URI uri = new URI("otpauth://hotp/issuer?digits=8&counter=10&secret=vv3kox7uqj4kyakohmzpph3us4cjimh6f3zknb5c2oobq6v2kiyhm27q&algorithm=SHA256"); + void getURIWithIssuer() throws URISyntaxException { + HOTPGenerator generator = new HOTPGenerator.Builder(secret.getBytes()).build(); + URI uri = generator.getURI(10, "issuer"); + + assertThat( uri.toString(), is("otpauth://hotp/issuer?digits=6&counter=10&secret=" + secret + "&algorithm=SHA1")); + } + + @Test + void getURIWithIssuerAndAccount_doesNotThrow() { + HOTPGenerator generator = new HOTPGenerator.Builder(secret.getBytes()).build(); assertDoesNotThrow(() -> { - HOTPGenerator generator = HOTPGenerator.Builder.fromOTPAuthURI(uri); - assertEquals(HMACAlgorithm.SHA256, generator.getAlgorithm()); - assertEquals(8, generator.getPasswordLength()); + generator.getURI(100, "issuer", "account"); }); } @Test - void fromURIThrowsWhenSecretNotProvided() throws URISyntaxException { - URI uri = new URI("otpauth://hotp/issuer?digits=8&counter=10&algorithm=SHA256"); + void getURIWithIssuerAndAccount() throws URISyntaxException { + HOTPGenerator generator = new HOTPGenerator.Builder(secret.getBytes()).build(); - assertThrows(IllegalArgumentException.class, () -> { + URI uri = generator.getURI(100, "issuer", "account"); + assertThat(uri.toString(), is("otpauth://hotp/issuer:account?digits=6&counter=100&secret=" + secret + "&algorithm=SHA1")); + } + + @Test + void fromURIWithAlgorithmUppercase() throws URISyntaxException { + URI uri = new URI("otpauth://hotp/issuer?counter=10&algorithm=SHA256&secret=" + secret); + + HOTPGenerator generator = HOTPGenerator.Builder.fromOTPAuthURI(uri); + HMACAlgorithm expected = HMACAlgorithm.SHA256; + + assertThat(generator.getAlgorithm(), is(expected)); + } + + @Test + void fromURIWithAlgorithmLowercase() throws URISyntaxException { + URI uri = new URI("otpauth://hotp/issuer?counter=10&algorithm=sha256&secret=" + secret); + HOTPGenerator generator = HOTPGenerator.Builder.fromOTPAuthURI(uri); + HMACAlgorithm expected = HMACAlgorithm.SHA256; + + assertThat(generator.getAlgorithm(), is(expected)); + } + + @Test + void fromURIWithDigitsIs7() throws URISyntaxException { + URI uri = new URI("otpauth://hotp/issuer?digits=7&counter=10&secret=" + secret); + HOTPGenerator generator = HOTPGenerator.Builder.fromOTPAuthURI(uri); + int expected = 7; + + assertThat(generator.getPasswordLength(), is(expected)); + } + + @Test + void fromURIWithInvalidDigits_throwsURISyntaxException() throws URISyntaxException { + URI uri = new URI("otpauth://hotp/issuer?digits=invalid&counter=10&secret=" + secret); + + assertThrows(URISyntaxException.class, () -> { HOTPGenerator.Builder.fromOTPAuthURI(uri); }); } @Test - void fromURIThrows() throws URISyntaxException { - URI uri = new URI("otpauth://hotp/issuer?digits=sd&counter=10&secret=vv3kox7uqj4kyakohmzpph3us4cjimh6f3zknb5c2oobq6v2kiyhm27q&algorithm=SHA256"); + void fromURIWithInvalidAlgorithm_throwsURISyntaxException() throws URISyntaxException { + URI uri = new URI("otpauth://hotp/issuer?counter=10&algorithm=invalid&secret=" + secret); assertThrows(URISyntaxException.class, () -> { HOTPGenerator.Builder.fromOTPAuthURI(uri); }); } + + @Test + void fromURIWithInvalidSecret_throwsIllegalArgumentException() throws URISyntaxException { + URI uri = new URI("otpauth://hotp/issuer?counter=10"); + + assertThrows(IllegalArgumentException.class, () -> { + HOTPGenerator.Builder.fromOTPAuthURI(uri); + }); + } } \ No newline at end of file diff --git a/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java b/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java index 5862a26..3cbfad3 100644 --- a/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java +++ b/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java @@ -22,11 +22,18 @@ class TOTPGeneratorTest { @Test void constructor_instanceOfTOTPGenerator() { - TOTPGenerator generator = new TOTPGenerator.Builder(secret.getBytes()).build(); + TOTPGenerator generator = new TOTPGenerator(6, Duration.ofSeconds(30), HMACAlgorithm.SHA1, secret.getBytes()); assertThat(generator, instanceOf(TOTPGenerator.class)); } + @Test + void constructorWithInvalidPasswordLength_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> { + new TOTPGenerator(5, Duration.ofSeconds(30), HMACAlgorithm.SHA1, secret.getBytes()); + }); + } + @Test void generateBasedOnSecondsPast1970() { TOTPGenerator generator = new TOTPGenerator.Builder(secret.getBytes()).build(); @@ -40,11 +47,11 @@ void generateBasedOnSecondsPast1970() { @Test void generateWithEightDigits() { TOTPGenerator generator = new TOTPGenerator.Builder(secret.getBytes()).withPasswordLength(8).build(); - String expected = "17455216"; + int expected = 8; String code = generator.generate(1); - assertThat(code, is(expected)); + assertThat(code.length(), is(expected)); } @Test From 22349ec570eb99145c6f46cb9ec62a95046bf2e1 Mon Sep 17 00:00:00 2001 From: Bastiaan Jansen Date: Mon, 13 Dec 2021 17:46:23 +0100 Subject: [PATCH 07/11] added otp generator tests --- .../com/bastiaanjansen/otp/OTPGenerator.java | 9 +- .../bastiaanjansen/otp/HOTPGeneratorTest.java | 19 +- .../bastiaanjansen/otp/OTPGeneratorTest.java | 172 ++++++++++++++++-- .../bastiaanjansen/otp/TOTPGeneratorTest.java | 27 +-- 4 files changed, 173 insertions(+), 54 deletions(-) diff --git a/src/main/java/com/bastiaanjansen/otp/OTPGenerator.java b/src/main/java/com/bastiaanjansen/otp/OTPGenerator.java index e2bb2d2..fcb0638 100644 --- a/src/main/java/com/bastiaanjansen/otp/OTPGenerator.java +++ b/src/main/java/com/bastiaanjansen/otp/OTPGenerator.java @@ -45,9 +45,11 @@ public class OTPGenerator { * @param secret used to generate hash */ protected OTPGenerator(final int passwordLength, final HMACAlgorithm algorithm, final byte[] secret) { - if (!validatePasswordLength(passwordLength)) { + if (!validatePasswordLength(passwordLength)) throw new IllegalArgumentException("Password length must be between 6 and 8 digits"); - } + + if (secret.length <= 0) + throw new IllegalArgumentException("Secret must not be empty"); this.passwordLength = passwordLength; this.algorithm = algorithm; @@ -104,6 +106,9 @@ public boolean verify(final String code, final long counter, final int delayWind * @throws IllegalStateException when hashing algorithm throws an error */ protected String generateCode(final long counter) throws IllegalStateException { + if (counter < 0) + throw new IllegalArgumentException("Counter must be greater than or equal to 0"); + byte[] secretBytes = decodeBase32(secret); byte[] counterBytes = longToBytes(counter); diff --git a/src/test/java/com/bastiaanjansen/otp/HOTPGeneratorTest.java b/src/test/java/com/bastiaanjansen/otp/HOTPGeneratorTest.java index 148e484..7e42c52 100644 --- a/src/test/java/com/bastiaanjansen/otp/HOTPGeneratorTest.java +++ b/src/test/java/com/bastiaanjansen/otp/HOTPGeneratorTest.java @@ -1,16 +1,13 @@ package com.bastiaanjansen.otp; -import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; import java.net.URI; import java.net.URISyntaxException; -import java.time.Duration; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -27,9 +24,7 @@ void constructor_instanceOfHOTPGenerator() { @Test void constructorWithInvalidPasswordLength_throwsIllegalArgumentException() { - assertThrows(IllegalArgumentException.class, () -> { - new HOTPGenerator(5, HMACAlgorithm.SHA1, secret.getBytes()); - }); + assertThrows(IllegalArgumentException.class, () -> new HOTPGenerator(5, HMACAlgorithm.SHA1, secret.getBytes())); } @Test @@ -154,26 +149,20 @@ void fromURIWithDigitsIs7() throws URISyntaxException { void fromURIWithInvalidDigits_throwsURISyntaxException() throws URISyntaxException { URI uri = new URI("otpauth://hotp/issuer?digits=invalid&counter=10&secret=" + secret); - assertThrows(URISyntaxException.class, () -> { - HOTPGenerator.Builder.fromOTPAuthURI(uri); - }); + assertThrows(URISyntaxException.class, () -> HOTPGenerator.Builder.fromOTPAuthURI(uri)); } @Test void fromURIWithInvalidAlgorithm_throwsURISyntaxException() throws URISyntaxException { URI uri = new URI("otpauth://hotp/issuer?counter=10&algorithm=invalid&secret=" + secret); - assertThrows(URISyntaxException.class, () -> { - HOTPGenerator.Builder.fromOTPAuthURI(uri); - }); + assertThrows(URISyntaxException.class, () -> HOTPGenerator.Builder.fromOTPAuthURI(uri)); } @Test void fromURIWithInvalidSecret_throwsIllegalArgumentException() throws URISyntaxException { URI uri = new URI("otpauth://hotp/issuer?counter=10"); - assertThrows(IllegalArgumentException.class, () -> { - HOTPGenerator.Builder.fromOTPAuthURI(uri); - }); + assertThrows(IllegalArgumentException.class, () -> HOTPGenerator.Builder.fromOTPAuthURI(uri)); } } \ No newline at end of file diff --git a/src/test/java/com/bastiaanjansen/otp/OTPGeneratorTest.java b/src/test/java/com/bastiaanjansen/otp/OTPGeneratorTest.java index ede3da2..1113bd3 100644 --- a/src/test/java/com/bastiaanjansen/otp/OTPGeneratorTest.java +++ b/src/test/java/com/bastiaanjansen/otp/OTPGeneratorTest.java @@ -2,41 +2,177 @@ import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; class OTPGeneratorTest { - private final byte[] secret = "vv3kox7uqj4kyakohmzpph3us4cjimh6f3zknb5c2oobq6v2kiyhm27q".getBytes(); + private final String secret = "vv3kox7uqj4kyakohmzpph3us4cjimh6f3zknb5c2oobq6v2kiyhm27q"; + + @Test + void constructorWithEmptySecret_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> new OTPGenerator(6, HMACAlgorithm.SHA1, new byte[]{})); + } + + @Test + void constructorWithPasswordLengthIs5_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> new OTPGenerator(5, HMACAlgorithm.SHA1, secret.getBytes())); + } + + @Test + void constructorWithPasswordLengthIs9_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> new OTPGenerator(9, HMACAlgorithm.SHA1, secret.getBytes())); + } + + @Test + void constructorWithAlgorithmSHA1() { + OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA1, secret.getBytes()); + HMACAlgorithm expected = HMACAlgorithm.SHA1; + + assertThat(generator.getAlgorithm(), is(expected)); + } + + @Test + void constructorWithAlgorithmSHA256() { + OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA256, secret.getBytes()); + HMACAlgorithm expected = HMACAlgorithm.SHA256; + + assertThat(generator.getAlgorithm(), is(expected)); + } + + @Test + void constructorWithAlgorithmSHA512() { + OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA512, secret.getBytes()); + HMACAlgorithm expected = HMACAlgorithm.SHA512; + + assertThat(generator.getAlgorithm(), is(expected)); + } + + @Test + void constructorWithPasswordLengthIs6() { + OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA1, secret.getBytes()); + int expected = 6; + + assertThat(generator.getPasswordLength(), is(expected)); + } + + @Test + void generateWithPasswordLengthIs6() { + OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA1, secret.getBytes()); + int expected = 6; + + assertThat(generator.generateCode(1).length(), is(expected)); + } + + @Test + void generateWithPasswordLengthIs7() { + OTPGenerator generator = new OTPGenerator(7, HMACAlgorithm.SHA1, secret.getBytes()); + int expected = 7; + + assertThat(generator.generateCode(1).length(), is(expected)); + } + + @Test + void generateWithPasswordLengthIs8() { + OTPGenerator generator = new OTPGenerator(8, HMACAlgorithm.SHA1, secret.getBytes()); + int expected = 8; + + assertThat(generator.generateCode(1).length(), is(expected)); + } @Test void generateWithSHA1() { - OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA1, secret); - assertEquals("560287", generator.generateCode(1)); + OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA1, secret.getBytes()); + String expected = "560287"; + + assertThat(generator.generateCode(1), is(expected)); } @Test void generateWithSHA256() { - OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA256, secret); - assertEquals("361406", generator.generateCode(1)); + OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA256, secret.getBytes()); + String expected = "361406"; + + assertThat(generator.generateCode(1), is(expected)); } @Test void generateWithSHA512() { - OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA512, secret); - assertEquals("016738", generator.generateCode(1)); + OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA512, secret.getBytes()); + String expected = "016738"; + + assertThat(generator.generateCode(1), is(expected)); + } + + @Test + void generateWithCounterIs2() { + OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA1, secret.getBytes()); + String expected = "447843"; + + assertThat(generator.generateCode(2), is(expected)); + } + + @Test + void generateWithCounterIs100() { + OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA1, secret.getBytes()); + String expected = "239543"; + + assertThat(generator.generateCode(100), is(expected)); + } + + @Test + void generateWithInvalidCounter_throwsIllegalArgumentException() { + OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA1, secret.getBytes()); + + assertThrows(IllegalArgumentException.class, () -> generator.generateCode(-1)); + } + + @Test + void verifyWithCounterIs10AndDelayWindowIs0_true() { + OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA1, secret.getBytes()); + String code = generator.generateCode(10); + + assertThat(generator.verify(code, 10), is(true)); } @Test - void verifyCode() { - OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA1, secret); + void verifyWithCounterIs9AndDelayWindowIs0_false() { + OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA1, secret.getBytes()); String code = generator.generateCode(10); - assertTrue(generator.verify(code, 10)); - assertFalse(generator.verify(code, 9)); - assertTrue(generator.verify(code, 9, 1)); - assertTrue(generator.verify(code, 11, 1)); - assertTrue(generator.verify(code, 8, 2)); - assertFalse(generator.verify(code, 20, 2)); + + assertThat(generator.verify(code, 9), is(false)); + } + + @Test + void verifyWithCounterIs9AndDelayWindowIs1_true() { + OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA1, secret.getBytes()); + String code = generator.generateCode(10); + + assertThat(generator.verify(code, 9, 1), is(true)); + } + + @Test + void verifyWithCounterIs11AndDelayWindowIs1_true() { + OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA1, secret.getBytes()); + String code = generator.generateCode(10); + + assertThat(generator.verify(code, 11, 1), is(true)); + } + + @Test + void verifyWithCounterIs8AndDelayWindowIs2_true() { + OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA1, secret.getBytes()); + String code = generator.generateCode(10); + + assertThat(generator.verify(code, 8, 2), is(true)); + } + + @Test + void verifyWithCounterIs20AndDelayWindowIs2_false() { + OTPGenerator generator = new OTPGenerator(6, HMACAlgorithm.SHA1, secret.getBytes()); + String code = generator.generateCode(10); + + assertThat(generator.verify(code, 20, 2), is(false)); } } \ No newline at end of file diff --git a/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java b/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java index 3cbfad3..4bd1bdd 100644 --- a/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java +++ b/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java @@ -12,7 +12,8 @@ import java.util.concurrent.TimeUnit; import static org.hamcrest.CoreMatchers.is; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.CoreMatchers.instanceOf; @@ -29,9 +30,7 @@ void constructor_instanceOfTOTPGenerator() { @Test void constructorWithInvalidPasswordLength_throwsIllegalArgumentException() { - assertThrows(IllegalArgumentException.class, () -> { - new TOTPGenerator(5, Duration.ofSeconds(30), HMACAlgorithm.SHA1, secret.getBytes()); - }); + assertThrows(IllegalArgumentException.class, () -> new TOTPGenerator(5, Duration.ofSeconds(30), HMACAlgorithm.SHA1, secret.getBytes())); } @Test @@ -77,9 +76,7 @@ void generateWithDate() { @Test void generateWithPeriodOfZero() { - assertThrows(IllegalArgumentException.class, () -> { - new TOTPGenerator.Builder(secret.getBytes()).withPeriod(Duration.ofSeconds(0)).build(); - }); + assertThrows(IllegalArgumentException.class, () -> new TOTPGenerator.Builder(secret.getBytes()).withPeriod(Duration.ofSeconds(0)).build()); } @Test @@ -221,27 +218,21 @@ void fromURIWithDigits() throws URISyntaxException { void fromURIWithInvalidPeriod_throwsURISyntaxException() throws URISyntaxException { URI uri = new URI("otpauth://totp/issuer:account?period=invalid&secret=" + secret); - assertThrows(URISyntaxException.class, () -> { - TOTPGenerator.Builder.fromOTPAuthURI(uri); - }); + assertThrows(URISyntaxException.class, () -> TOTPGenerator.Builder.fromOTPAuthURI(uri)); } @Test void fromURIWithDigitsIs5_throwsIllegalArgumentException() throws URISyntaxException { URI uri = new URI("otpauth://totp/issuer:account?digits=5&secret=" + secret); - assertThrows(IllegalArgumentException.class, () -> { - TOTPGenerator.Builder.fromOTPAuthURI(uri); - }); + assertThrows(IllegalArgumentException.class, () -> TOTPGenerator.Builder.fromOTPAuthURI(uri)); } @Test void fromURIWithDigitsIs9_throwsIllegalArgumentException() throws URISyntaxException { URI uri = new URI("otpauth://totp/issuer:account?digits=9&secret=" + secret); - assertThrows(IllegalArgumentException.class, () -> { - TOTPGenerator.Builder.fromOTPAuthURI(uri); - }); + assertThrows(IllegalArgumentException.class, () -> TOTPGenerator.Builder.fromOTPAuthURI(uri)); } @Test @@ -275,8 +266,6 @@ void fromURIWithDigitsIs7_doesNotThrow() throws URISyntaxException { void fromURIWithInvalidAlgorithm_throwsURISyntaxException() throws URISyntaxException { URI uri = new URI("otpauth://totp/issuer:account?algorithm=invalid&secret=" + secret); - assertThrows(URISyntaxException.class, () -> { - TOTPGenerator.Builder.fromOTPAuthURI(uri); - }); + assertThrows(URISyntaxException.class, () -> TOTPGenerator.Builder.fromOTPAuthURI(uri)); } } \ No newline at end of file From dc87cd642b4f988114768ab76ec473c350167330 Mon Sep 17 00:00:00 2001 From: Bastiaan Jansen <21118262+BastiaanJansen@users.noreply.github.com> Date: Mon, 13 Dec 2021 17:47:14 +0100 Subject: [PATCH 08/11] updated library version --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 59e76ff..f019db2 100644 --- a/README.md +++ b/README.md @@ -31,23 +31,23 @@ The following features are supported: com.github.bastiaanjansen otp-java - 1.2.2 + 1.2.3 ``` ### Gradle ```gradle -implementation 'com.github.bastiaanjansen:otp-java:1.2.1' +implementation 'com.github.bastiaanjansen:otp-java:1.2.3' ``` ### Scala SBT ```scala -libraryDependencies += "com.github.bastiaanjansen" % "otp-java" % "1.2.2" +libraryDependencies += "com.github.bastiaanjansen" % "otp-java" % "1.2.3" ``` ### Apache Ivy ```xml - + ``` Or you can download the source from the [GitHub releases page](https://github.com/BastiaanJansen/OTP-Java/releases). From c1099519ffe6e7fd1258b1f3ff1c0fce9e72412f Mon Sep 17 00:00:00 2001 From: Bastiaan Jansen Date: Mon, 13 Dec 2021 18:24:20 +0100 Subject: [PATCH 09/11] fixed maven test command not running tests --- pom.xml | 267 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 136 insertions(+), 131 deletions(-) diff --git a/pom.xml b/pom.xml index ac633e6..2b1b1ac 100644 --- a/pom.xml +++ b/pom.xml @@ -1,132 +1,137 @@ - - - 4.0.0 - - - org.sonatype.oss - oss-parent - 9 - - - com.github.bastiaanjansen - otp-java - 1.2.3-SNAPSHOT - - OTP-Java - A small and easy-to-use one-time password generator for Java according to RFC 4226 (HOTP) and RFC 6238 (TOTP). - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - - - MIT Licence - https://github.com/BastiaanJansen/OTP-Java/blob/main/LICENSE - repo - - - - - 1.8 - 1.8 - - - - scm:git:https://github.com/BastiaanJansen/OTP-Java.git - http://github.com/BastiaanJansen/OTP-Java - scm:git:https://github.com/BastiaanJansen/OTP-Java.git - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - test - - - org.junit.jupiter - junit-jupiter - 5.7.0 - test - - - commons-codec - commons-codec - 1.11 - - - org.hamcrest - java-hamcrest - 2.0.0.0 - test - - - - - - - org.apache.maven.plugins - maven-source-plugin - 3.2.0 - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - 8 - - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.7 - true - - ossrh - https://oss.sonatype.org/ - true - - - - + + + 4.0.0 + + + org.sonatype.oss + oss-parent + 9 + + + com.github.bastiaanjansen + otp-java + 1.2.3-SNAPSHOT + + OTP-Java + A small and easy-to-use one-time password generator for Java according to RFC 4226 (HOTP) and RFC 6238 (TOTP). + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + MIT Licence + https://github.com/BastiaanJansen/OTP-Java/blob/main/LICENSE + repo + + + + + 1.8 + 1.8 + + + + scm:git:https://github.com/BastiaanJansen/OTP-Java.git + http://github.com/BastiaanJansen/OTP-Java + scm:git:https://github.com/BastiaanJansen/OTP-Java.git + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + test + + + org.junit.jupiter + junit-jupiter + 5.8.2 + test + + + commons-codec + commons-codec + 1.11 + + + org.hamcrest + java-hamcrest + 2.0.0.0 + test + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.0 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + 8 + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.7 + true + + ossrh + https://oss.sonatype.org/ + true + + + + \ No newline at end of file From 272a853e65a1232b75905009a645566b1b54579c Mon Sep 17 00:00:00 2001 From: Bastiaan Jansen Date: Mon, 13 Dec 2021 18:31:46 +0100 Subject: [PATCH 10/11] new unit tests --- .../bastiaanjansen/otp/TOTPGeneratorTest.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java b/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java index 4bd1bdd..9d400f3 100644 --- a/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java +++ b/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java @@ -63,17 +63,34 @@ void generateWithInstant() { assertThat(code, is(expected)); } + @Test + void generateWithSeconds() { + TOTPGenerator generator = new TOTPGenerator.Builder(secret.getBytes()).build(); + String expected = "650012"; + + String code = generator.generate(100); + + assertThat(code, is(expected)); + } + @Test void generateWithDate() { TOTPGenerator generator = new TOTPGenerator.Builder(secret.getBytes()).build(); - Date date = new GregorianCalendar(2014, Calendar.FEBRUARY, 11).getTime(); - String expected = "019287"; + Date date = new Date(Long.parseLong("100") * 1000); + String expected = "650012"; String code = generator.generate(date); assertThat(code, is(expected)); } + @Test + void generateWithInvalidSeconds_throwsIllegalArgumentException() { + TOTPGenerator generator = new TOTPGenerator.Builder(secret.getBytes()).build(); + + assertThrows(IllegalArgumentException.class, () -> generator.generate(-1)); + } + @Test void generateWithPeriodOfZero() { assertThrows(IllegalArgumentException.class, () -> new TOTPGenerator.Builder(secret.getBytes()).withPeriod(Duration.ofSeconds(0)).build()); From 61a23c6ee3681a11e4c8df88f514295e9a944d2a Mon Sep 17 00:00:00 2001 From: Bastiaan Jansen Date: Mon, 13 Dec 2021 18:32:51 +0100 Subject: [PATCH 11/11] new unit tests --- src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java b/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java index 9d400f3..e7b1f69 100644 --- a/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java +++ b/src/test/java/com/bastiaanjansen/otp/TOTPGeneratorTest.java @@ -76,7 +76,8 @@ void generateWithSeconds() { @Test void generateWithDate() { TOTPGenerator generator = new TOTPGenerator.Builder(secret.getBytes()).build(); - Date date = new Date(Long.parseLong("100") * 1000); + int secondsSince1970 = 100; + Date date = new Date(secondsSince1970 * 1000); String expected = "650012"; String code = generator.generate(date);