finally, minimal and straight registration schema and client implementation
This commit is contained in:
parent
8f950991a3
commit
8b8724b0f1
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,3 +4,4 @@
|
|||||||
/build/classes/kotlin/js/test/
|
/build/classes/kotlin/js/test/
|
||||||
/build/classes/kotlin/jvm/main/
|
/build/classes/kotlin/jvm/main/
|
||||||
/build/classes/kotlin/jvm/test/
|
/build/classes/kotlin/jvm/test/
|
||||||
|
/.idea
|
@ -6,6 +6,10 @@ import net.sergeych.unikrypto.Passwords
|
|||||||
import net.sergeych.unikrypto.SymmetricKey
|
import net.sergeych.unikrypto.SymmetricKey
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common structure to keep PBKDF2 params to safely generate keys
|
||||||
|
* and a method to derive password accordingly.
|
||||||
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
class PasswordDerivationParams(
|
class PasswordDerivationParams(
|
||||||
val rounds: Int = 15000,
|
val rounds: Int = 15000,
|
||||||
@ -13,8 +17,33 @@ class PasswordDerivationParams(
|
|||||||
val salt: ByteArray = Random.nextBytes(32),
|
val salt: ByteArray = Random.nextBytes(32),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derive one or more keys in cryptographically-independent manner
|
||||||
|
* @param password password to derive keys from
|
||||||
|
* @param amount deisred number of keys (all of them will be mathematically and cryptographically independent)
|
||||||
|
* @return list of generated symmetric keys (with a proper ids that allow independent derivation later)
|
||||||
|
*/
|
||||||
suspend fun derive(password: String,amount: Int = 1): List<SymmetricKey> {
|
suspend fun derive(password: String,amount: Int = 1): List<SymmetricKey> {
|
||||||
return Passwords.deriveKeys(password, amount, rounds, algorithm, salt, Passwords.KeyIdAlgorithm.Independent)
|
return Passwords.deriveKeys(password, amount, rounds, algorithm, salt, Passwords.KeyIdAlgorithm.Independent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other == null || this::class != other::class) return false
|
||||||
|
|
||||||
|
other as PasswordDerivationParams
|
||||||
|
|
||||||
|
if (rounds != other.rounds) return false
|
||||||
|
if (algorithm != other.algorithm) return false
|
||||||
|
if (!salt.contentEquals(other.salt)) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = rounds
|
||||||
|
result = 31 * result + algorithm.hashCode()
|
||||||
|
result = 31 * result + salt.contentHashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,14 +1,28 @@
|
|||||||
package net.sergeych.superlogin
|
package net.sergeych.superlogin
|
||||||
|
|
||||||
|
import net.sergeych.mp_logger.LogTag
|
||||||
|
import net.sergeych.mp_logger.error
|
||||||
|
import net.sergeych.mp_logger.exception
|
||||||
import net.sergeych.parsec3.Adapter
|
import net.sergeych.parsec3.Adapter
|
||||||
import net.sergeych.parsec3.WithAdapter
|
import net.sergeych.parsec3.WithAdapter
|
||||||
|
import net.sergeych.unikrypto.HashAlgorithm
|
||||||
|
import net.sergeych.unikrypto.SymmetricKey
|
||||||
|
import net.sergeych.unikrypto.digest
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registration instances are used to perform registration
|
* Registration instances are used to perform superlogin-compatible registration in a smart way
|
||||||
* in steps to not no regemerate all keys on every attempt to
|
* avoiding unnecessary time-consuming password derivation and key generation repetitions.
|
||||||
* say change login.
|
* once calculated these values are kept in the instance unless new password or different
|
||||||
|
* password derivation parameters are supplied. This is useful in the case the login in user error
|
||||||
|
* result - user can provide new login and re-register times faster than otherwise re-deriving
|
||||||
|
* and creating new keys.
|
||||||
|
*
|
||||||
|
* It is important to use _different_ instances for different registrations, and the same instance
|
||||||
|
* for consecutive attempts to register the same user varying login only.
|
||||||
|
*
|
||||||
|
* See [register] for more.
|
||||||
*/
|
*/
|
||||||
class Registration<T: WithAdapter>(adapter: Adapter<T>) {
|
class Registration<T: WithAdapter>(val adapter: Adapter<T>,loginKeyStrength: Int = 4096): LogTag("SLREG") {
|
||||||
|
|
||||||
sealed class Result {
|
sealed class Result {
|
||||||
/**
|
/**
|
||||||
@ -20,14 +34,69 @@ class Registration<T: WithAdapter>(adapter: Adapter<T>) {
|
|||||||
* Operation failed for nknown reason, usually it means
|
* Operation failed for nknown reason, usually it means
|
||||||
* network or server downtime
|
* network or server downtime
|
||||||
*/
|
*/
|
||||||
object NetworkFailure: Result()
|
class NetworkFailure(val exception: Throwable?=null): Result()
|
||||||
|
|
||||||
// class Success(val l)
|
class Success(val secret: String,val dataKey: SymmetricKey,loginToken: ByteArray): Result()
|
||||||
}
|
}
|
||||||
|
|
||||||
// fun register(
|
private var lastPasswordHash: ByteArray? = null
|
||||||
// login: String,
|
private var lastDerivationParams: PasswordDerivationParams? = null
|
||||||
// password: String,
|
private var passwordKeys: DerivedKeys? = null
|
||||||
// )
|
val api = SuperloginServerApi<T>()
|
||||||
|
private val deferredLoginKey = BackgroundKeyGenerator.getKeyAsync(loginKeyStrength)
|
||||||
|
private val dataKey = BackgroundKeyGenerator.randomSymmetricKey()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Smart attempt to register. It is ok to repeatedly call it if the result is not [Result.Success]: it will
|
||||||
|
* cache internal data and reuse time-consuming precalculated values for caches and derived keys if the
|
||||||
|
* password and derivarion parameters are not changed between calls.
|
||||||
|
*/
|
||||||
|
suspend fun register(
|
||||||
|
login: String,
|
||||||
|
password: String,
|
||||||
|
derivationParams: PasswordDerivationParams = PasswordDerivationParams()
|
||||||
|
): Result {
|
||||||
|
val newPasswordHash = HashAlgorithm.SHA3_256.digest(password)
|
||||||
|
if( lastPasswordHash?.contentEquals(newPasswordHash) != true ||
|
||||||
|
lastDerivationParams?.equals(derivationParams) != true ||
|
||||||
|
passwordKeys == null ) {
|
||||||
|
passwordKeys = DerivedKeys.derive(password,derivationParams)
|
||||||
|
lastDerivationParams = derivationParams
|
||||||
|
lastPasswordHash = newPasswordHash
|
||||||
|
}
|
||||||
|
val spl = SuperloginPayload(login, deferredLoginKey.await(), dataKey)
|
||||||
|
repeat(10) {
|
||||||
|
val (restoreKey, restoreData) = AccessControlObject.pack(passwordKeys!!.loginAccessKey,spl)
|
||||||
|
try {
|
||||||
|
val result = adapter.invokeCommand(
|
||||||
|
api.registerUser, RegistrationArgs(
|
||||||
|
login,
|
||||||
|
passwordKeys!!.loginId,
|
||||||
|
deferredLoginKey.await().publicKey,
|
||||||
|
derivationParams,
|
||||||
|
restoreKey.restoreId, restoreData
|
||||||
|
)
|
||||||
|
)
|
||||||
|
when (result) {
|
||||||
|
AuthenticationResult.RestoreIdUnavailable -> {
|
||||||
|
// rare situation but still possible: just repack the ACO
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationResult.LoginUnavailable -> return Result.InvalidLogin
|
||||||
|
is AuthenticationResult.Success -> return Result.Success(
|
||||||
|
restoreKey.secret,
|
||||||
|
dataKey,
|
||||||
|
result.loginToken
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(x: Throwable) {
|
||||||
|
exception { "Failed to register" to x }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we still get there, its a strange error - 10 times we cant get suitable restoreId...
|
||||||
|
error { "Failed to register after 10 repetitions with no apparent reason" }
|
||||||
|
return Result.NetworkFailure(null)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,7 +0,0 @@
|
|||||||
package net.sergeych.superlogin
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import net.sergeych.unikrypto.PrivateKey
|
|
||||||
import net.sergeych.unikrypto.SymmetricKey
|
|
||||||
|
|
||||||
|
|
@ -3,12 +3,15 @@ package net.sergeych.superlogin
|
|||||||
import net.sergeych.parsec3.Adapter
|
import net.sergeych.parsec3.Adapter
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SuperloginClient(adapter: Adapter<*>) {
|
class SuperloginClient(adapter: Adapter<*>) {
|
||||||
|
|
||||||
// init {
|
// init {
|
||||||
// adapter.invokeCommand()
|
// adapter.invokeCommand()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
fun register() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package net.sergeych.superlogin
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import net.sergeych.unikrypto.PrivateKey
|
||||||
|
import net.sergeych.unikrypto.SymmetricKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base payload class for superlogin dance, it contains basic information
|
||||||
|
* needed for an average superlogin system:
|
||||||
|
*
|
||||||
|
* - login (whatever string, say, email)
|
||||||
|
* - login private key (used only to sign in to a service)
|
||||||
|
* - storage key (used to safely keep stored data)
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class SuperloginPayload(
|
||||||
|
val login: String,
|
||||||
|
val loginPrivateKey: PrivateKey,
|
||||||
|
val dataStorageKey: SymmetricKey
|
||||||
|
) {
|
||||||
|
}
|
@ -12,10 +12,8 @@ data class RegistrationArgs(
|
|||||||
val loginId: ByteArray,
|
val loginId: ByteArray,
|
||||||
val loginPublicKey: PublicKey,
|
val loginPublicKey: PublicKey,
|
||||||
val derivationParams: PasswordDerivationParams,
|
val derivationParams: PasswordDerivationParams,
|
||||||
val loginData: ByteArray,
|
|
||||||
val restoreId: ByteArray,
|
val restoreId: ByteArray,
|
||||||
val restoreData: ByteArray,
|
val restoreData: ByteArray
|
||||||
val extraData: ByteArray? = null
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -23,7 +21,6 @@ sealed class AuthenticationResult {
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class Success(
|
data class Success(
|
||||||
val loginToken: ByteArray,
|
val loginToken: ByteArray,
|
||||||
val extraData: ByteArray?
|
|
||||||
): AuthenticationResult()
|
): AuthenticationResult()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
Loading…
Reference in New Issue
Block a user