finally, minimal and straight registration schema and client implementation
This commit is contained in:
parent
8f950991a3
commit
8b8724b0f1
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,4 +3,5 @@
|
||||
/build/
|
||||
/build/classes/kotlin/js/test/
|
||||
/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 kotlin.random.Random
|
||||
|
||||
/**
|
||||
* Common structure to keep PBKDF2 params to safely generate keys
|
||||
* and a method to derive password accordingly.
|
||||
*/
|
||||
@Serializable
|
||||
class PasswordDerivationParams(
|
||||
val rounds: Int = 15000,
|
||||
@ -13,8 +17,33 @@ class PasswordDerivationParams(
|
||||
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> {
|
||||
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
|
||||
|
||||
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.WithAdapter
|
||||
import net.sergeych.unikrypto.HashAlgorithm
|
||||
import net.sergeych.unikrypto.SymmetricKey
|
||||
import net.sergeych.unikrypto.digest
|
||||
|
||||
/**
|
||||
* Registration instances are used to perform registration
|
||||
* in steps to not no regemerate all keys on every attempt to
|
||||
* say change login.
|
||||
* Registration instances are used to perform superlogin-compatible registration in a smart way
|
||||
* avoiding unnecessary time-consuming password derivation and key generation repetitions.
|
||||
* 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 {
|
||||
/**
|
||||
@ -20,14 +34,69 @@ class Registration<T: WithAdapter>(adapter: Adapter<T>) {
|
||||
* Operation failed for nknown reason, usually it means
|
||||
* 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(
|
||||
// login: String,
|
||||
// password: String,
|
||||
// )
|
||||
private var lastPasswordHash: ByteArray? = null
|
||||
private var lastDerivationParams: PasswordDerivationParams? = null
|
||||
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
|
||||
|
||||
|
||||
|
||||
class SuperloginClient(adapter: Adapter<*>) {
|
||||
|
||||
// init {
|
||||
// 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 loginPublicKey: PublicKey,
|
||||
val derivationParams: PasswordDerivationParams,
|
||||
val loginData: ByteArray,
|
||||
val restoreId: ByteArray,
|
||||
val restoreData: ByteArray,
|
||||
val extraData: ByteArray? = null
|
||||
val restoreData: ByteArray
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@ -23,7 +21,6 @@ sealed class AuthenticationResult {
|
||||
@Serializable
|
||||
data class Success(
|
||||
val loginToken: ByteArray,
|
||||
val extraData: ByteArray?
|
||||
): AuthenticationResult()
|
||||
|
||||
@Serializable
|
||||
|
Loading…
Reference in New Issue
Block a user