Skip to content

Commit 452b029

Browse files
committed
use async and precomputation to improve performance
1 parent 707ee34 commit 452b029

File tree

3 files changed

+60
-33
lines changed

3 files changed

+60
-33
lines changed

amplifyframework/src/commonMain/kotlin/com/jump/sdk/amplifyframework/CognitoException.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,4 @@ package com.jump.sdk.amplifyframework
33
sealed class CognitoException(override val message: String) : Exception(message) {
44
data object BadSrpB : CognitoException("Bad server public value 'B'")
55
data object HashOfAAndSrpBCannotBeZero : CognitoException("Hash of A and B cannot be zero")
6-
data object UserIdNotSet : CognitoException("Must call setUserPoolParams() before this")
76
}

amplifyframework/src/commonMain/kotlin/com/jump/sdk/amplifyframework/SRPHelper.kt

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package com.jump.sdk.amplifyframework
1717

1818
import com.ionspin.kotlin.bignum.integer.BigInteger
1919
import com.ionspin.kotlin.bignum.integer.Sign
20+
import com.ionspin.kotlin.bignum.integer.util.fromTwosComplementByteArray
2021
import com.ionspin.kotlin.bignum.integer.util.toTwosComplementByteArray
2122
import com.ionspin.kotlin.bignum.modular.ModularBigInteger
2223
import io.ktor.utils.io.core.toByteArray
@@ -44,45 +45,47 @@ private const val HEX_N =
4445
"CEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E2" +
4546
"4FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF"
4647

