Browse Source

Refactor hotp/totp to return objects, including expiary time

master
Chris Smith 1 year ago
parent
commit
6ff1083675

+ 5
- 0
.idea/codeStyles/codeStyleConfig.xml View File

@@ -0,0 +1,5 @@
1
+<component name="ProjectCodeStyleConfiguration">
2
+  <state>
3
+    <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default (1)" />
4
+  </state>
5
+</component>

+ 14
- 5
app/build.gradle View File

@@ -1,6 +1,7 @@
1 1
 apply plugin: 'com.android.application'
2 2
 apply plugin: 'kotlin-android'
3 3
 apply plugin: 'kotlin-android-extensions'
4
+apply plugin: 'kotlin-kapt'
4 5
 
5 6
 android {
6 7
     compileSdkVersion 28
@@ -18,17 +19,25 @@ android {
18 19
             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 20
         }
20 21
     }
22
+
23
+    dataBinding {
24
+        enabled true
25
+    }
21 26
 }
22 27
 
23 28
 dependencies {
29
+    def lifecycle_version = "2.0.0-rc01"
30
+    def support_version = "1.0.0-rc01"
31
+
24 32
     implementation fileTree(dir: 'libs', include: ['*.jar'])
25
-    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
26
-    implementation 'com.android.support:appcompat-v7:28.0.0-rc01'
27
-    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
33
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
34
+    implementation "androidx.appcompat:appcompat:$support_version"
35
+    implementation "androidx.recyclerview:recyclerview:$support_version"
36
+    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
28 37
 
29 38
     testImplementation 'junit:junit:4.12'
30 39
     testImplementation 'com.natpryce:hamkrest:1.5.0.0'
31 40
 
32
-    androidTestImplementation 'com.android.support.test:runner:1.0.2'
33
-    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
41
+    androidTestImplementation "androidx.test:runner:$support_version"
42
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4'
34 43
 }

+ 0
- 24
app/src/androidTest/java/com/chameth/yaotp/ExampleInstrumentedTest.kt View File

@@ -1,24 +0,0 @@
1
-package com.chameth.yaotp
2
-
3
-import android.support.test.InstrumentationRegistry
4
-import android.support.test.runner.AndroidJUnit4
5
-
6
-import org.junit.Test
7
-import org.junit.runner.RunWith
8
-
9
-import org.junit.Assert.*
10
-
11
-/**
12
- * Instrumented test, which will execute on an Android device.
13
- *
14
- * See [testing documentation](http://d.android.com/tools/testing).
15
- */
16
-@RunWith(AndroidJUnit4::class)
17
-class ExampleInstrumentedTest {
18
-    @Test
19
-    fun useAppContext() {
20
-        // Context of the app under test.
21
-        val appContext = InstrumentationRegistry.getTargetContext()
22
-        assertEquals("com.chameth.yaotp", appContext.packageName)
23
-    }
24
-}

+ 1
- 1
app/src/main/java/com/chameth/yaotp/MainActivity.kt View File

@@ -1,7 +1,7 @@
1 1
 package com.chameth.yaotp
2 2
 
3
-import android.support.v7.app.AppCompatActivity
4 3
 import android.os.Bundle
4
+import androidx.appcompat.app.AppCompatActivity
5 5
 
