Skip to content

Commit

Permalink
Add an OTP Auth URI builder; Update of dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelkliemannel committed Aug 23, 2022
1 parent a219170 commit dc52067
Show file tree
Hide file tree
Showing 12 changed files with 584 additions and 46 deletions.
38 changes: 32 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ This library is available at [Maven Central](https://mvnrepository.com/artifact/

```java
// Groovy
implementation 'dev.turingcomplete:kotlin-onetimepassword:2.3.0'
implementation 'dev.turingcomplete:kotlin-onetimepassword:2.4.0'

// Kotlin
implementation("dev.turingcomplete:kotlin-onetimepassword:2.3.0")
implementation("dev.turingcomplete:kotlin-onetimepassword:2.4.0")
```

### Maven
Expand All @@ -37,7 +37,7 @@ implementation("dev.turingcomplete:kotlin-onetimepassword:2.3.0")
<dependency>
<groupId>dev.turingcomplete</groupId>
<artifactId>kotlin-onetimepassword</artifactId>
<version>2.2.0</version>
<version>2.4.0</version>
</dependency>
```

Expand Down Expand Up @@ -173,12 +173,38 @@ There is also a helper method ```GoogleAuthenticator.createRandomSecretAsByteArr
Some generators limit the length of the **plain text secret** or set a fixed number of characters. So the "Google way", which has a fixed value of 10 characters. Anything outside this range will not be handled correctly by some generators.


#### QR Code
#### Key URI Format and QR Code

QR codes must use a URI that follows the definition in [Key Uri Format](https://github.com/google/google-authenticator/wiki/Key-Uri-Format). The secret in this URI is the Base32-encoded one.
The [Key Uri Format](https://github.com/google/google-authenticator/wiki/Key-Uri-Format) specification defines a URI which can carry all generator configuration values. This URI can be embedded inside a QR code, which makes the setup of an OTP account in OTP Apps easy and error-free.

This library provides the `OtpAuthUriBuilder` do generate such a URI. For example:
```kotlin
OtpAuthUriBuilder.forTotp(Base32().encode("secret".toByteArray()))
.label("John", "Company")
.issuer("Company")
.digits(8)
.buildToString()
```
Would generate the URI:
```text
otpauth://totp/Company:John/?issuer=Company&digits=8&secret=ONSWG4TFOQ
```

Note that according to the specification, the Base32 padding character `=` will be removed in the `secret` parameter value.

All three generator are providing the method `otpAuthUriBuilder()` to create an `OtpAuthUriBuilder` which already has all the configuration values set. For example:
```kotlin
GoogleAuthenticator(Base32().encode("secret".toByteArray()))
.otpAuthUriBuilder()
.issuer("Company")
.buildToString()
```
Would generate the URI:
```text
otpauth://totp/?algorithm=SHA1&digits=6&period=30&issuer=Company&secret=ONSWG4TFOQ
```

#### Simulate the Google Authenticator
### Simulate the Google Authenticator

The directory ```example/googleauthenticator``` contains a simple JavaFX application to simulate the Google Authenticator:

Expand Down
12 changes: 6 additions & 6 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import java.net.URI

plugins {
`java-library`
kotlin("jvm") version "1.3.41"
id("org.jetbrains.dokka") version "1.4.32"
kotlin("jvm") version "1.7.10"
id("org.jetbrains.dokka") version "1.7.10"

signing
`maven-publish`
}

allprojects {
group = "dev.turingcomplete"
version = "2.3.0"
version = "2.4.0"

repositories {
mavenLocal()
Expand Down Expand Up @@ -56,15 +56,15 @@ dependencies {
implementation(kotlin("stdlib"))
implementation("commons-codec:commons-codec:1.15")

val jUnitVersion = "5.8.2"
val jUnitVersion = "5.9.0"
testImplementation("org.junit.jupiter:junit-jupiter-params:$jUnitVersion")
testImplementation("org.junit.jupiter:junit-jupiter-api:$jUnitVersion")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$jUnitVersion")

testImplementation("com.github.bastiaanjansen:otp-java:1.3.0") {
testImplementation("com.github.bastiaanjansen:otp-java:1.3.2") {
because("For `OtherLibrariesComparisonTest`")
}
testImplementation("com.eatthepath:java-otp:0.3.1") {
testImplementation("com.eatthepath:java-otp:0.4.0") {
because("For `OtherLibrariesComparisonTest`")
}
testImplementation("com.j256.two-factor-auth:two-factor-auth:1.3") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import java.util.concurrent.TimeUnit
*
* @param base32secret the shared Base32-encoded-encoded secret.
*/
class GoogleAuthenticator(base32secret: ByteArray) {
class GoogleAuthenticator(private val base32secret: ByteArray) {
// -- Companion Object -------------------------------------------------------------------------------------------- //

companion object {
Expand All @@ -40,6 +40,8 @@ class GoogleAuthenticator(base32secret: ByteArray) {
val randomSecret = RandomSecretGenerator().createRandomSecret(10)
return Base32().encode(randomSecret)
}

val CONFIG = TimeBasedOneTimePasswordConfig(30, TimeUnit.SECONDS, 6, HmacAlgorithm.SHA1)
}

// -- Properties -------------------------------------------------------------------------------------------------- //
Expand All @@ -49,10 +51,7 @@ class GoogleAuthenticator(base32secret: ByteArray) {
// -- Initialization ---------------------------------------------------------------------------------------------- //

init {
val hmacAlgorithm = HmacAlgorithm.SHA1
val config = TimeBasedOneTimePasswordConfig(30, TimeUnit.SECONDS, 6, hmacAlgorithm)

timeBasedOneTimePasswordGenerator = TimeBasedOneTimePasswordGenerator(Base32().decode(base32secret), config)
timeBasedOneTimePasswordGenerator = TimeBasedOneTimePasswordGenerator(Base32().decode(base32secret), CONFIG)
}

@Deprecated("Use ByteArray representation",
Expand Down Expand Up @@ -82,6 +81,13 @@ class GoogleAuthenticator(base32secret: ByteArray) {
return code == generate(timestamp)
}

/**
* Creates an [OtpAuthUriBuilder], which pre-configured with the secret, as
* well as the fixed Google authenticator configuration for the algorithm,
* code digits and time step.
*/
fun otpAuthUriBuilder(): OtpAuthUriBuilder.Totp = timeBasedOneTimePasswordGenerator.otpAuthUriBuilder()

// -- Private Methods --------------------------------------------------------------------------------------------- //
// -- Inner Type -------------------------------------------------------------------------------------------------- //
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package dev.turingcomplete.kotlinonetimepassword

import java.lang.IllegalArgumentException

/**
* The configuration for the [HmacOneTimePasswordGenerator].
*
Expand All @@ -20,7 +18,7 @@ import java.lang.IllegalArgumentException
*
* @throws IllegalArgumentException if `codeDigits` is negative.
*/
open class HmacOneTimePasswordConfig(var codeDigits: Int, var hmacAlgorithm: HmacAlgorithm) {
open class HmacOneTimePasswordConfig(val codeDigits: Int, val hmacAlgorithm: HmacAlgorithm) {
// -- Companion Object -------------------------------------------------------------------------------------------- //
// -- Properties -------------------------------------------------------------------------------------------------- //
// -- Initialization ---------------------------------------------------------------------------------------------- //
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.turingcomplete.kotlinonetimepassword

import org.apache.commons.codec.binary.Base32
import java.nio.ByteBuffer
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
Expand Down Expand Up @@ -89,7 +90,7 @@ open class HmacOneTimePasswordGenerator(private val secret: ByteArray,
val codeInt = binary.int.rem(10.0.pow(config.codeDigits).toInt())

// The integer code variable may contain a value with fewer digits than the
// required code digits. Therefore the final code value is filled with zeros
// required code digits. Therefore, the final code value is filled with zeros
// on the left, till the code digits requirement is fulfilled.
//
// Ongoing example:
Expand All @@ -109,6 +110,16 @@ open class HmacOneTimePasswordGenerator(private val secret: ByteArray,
return code == generate(counter)
}

/**
* Creates an [OtpAuthUriBuilder], which pre-configured with the secret, as
* well as the algorithm and code digits from the [config].
*/
fun otpAuthUriBuilder(initialCounter: Long): OtpAuthUriBuilder.Hotp {
return OtpAuthUriBuilder.forHotp(initialCounter, Base32().encode(secret))
.algorithm(config.hmacAlgorithm)
.digits(config.codeDigits)
}

// -- Private Methods --------------------------------------------------------------------------------------------- //
// -- Inner Type -------------------------------------------------------------------------------------------------- //
}
Loading

0 comments on commit dc52067

Please sign in to comment.