48+
// precomputed: k = H(g|N)
49+
private val kByteArray = byteArrayOf(83, -126, -126, -60, 53, 71, 66, -41, -53, -67, -30, 53, -97, -49, 103, -7, -11, -77, -90, -80, -121, -111, -27, 1, 27, 67, -72, -91, -74, 109, -98, -26)
50+
// precomputed: N = BigInteger.parseString(HEX_N, 16)
51+
private val nByteArray = byteArrayOf(0, -1, -1, -1, -1, -1, -1, -1, -1, -55, 15, -38, -94, 33, 104, -62, 52, -60, -58, 98, -117, -128, -36, 28, -47, 41, 2, 78, 8, -118, 103, -52, 116, 2, 11, -66, -90, 59, 19, -101, 34, 81, 74, 8, 121, -114, 52, 4, -35, -17, -107, 25, -77, -51, 58, 67, 27, 48, 43, 10, 109, -14, 95, 20, 55, 79, -31, 53, 109, 109, 81, -62, 69, -28, -123, -75, 118, 98, 94, 126, -58, -12, 76, 66, -23, -90, 55, -19, 107, 11, -1, 92, -74, -12, 6, -73, -19, -18, 56, 107, -5, 90, -119, -97, -91, -82, -97, 36, 17, 124, 75, 31, -26, 73, 40, 102, 81, -20, -28, 91, 61, -62, 0, 124, -72, -95, 99, -65, 5, -104, -38, 72, 54, 28, 85, -45, -102, 105, 22, 63, -88, -3, 36, -49, 95, -125, 101, 93, 35, -36, -93, -83, -106, 28, 98, -13, 86, 32, -123, 82, -69, -98, -43, 41, 7, 112, -106, -106, 109, 103, 12, 53, 78, 74, -68, -104, 4, -15, 116, 108, 8, -54, 24, 33, 124, 50, -112, 94, 70, 46, 54, -50, 59, -29, -98, 119, 44, 24, 14, -122, 3, -101, 39, -125, -94, -20, 7, -94, -113, -75, -59, 93, -16, 111, 76, 82, -55, -34, 43, -53, -10, -107, 88, 23, 24, 57, -107, 73, 124, -22, -107, 106, -27, 21, -46, 38, 24, -104, -6, 5, 16, 21, 114, -114, 90, -118, -86, -60, 45, -83, 51, 23, 13, 4, 80, 122, 51, -88, 85, 33, -85, -33, 28, -70, 100, -20, -5, -123, 4, 88, -37, -17, 10, -118, -22, 113, 87, 93, 6, 12, 125, -77, -105, 15, -123, -90, -31, -28, -57, -85, -11, -82, -116, -37, 9, 51, -41, 30, -116, -108, -32, 74, 37, 97, -99, -50, -29, -46, 38, 26, -46, -18, 107, -15, 47, -6, 6, -39, -118, 8, 100, -40, 118, 2, 115, 62, -56, 106, 100, 82, 31, 43, 24, 23, 123, 32, 12, -69, -31, 23, 87, 122, 97, 93, 108, 119, 9, -120, -64, -70, -39, 70, -30, 8, -30, 79, -96, 116, -27, -85, 49, 67, -37, 91, -4, -32, -3, 16, -114, 75, -126, -47, 32, -87, 58, -46, -54, -1, -1, -1, -1, -1, -1, -1, -1)
52+
4753
@OptIn(ExperimentalEncodingApi::class)
4854
@Suppress("TooManyFunctions")
49-
class SRPHelper(private val password: String, userPoolName: String) {
55+
class SRPHelper(userPool: String) {
5056
@Suppress("VariableNaming")
51-
private val N = BigInteger.parseString(HEX_N, 16)
57+
private val N = BigInteger.fromTwosComplementByteArray(nByteArray)
5258

5359
private val creator = ModularBigInteger.creatorForModulo(N)
5460
private val g = creator.fromInt(2)
5561

5662
private val random = SecureRandom()
5763

58-
private val k: BigInteger
59-
private var privateA: BigInteger
60-
private var publicA: ModularBigInteger
64+
private val k: BigInteger = BigInteger.fromTwosComplementByteArray(kByteArray)
65+
lateinit var privateA: BigInteger
66+
lateinit var publicA: ModularBigInteger
6167
var timestamp: String = nowAsFormattedString()
6268
internal set
6369

6470
private val digest = SHA256()
65-
var userIdForSrp: String? = null
6671
private val userPoolName: String
6772

6873
init {
69-
if (userPoolName.contains("_")) {
70-
this.userPoolName = userPoolName.split(Regex("_"), 2)[1]
74+
if (userPool.contains("_")) {
75+
this.userPoolName = userPool.split(Regex("_"), 2)[1]
7176
} else {
72-
this.userPoolName = userPoolName
77+
this.userPoolName = userPool
7378
}
79+
}
7480

75-
// Generate client private 'a' and public 'A' values
81+
// Generate client private 'a' and public 'A' values
82+
suspend fun getPublicA(): String {
7683
do {
7784
privateA = BigInteger.fromByteArray(random.nextBytesOf(EPHEMERAL_KEY_LENGTH), Sign.POSITIVE).mod(N)
7885
// A = (g ^ a) % N
7986
publicA = g.pow(privateA)
8087
} while (publicA.residue == BigInteger.ZERO)
81-
82-
// compute k = H(g|N)
83-
digest.reset()
84-
digest.update(N.toTwosComplementByteArray())
85-
k = BigInteger.fromByteArray(digest.digest(g.toByteArray()), Sign.POSITIVE)
88+
return publicA.toString(HEX)
8689
}
8790

8891
// @TestOnly
@@ -94,8 +97,6 @@ class SRPHelper(private val password: String, userPoolName: String) {
9497
this.publicA = creator.fromBigInteger(publicA)
9598
}
9699

97-
fun getPublicA(): String = publicA.toString(HEX)
98-
99100
// u = H(A, B)
100101
internal fun computeU(srpB: BigInteger): BigInteger {
101102
digest.reset()
@@ -105,10 +106,10 @@ class SRPHelper(private val password: String, userPoolName: String) {
105106

106107
// x = H(salt | H(poolName | userId | ":" | password))
107108
@Throws(CognitoException::class)
108-
internal fun computeX(salt: BigInteger): BigInteger {
109+
internal fun computeX(salt: BigInteger, userIdForSrp: String, password: String): BigInteger {
109110
digest.reset()
110111
digest.update(userPoolName.toByteArray())
111-
digest.update(userIdForSrp?.toByteArray() ?: throw CognitoException.UserIdNotSet)
112+
digest.update(userIdForSrp.toByteArray())
112113
digest.update(":".toByteArray())
113114
val userIdPasswordHash = digest.digest(password.toByteArray())
114115

@@ -119,8 +120,12 @@ class SRPHelper(private val password: String, userPoolName: String) {
119120

120121
// verifier = (g ^ x) % N
121122
@Throws(CognitoException::class)
122-
internal fun computePasswordVerifier(salt: BigInteger): ModularBigInteger {
123-
val xValue = computeX(salt)
123+
internal fun computePasswordVerifier(
124+
salt: BigInteger,
125+
userIdForSrp: String,
126+
password: String,
127+
): ModularBigInteger {
128+
val xValue = computeX(salt = salt, userIdForSrp = userIdForSrp, password = password)
124129
return g.pow(xValue)
125130
}
126131

@@ -151,10 +156,14 @@ class SRPHelper(private val password: String, userPoolName: String) {
151156

152157
// M1 = MAC(poolId | userId | secret | timestamp, key)
153158
@Throws(CognitoException::class)
154-
internal fun generateM1Signature(key: ByteArray, secretBlock: String): ByteArray {
159+
internal fun generateM1Signature(
160+
key: ByteArray,
161+
secretBlock: String,
162+
userIdForSrp: String,
163+
): ByteArray {
155164
val mac = HmacSHA256(key)
156165
mac.update(userPoolName.toByteArray())
157-
mac.update(userIdForSrp?.toByteArray() ?: throw CognitoException.UserIdNotSet)
166+
mac.update(userIdForSrp.toByteArray())
158167
mac.update(Base64.decode(secretBlock))
159168
return mac.doFinal(timestamp.toByteArray())
160169
}
@@ -174,10 +183,18 @@ class SRPHelper(private val password: String, userPoolName: String) {
174183
* @param srpB The SRP_B value provided by the Cognito service.
175184
* @param secretBlock The secret block - should be passed into PASSWORD_CLAIM_SECRET_BLOCK
176185
* for the subsequent call to AWSCognitoIdentityProviderService.RespondToAuthChallenge
186+
* @param userIdForSrp The user ID used in the authentication process.
187+
* @param password The password used in the authentication process.
177188
* @return A string representing the PASSWORD_CLAIM_SIGNATURE for authentication.
178189
*/
179190
@Throws(CognitoException::class, CancellationException::class)
180-
suspend fun getSignature(salt: String, srpB: String, secretBlock: String): String {
191+
suspend fun getSignature(
192+
salt: String,
193+
srpB: String,
194+
secretBlock: String,
195+
userIdForSrp: String,
196+
password: String,
197+
): String {
181198
val bigIntSRPB = BigInteger.parseString(srpB, HEX)
182199
val bigIntSalt = BigInteger.parseString(salt, HEX)
183200

@@ -191,10 +208,14 @@ class SRPHelper(private val password: String, userPoolName: String) {
191208
throw CognitoException.HashOfAAndSrpBCannotBeZero
192209
}
193210

194-
val xValue = computeX(bigIntSalt)
211+
val xValue = computeX(salt = bigIntSalt, userIdForSrp = userIdForSrp, password = password)
195212
val sValue = computeS(uValue, xValue, bigIntSRPB)
196213
val key = computePasswordAuthenticationKey(sValue, uValue)
197-
val m1Signature = generateM1Signature(key, secretBlock)
214+
val m1Signature = generateM1Signature(
215+
key = key,
216+
secretBlock = secretBlock,
217+
userIdForSrp = userIdForSrp,
218+
)
198219
return Base64.encode(m1Signature)
199220
}
200221
}

amplifyframework/src/commonTest/kotlin/com/jump/sdk/amplifyframework/SRPHelperTests.kt

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import kotlin.test.BeforeTest
2323
import kotlin.test.Test
2424
import kotlin.test.assertEquals
2525
import kotlin.test.assertNotEquals
26+
import kotlinx.coroutines.runBlocking
2627

2728

2829
@OptIn(ExperimentalEncodingApi::class)
@@ -101,18 +102,20 @@ class SRPHelperTests {
101102
private val m1Expected = "QG7a57h+ndPBVasvx/OkmsJdy5uoMEVRshboEd4S+j8="
102103

103104
private lateinit var helper: SRPHelper
105+
private val password = "Password123"
106+
private val userIdForSrp = "username"
104107

105108
@BeforeTest
106109
fun setUp() {
107-
helper = SRPHelper(password = "Password123", userPoolName = "us-east-2_KO6fcefgd")
110+
helper = SRPHelper(userPool = "us-east-2_KO6fcefgd")
108111
helper.setAValues(privateA, publicA)
109-
helper.userIdForSrp = "username"
110112
}
111113

112114
@Test
113115
fun testValidPublicA() {
114-
val testHelper = SRPHelper(password = "Password123", userPoolName = "us-east-2_KO6fcefgd")
115-
val bigA = BigInteger.parseString(testHelper.getPublicA(), 16)
116+
val testHelper = SRPHelper(userPool = "us-east-2_KO6fcefgd")
117+
val publicA = runBlocking { testHelper.getPublicA() }
118+
val bigA = BigInteger.parseString(publicA, 16)
116119
assertNotEquals(BigInteger.ZERO, testHelper.modN(bigA))
117120
}
118121

@@ -126,7 +129,7 @@ class SRPHelperTests {
126129
fun testComputeX() {
127130
val salt = BigInteger.parseString("e7dc204cebbfda6b62b8493e932f7f4c", 16)
128131
println(salt)
129-
val xActual = helper.computeX(salt)
132+
val xActual = helper.computeX(salt = salt, userIdForSrp = userIdForSrp, password = password)
130133
println(xActual)
131134
assertEquals(xExpected, xActual)
132135
}
@@ -146,7 +149,11 @@ class SRPHelperTests {
146149
@Test
147150
fun testGenerateM1() {
148151
helper.timestamp = "Wed Sep 29 06:40:48 UTC 2021"
149-
val m1Actual = helper.generateM1Signature(keyExpected.toByteArray(), secretBlock)
152+
val m1Actual = helper.generateM1Signature(
153+
key = keyExpected.toByteArray(),
154+
secretBlock = secretBlock,
155+
userIdForSrp = userIdForSrp,
156+
)
150157
assertEquals(m1Expected, Base64.encode(m1Actual))
151158
}
152159
}

0 commit comments

Comments
 (0)