6 6
 class MainActivity : AppCompatActivity() {
7 7
 

+ 1
- 1
app/src/main/java/com/chameth/yaotp/algos/Hotp.kt View File

@@ -6,7 +6,7 @@ import kotlin.math.pow
6 6
 
7 7
 data class HotpParams(val key: ByteArray, val length: Int = 6, val hmacFunc: HmacFunc = ::hmacSha1)
8 8
 
9
-fun hotp(params: HotpParams, counter: ByteArray) = hmacToHotp(params.hmacFunc(params.key, counter), params.length)
9
+fun hotp(params: HotpParams, counter: ByteArray) = newOtp(hmacToHotp(params.hmacFunc(params.key, counter), params.length), params.length)
10 10
 
11 11
 internal fun offsetValue(bytes: ByteArray) = (bytes[bytes.size - 1] and 0x0F).toInt()
12 12
 

+ 8
- 0
app/src/main/java/com/chameth/yaotp/algos/Otp.kt View File

@@ -0,0 +1,8 @@
1
+package com.chameth.yaotp.algos
2
+
3
+import kotlin.math.max
4
+
5
+data class Otp(val otp: String, val expiresAt: Long?)
6
+
7
+internal fun newOtp(otp: Int, targetLength: Int, expiresAt: Long? = null) = Otp(pad(otp, targetLength), expiresAt)
8
+internal fun pad(otp: Int, targetLength: Int) = with(otp.toString()) { "0".repeat(max(0, targetLength - length)) + this }

+ 7
- 2
app/src/main/java/com/chameth/yaotp/algos/Totp.kt View File

@@ -4,10 +4,15 @@ import java.nio.ByteBuffer
4 4
 
5 5
 data class TotpParams(val hotpParams: HotpParams, val startTime: Long = 0, val step: Int = 30)
6 6
 
7
-fun totp(totpParams: TotpParams, currentTime: Long = currentTime()) = hotp(totpParams.hotpParams, count(totpParams.startTime, totpParams.step, currentTime).toByteArray())
7
+fun totp(totpParams: TotpParams, currentTime: Long = currentTime()): Otp {
8
+    val hotp = hotp(totpParams.hotpParams, count(totpParams.startTime, totpParams.step, currentTime).toByteArray())
9
+    return Otp(hotp.otp, expiry(totpParams.startTime, totpParams.step, currentTime))
10
+}
8 11
 
9 12
 internal fun count(startTime: Long, step: Int, currentTime: Long) = (currentTime - startTime) / step
10 13
 
11 14
 internal fun currentTime() = System.currentTimeMillis() / 1000
12 15
 
13
-internal fun Long.toByteArray() = ByteBuffer.allocate(java.lang.Long.BYTES).putLong(this).array()
16
+internal fun Long.toByteArray() = ByteBuffer.allocate(java.lang.Long.BYTES).putLong(this).array()
17
+
18
+internal fun expiry(startTime: Long, step: Int, currentTime: Long) = currentTime + step - (currentTime - startTime) % step

+ 2
- 2
app/src/test/java/com/chameth/yaotp/algos/HotpTest.kt View File

@@ -42,9 +42,9 @@ class HotpTest {
42 42
     @Test
43 43
     fun testHotp_withRfcTestData() {
44 44
         val key = "12345678901234567890".toByteArray()
45
-        val expected = listOf(755224, 287082, 359152, 969429, 338314, 254676, 287922, 162583, 399871, 520489)
45
+        val expected = listOf("755224", "287082", "359152", "969429", "338314", "254676", "287922", "162583", "399871", "520489")
46 46
 
47
-        expected.forEachIndexed { index, i -> assert.that(hotp(HotpParams(key), byteArrayOfInts(0, 0, 0, 0, 0, 0, 0, index)), equalTo(i)) }
47
+        expected.forEachIndexed { index, i -> assert.that(hotp(HotpParams(key), byteArrayOfInts(0, 0, 0, 0, 0, 0, 0, index)).otp, equalTo(i)) }
48 48
     }
49 49
 
50 50
     private fun byteArrayOfInts(vararg ints: Int) = ByteArray(ints.size) { pos -> ints[pos].toByte() }

+ 21
- 0
app/src/test/java/com/chameth/yaotp/algos/OtpTest.kt View File

@@ -0,0 +1,21 @@
1
+package com.chameth.yaotp.algos
2
+
3
+import org.junit.Assert.assertEquals
4
+import org.junit.Test
5
+
6
+class OtpTest {
7
+
8
+    @Test
9
+    fun testPad_withShorterNumber() {
10
+        assertEquals("000123", pad(123, 6))
11
+        assertEquals("0000000123", pad(123, 10))
12
+        assertEquals("000000", pad(0, 6))
13
+    }
14
+
15
+    @Test
16
+    fun testPad_withLongerNumber() {
17
+        assertEquals("123", pad(123, 0))
18
+        assertEquals("123", pad(123, 3))
19
+    }
20
+
21
+}

+ 34
- 18
app/src/test/java/com/chameth/yaotp/algos/TotpTest.kt View File

@@ -3,6 +3,7 @@ package com.chameth.yaotp.algos
3 3
 import com.chameth.yaotp.toHexString
4 4
 import com.natpryce.hamkrest.assertion.assert
5 5
 import com.natpryce.hamkrest.equalTo
6
+import org.junit.Assert.assertEquals
6 7
 import org.junit.Test
7 8
 
8 9
 class TotpTest {
@@ -40,32 +41,47 @@ class TotpTest {
40 41
 
41 42
     @Test
42 43
     fun testTotp_withRfcValues_usingSha1() {
43
-        assert.that(totp(rfcKeySha1, 59), equalTo(94287082))
44
-        assert.that(totp(rfcKeySha1, 1111111109), equalTo(7081804))
45
-        assert.that(totp(rfcKeySha1, 1111111111), equalTo(14050471))
46
-        assert.that(totp(rfcKeySha1, 1234567890), equalTo(89005924))
47
-        assert.that(totp(rfcKeySha1, 2000000000), equalTo(69279037))
48
-        assert.that(totp(rfcKeySha1, 20000000000), equalTo(65353130))
44
+        assert.that(totp(rfcKeySha1, 59).otp, equalTo("94287082"))
45
+        assert.that(totp(rfcKeySha1, 1111111109).otp, equalTo("07081804"))
46
+        assert.that(totp(rfcKeySha1, 1111111111).otp, equalTo("14050471"))
47
+        assert.that(totp(rfcKeySha1, 1234567890).otp, equalTo("89005924"))
48
+        assert.that(totp(rfcKeySha1, 2000000000).otp, equalTo("69279037"))
49
+        assert.that(totp(rfcKeySha1, 20000000000).otp, equalTo("65353130"))
49 50
     }
50 51
 
51 52
     @Test
52 53
     fun testTotp_withRfcValues_usingSha256() {
53
-        assert.that(totp(rfcKeySha256, 59), equalTo(46119246))
54
-        assert.that(totp(rfcKeySha256, 1111111109), equalTo(68084774))
55
-        assert.that(totp(rfcKeySha256, 1111111111), equalTo(67062674))
56
-        assert.that(totp(rfcKeySha256, 1234567890), equalTo(91819424))
57
-        assert.that(totp(rfcKeySha256, 2000000000), equalTo(90698825))
58
-        assert.that(totp(rfcKeySha256, 20000000000), equalTo(77737706))
54
+        assert.that(totp(rfcKeySha256, 59).otp, equalTo("46119246"))
55
+        assert.that(totp(rfcKeySha256, 1111111109).otp, equalTo("68084774"))
56
+        assert.that(totp(rfcKeySha256, 1111111111).otp, equalTo("67062674"))
57
+        assert.that(totp(rfcKeySha256, 1234567890).otp, equalTo("91819424"))
58
+        assert.that(totp(rfcKeySha256, 2000000000).otp, equalTo("90698825"))
59
+        assert.that(totp(rfcKeySha256, 20000000000).otp, equalTo("77737706"))
59 60
     }
60 61
 
61 62
     @Test
62 63
     fun testTotp_withRfcValues_usingSha512() {
63
-        assert.that(totp(rfcKeySha512, 59), equalTo(90693936))
64
-        assert.that(totp(rfcKeySha512, 1111111109), equalTo(25091201))
65
-        assert.that(totp(rfcKeySha512, 1111111111), equalTo(99943326))
66
-        assert.that(totp(rfcKeySha512, 1234567890), equalTo(93441116))
67
-        assert.that(totp(rfcKeySha512, 2000000000), equalTo(38618901))
68
-        assert.that(totp(rfcKeySha512, 20000000000), equalTo(47863826))
64
+        assert.that(totp(rfcKeySha512, 59).otp, equalTo("90693936"))
65
+        assert.that(totp(rfcKeySha512, 1111111109).otp, equalTo("25091201"))
66
+        assert.that(totp(rfcKeySha512, 1111111111).otp, equalTo("99943326"))
67
+        assert.that(totp(rfcKeySha512, 1234567890).otp, equalTo("93441116"))
68
+        assert.that(totp(rfcKeySha512, 2000000000).otp, equalTo("38618901"))
69
+        assert.that(totp(rfcKeySha512, 20000000000).otp, equalTo("47863826"))
70
+    }
71
+
72
+    @Test
73
+    fun testExpirary_withPartialValues() {
74
+        assertEquals(3000030L, expiry(0L, 30, 3000029L))
75
+        assertEquals(3000030L, expiry(0L, 30, 3000020L))
76
+        assertEquals(3000030L, expiry(0L, 30, 3000010L))
77
+        assertEquals(3000030L, expiry(0L, 30, 3000005L))
78
+        assertEquals(3000030L, expiry(0L, 30, 3000001L))
79
+    }
80
+
81
+    @Test
82
+    fun testExpirary_withStepValues() {
83
+        assertEquals(3000060L, expiry(0L, 30, 3000030L))
84
+        assertEquals(3000030L, expiry(0L, 30, 3000000L))
69 85
     }
70 86
 
71 87
 }

+ 1
- 1
build.gradle View File

@@ -8,7 +8,7 @@ buildscript {
8 8
         jcenter()
9 9
     }
10 10
     dependencies {
11
-        classpath 'com.android.tools.build:gradle:3.1.4'
11
+        classpath 'com.android.tools.build:gradle:3.3.0-alpha06'
12 12
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 13
     }
14 14
 }

+ 3
- 0
gradle.properties View File

@@ -11,3 +11,6 @@ org.gradle.jvmargs=-Xmx1536m
11 11
 # This option should only be used with decoupled projects. More details, visit
12 12
 # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 13
 # org.gradle.parallel=true
14
+
15
+android.useAndroidX=true
16
+android.enableJetifier=true

+ 2
- 2
gradle/wrapper/gradle-wrapper.properties View File

@@ -1,6 +1,6 @@
1
-#Sat Aug 18 14:25:48 BST 2018
1
+#Sun Aug 19 13:12:52 BST 2018
2 2
 distributionBase=GRADLE_USER_HOME
3 3
 distributionPath=wrapper/dists
4 4
 zipStoreBase=GRADLE_USER_HOME
5 5
 zipStorePath=wrapper/dists
6
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
6
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip

Loading…
Cancel
Save