Add support for encrypted second password hashes

This commit is contained in:
Jonas Lochmann 2022-09-12 02:00:00 +02:00
parent c135a640c6
commit 15168bed66
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
14 changed files with 207 additions and 35 deletions

View file

@ -0,0 +1,79 @@
/*
* TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.timelimit.android.crypto
import io.timelimit.android.extensions.base64
import io.timelimit.android.sync.network.ServerDhKey
import java.security.KeyFactory
import java.security.KeyPairGenerator
import java.security.SecureRandom
import java.security.spec.ECGenParameterSpec
import java.security.spec.X509EncodedKeySpec
import javax.crypto.Cipher
import javax.crypto.KeyAgreement
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
data class DHHandshake(val keyVersion: String, val ownPublicKey: ByteArray, val sharedSecret: ByteArray) {
companion object {
private const val SHARED_SECRET_LENGTH = 32
private const val AES_KEY_SIZE = 16
private const val AES_AUTH_TAG_BITS = 128
fun fromServerKey(serverDhKey: ServerDhKey): DHHandshake {
val otherPublicKey = KeyFactory.getInstance("EC").generatePublic(X509EncodedKeySpec(serverDhKey.key))
val ownKeypair = KeyPairGenerator.getInstance("EC").let {
it.initialize(ECGenParameterSpec("secp256r1"))
it.genKeyPair()
}
val ownPublicKey = ownKeypair.public.encoded
val sharedSecret = KeyAgreement.getInstance("ECDH").let {
it.init(ownKeypair.private)
it.doPhase(otherPublicKey, true)
it.generateSecret()
}
return DHHandshake(
keyVersion = serverDhKey.version,
ownPublicKey = ownPublicKey,
sharedSecret = sharedSecret
)
}
}
init {
if (sharedSecret.size != SHARED_SECRET_LENGTH) {
throw IllegalArgumentException()
}
}
fun encrypt(cryptData: ByteArray, authData: ByteArray): String {
val aesKey = SecretKeySpec(sharedSecret.sliceArray(0 until AES_KEY_SIZE), "AES")
val iv = ByteArray(12).also { SecureRandom().nextBytes(it) }
val cipher = Cipher.getInstance("AES/GCM/NoPadding").also {
it.init(Cipher.ENCRYPT_MODE, aesKey, GCMParameterSpec(AES_AUTH_TAG_BITS, iv))
it.updateAAD(authData)
}
val encryptedData = cipher.doFinal(cryptData)
return (iv + encryptedData).base64() + "." + ownPublicKey.base64() + "." + keyVersion
}
}

View file

@ -30,6 +30,7 @@ import io.timelimit.android.extensions.parseBase64
import io.timelimit.android.extensions.toJsonReader import io.timelimit.android.extensions.toJsonReader
import io.timelimit.android.livedata.ignoreUnchanged import io.timelimit.android.livedata.ignoreUnchanged
import io.timelimit.android.livedata.map import io.timelimit.android.livedata.map
import io.timelimit.android.sync.network.ServerDhKey
import io.timelimit.android.update.UpdateStatus import io.timelimit.android.update.UpdateStatus
import java.io.StringWriter import java.io.StringWriter
@ -363,4 +364,19 @@ abstract class ConfigDao {
fun getLastServerKeyResponseSequenceSync(): Long? = getValueOfKeySync(ConfigurationItemType.LastKeyResponseSequence)?.toLong() fun getLastServerKeyResponseSequenceSync(): Long? = getValueOfKeySync(ConfigurationItemType.LastKeyResponseSequence)?.toLong()
fun setLastServerKeyResponseSequenceSync(value: Long) = updateValueSync(ConfigurationItemType.LastKeyResponseSequence, value.toString()) fun setLastServerKeyResponseSequenceSync(value: Long) = updateValueSync(ConfigurationItemType.LastKeyResponseSequence, value.toString())
@Transaction
open fun getLastDhKeySync(): ServerDhKey? {
val version = getValueOfKeySync(ConfigurationItemType.DhKeyVersion)
val key = getValueOfKeySync(ConfigurationItemType.DhKey)
return if (version != null && key != null) ServerDhKey(version = version, key = key.parseBase64())
else null
}
@Transaction
open fun setLastDhKeySync(key: ServerDhKey) {
updateValueSync(ConfigurationItemType.DhKeyVersion, key.version)
updateValueSync(ConfigurationItemType.DhKey, key.key.base64())
}
} }

View file

@ -103,7 +103,9 @@ enum class ConfigurationItemType {
SigningKey, SigningKey,
SignSequenceNumber, SignSequenceNumber,
LastServerKeyRequestSequence, LastServerKeyRequestSequence,
LastKeyResponseSequence LastKeyResponseSequence,
DhKey,
DhKeyVersion
} }
object ConfigurationItemTypeUtil { object ConfigurationItemTypeUtil {
@ -136,6 +138,8 @@ object ConfigurationItemTypeUtil {
private const val SIGN_SEQUENCE_NUMBER = 28 private const val SIGN_SEQUENCE_NUMBER = 28
private const val LAST_SERVER_KEY_REQUEST_SEQUENCE = 29 private const val LAST_SERVER_KEY_REQUEST_SEQUENCE = 29
private const val LAST_SERVER_KEY_RESPONSE_SEQUENCE = 30 private const val LAST_SERVER_KEY_RESPONSE_SEQUENCE = 30
private const val DH_KEY = 31
private const val DH_KEY_VERSION = 32
val TYPES = listOf( val TYPES = listOf(
ConfigurationItemType.OwnDeviceId, ConfigurationItemType.OwnDeviceId,
@ -166,7 +170,9 @@ object ConfigurationItemTypeUtil {
ConfigurationItemType.SigningKey, ConfigurationItemType.SigningKey,
ConfigurationItemType.SignSequenceNumber, ConfigurationItemType.SignSequenceNumber,
ConfigurationItemType.LastServerKeyRequestSequence, ConfigurationItemType.LastServerKeyRequestSequence,
ConfigurationItemType.LastKeyResponseSequence ConfigurationItemType.LastKeyResponseSequence,
ConfigurationItemType.DhKey,
ConfigurationItemType.DhKeyVersion
) )
fun serialize(value: ConfigurationItemType) = when(value) { fun serialize(value: ConfigurationItemType) = when(value) {
@ -199,6 +205,8 @@ object ConfigurationItemTypeUtil {
ConfigurationItemType.SignSequenceNumber -> SIGN_SEQUENCE_NUMBER ConfigurationItemType.SignSequenceNumber -> SIGN_SEQUENCE_NUMBER
ConfigurationItemType.LastServerKeyRequestSequence -> LAST_SERVER_KEY_REQUEST_SEQUENCE ConfigurationItemType.LastServerKeyRequestSequence -> LAST_SERVER_KEY_REQUEST_SEQUENCE
ConfigurationItemType.LastKeyResponseSequence -> LAST_SERVER_KEY_RESPONSE_SEQUENCE ConfigurationItemType.LastKeyResponseSequence -> LAST_SERVER_KEY_RESPONSE_SEQUENCE
ConfigurationItemType.DhKey -> DH_KEY
ConfigurationItemType.DhKeyVersion -> DH_KEY_VERSION
} }
fun parse(value: Int) = when(value) { fun parse(value: Int) = when(value) {
@ -231,6 +239,8 @@ object ConfigurationItemTypeUtil {
SIGN_SEQUENCE_NUMBER -> ConfigurationItemType.SignSequenceNumber SIGN_SEQUENCE_NUMBER -> ConfigurationItemType.SignSequenceNumber
LAST_SERVER_KEY_REQUEST_SEQUENCE -> ConfigurationItemType.LastServerKeyRequestSequence LAST_SERVER_KEY_REQUEST_SEQUENCE -> ConfigurationItemType.LastServerKeyRequestSequence
LAST_SERVER_KEY_RESPONSE_SEQUENCE -> ConfigurationItemType.LastKeyResponseSequence LAST_SERVER_KEY_RESPONSE_SEQUENCE -> ConfigurationItemType.LastKeyResponseSequence
DH_KEY -> ConfigurationItemType.DhKey
DH_KEY_VERSION -> ConfigurationItemType.DhKeyVersion
else -> throw IllegalArgumentException() else -> throw IllegalArgumentException()
} }
} }

View file

@ -53,6 +53,8 @@ object ApplyServerDataStatus {
database.config().setServerApiLevelSync(status.apiLevel) database.config().setServerApiLevelSync(status.apiLevel)
} }
status.dh?.also { database.config().setLastDhKeySync(it) }
var didCreateNewActions = false var didCreateNewActions = false
run { run {

View file

@ -25,7 +25,8 @@ data class ClientDataStatus(
val categories: Map<String, CategoryDataStatus>, val categories: Map<String, CategoryDataStatus>,
val userListVersion: String, val userListVersion: String,
val lastKeyRequestServerSequence: Long?, val lastKeyRequestServerSequence: Long?,
val lastKeyResponseServerSequence: Long? val lastKeyResponseServerSequence: Long?,
val dhKeyVersion: String?
) { ) {
companion object { companion object {
private const val DEVICES = "devices" private const val DEVICES = "devices"
@ -36,7 +37,8 @@ data class ClientDataStatus(
private const val DEVICES_DETAIL = "devicesDetail" private const val DEVICES_DETAIL = "devicesDetail"
private const val LAST_KEY_REQUEST_SEQUENCE = "kri" private const val LAST_KEY_REQUEST_SEQUENCE = "kri"
private const val LAST_KEY_RESPONSE_SEQUENCE = "kr" private const val LAST_KEY_RESPONSE_SEQUENCE = "kr"
private const val CLIENT_LEVEL_VALUE = 4 private const val DH = "dh"
private const val CLIENT_LEVEL_VALUE = 5
val empty = ClientDataStatus( val empty = ClientDataStatus(
deviceListVersion = "", deviceListVersion = "",
@ -45,7 +47,8 @@ data class ClientDataStatus(
categories = emptyMap(), categories = emptyMap(),
userListVersion = "", userListVersion = "",
lastKeyRequestServerSequence = null, lastKeyRequestServerSequence = null,
lastKeyResponseServerSequence = null lastKeyResponseServerSequence = null,
dhKeyVersion = null
) )
fun getClientDataStatusSync(database: Database): ClientDataStatus { fun getClientDataStatusSync(database: Database): ClientDataStatus {
@ -83,7 +86,8 @@ data class ClientDataStatus(
}, },
userListVersion = database.config().getUserListVersionSync(), userListVersion = database.config().getUserListVersionSync(),
lastKeyRequestServerSequence = database.config().getLastServerKeyRequestSequenceSync(), lastKeyRequestServerSequence = database.config().getLastServerKeyRequestSequenceSync(),
lastKeyResponseServerSequence = database.config().getLastServerKeyResponseSequenceSync() lastKeyResponseServerSequence = database.config().getLastServerKeyResponseSequenceSync(),
dhKeyVersion = database.config().getLastDhKeySync()?.version
) )
} }
} }
@ -123,6 +127,7 @@ data class ClientDataStatus(
lastKeyRequestServerSequence?.let { writer.name(LAST_KEY_REQUEST_SEQUENCE).value(it) } lastKeyRequestServerSequence?.let { writer.name(LAST_KEY_REQUEST_SEQUENCE).value(it) }
lastKeyResponseServerSequence?.let { writer.name(LAST_KEY_RESPONSE_SEQUENCE).value(it) } lastKeyResponseServerSequence?.let { writer.name(LAST_KEY_RESPONSE_SEQUENCE).value(it) }
dhKeyVersion?.let { writer.name(DH).value(it) }
writer.endObject() writer.endObject()
} }

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -18,38 +18,53 @@ package io.timelimit.android.sync.network
import android.util.JsonWriter import android.util.JsonWriter
import io.timelimit.android.async.Threads import io.timelimit.android.async.Threads
import io.timelimit.android.coroutines.executeAndWait import io.timelimit.android.coroutines.executeAndWait
import io.timelimit.android.crypto.DHHandshake
import io.timelimit.android.crypto.PasswordHashing import io.timelimit.android.crypto.PasswordHashing
import org.json.JSONObject import org.json.JSONObject
data class ParentPassword ( data class ParentPassword (
val parentPasswordHash: String, val parentPasswordHash: String,
val parentPasswordSecondHash: String, val parentPasswordSecondHash: String,
val parentPasswordSecondSalt: String val parentPasswordSecondSalt: String,
val encrypted: Boolean
) { ) {
companion object { companion object {
private const val HASH = "hash" private const val HASH = "hash"
private const val SECOND_HASH = "secondHash" private const val SECOND_HASH = "secondHash"
private const val SECOND_SALT = "secondSalt" private const val SECOND_SALT = "secondSalt"
private const val ENCRYPTED = "encrypted"
fun createSync(password: String): ParentPassword { fun createSync(password: String, dhKey: ServerDhKey?): ParentPassword {
val hash = PasswordHashing.hashSync(password)
val secondSalt = PasswordHashing.generateSalt() val secondSalt = PasswordHashing.generateSalt()
val secondHash = PasswordHashing.hashSyncWithSalt(password, secondSalt)
if (dhKey == null) {
return ParentPassword(
parentPasswordHash = hash,
parentPasswordSecondSalt = secondSalt,
parentPasswordSecondHash = secondHash,
encrypted = false
)
} else {
val handshake = DHHandshake.fromServerKey(dhKey)
val secondHashEncrypted = handshake.encrypt(
cryptData = secondHash.toByteArray(Charsets.US_ASCII),
authData = "ParentPassword:$hash:$secondSalt".toByteArray(Charsets.US_ASCII)
)
return ParentPassword( return ParentPassword(
parentPasswordHash = PasswordHashing.hashSync(password), parentPasswordHash = hash,
parentPasswordSecondSalt = secondSalt, parentPasswordSecondSalt = secondSalt,
parentPasswordSecondHash = PasswordHashing.hashSyncWithSalt(password, secondSalt) parentPasswordSecondHash = secondHashEncrypted,
encrypted = true
) )
} }
suspend fun createCoroutine(password: String) = Threads.crypto.executeAndWait {
createSync(password)
} }
fun parse(obj: JSONObject) = ParentPassword( suspend fun createCoroutine(password: String, dhKey: ServerDhKey?) = Threads.crypto.executeAndWait {
parentPasswordHash = obj.getString(HASH), createSync(password, dhKey)
parentPasswordSecondHash = obj.getString(SECOND_HASH), }
parentPasswordSecondSalt = obj.getString(SECOND_SALT)
)
} }
fun serialize(writer: JsonWriter) { fun serialize(writer: JsonWriter) {
@ -59,6 +74,8 @@ data class ParentPassword (
writer.name(SECOND_HASH).value(parentPasswordSecondHash) writer.name(SECOND_HASH).value(parentPasswordSecondHash)
writer.name(SECOND_SALT).value(parentPasswordSecondSalt) writer.name(SECOND_SALT).value(parentPasswordSecondSalt)
if (encrypted) writer.name(ENCRYPTED).value(true)
writer.endObject() writer.endObject()
} }
} }

View file

@ -46,6 +46,7 @@ data class ServerDataStatus(
val newUserList: ServerUserList?, val newUserList: ServerUserList?,
val pendingKeyRequests: List<ServerKeyRequest>, val pendingKeyRequests: List<ServerKeyRequest>,
val keyResponses: List<ServerKeyResponse>, val keyResponses: List<ServerKeyResponse>,
val dh: ServerDhKey?,
val fullVersionUntil: Long, val fullVersionUntil: Long,
val message: String?, val message: String?,
val apiLevel: Int val apiLevel: Int
@ -63,6 +64,7 @@ data class ServerDataStatus(
private const val NEW_USER_LIST = "users" private const val NEW_USER_LIST = "users"
private const val PENDING_KEY_REQUESTS = "krq" private const val PENDING_KEY_REQUESTS = "krq"
private const val KEY_RESPONSES = "kr" private const val KEY_RESPONSES = "kr"
private const val DH = "dh"
private const val FULL_VERSION_UNTIL = "fullVersion" private const val FULL_VERSION_UNTIL = "fullVersion"
private const val MESSAGE = "message" private const val MESSAGE = "message"
private const val API_LEVEL = "apiLevel" private const val API_LEVEL = "apiLevel"
@ -80,6 +82,7 @@ data class ServerDataStatus(
var newUserList: ServerUserList? = null var newUserList: ServerUserList? = null
var pendingKeyRequests = emptyList<ServerKeyRequest>() var pendingKeyRequests = emptyList<ServerKeyRequest>()
var keyResponses = emptyList<ServerKeyResponse>() var keyResponses = emptyList<ServerKeyResponse>()
var dh: ServerDhKey? = null
var fullVersionUntil: Long? = null var fullVersionUntil: Long? = null
var message: String? = null var message: String? = null
var apiLevel = 0 var apiLevel = 0
@ -99,6 +102,7 @@ data class ServerDataStatus(
NEW_USER_LIST -> newUserList = ServerUserList.parse(reader) NEW_USER_LIST -> newUserList = ServerUserList.parse(reader)
PENDING_KEY_REQUESTS -> pendingKeyRequests = ServerKeyRequest.parseList(reader) PENDING_KEY_REQUESTS -> pendingKeyRequests = ServerKeyRequest.parseList(reader)
KEY_RESPONSES -> keyResponses = ServerKeyResponse.parseList(reader) KEY_RESPONSES -> keyResponses = ServerKeyResponse.parseList(reader)
DH -> dh = ServerDhKey.parse(reader)
FULL_VERSION_UNTIL -> fullVersionUntil = reader.nextLong() FULL_VERSION_UNTIL -> fullVersionUntil = reader.nextLong()
MESSAGE -> message = reader.nextString() MESSAGE -> message = reader.nextString()
API_LEVEL -> apiLevel = reader.nextInt() API_LEVEL -> apiLevel = reader.nextInt()
@ -120,6 +124,7 @@ data class ServerDataStatus(
newUserList = newUserList, newUserList = newUserList,
pendingKeyRequests = pendingKeyRequests, pendingKeyRequests = pendingKeyRequests,
keyResponses = keyResponses, keyResponses = keyResponses,
dh = dh,
fullVersionUntil = fullVersionUntil!!, fullVersionUntil = fullVersionUntil!!,
message = message, message = message,
apiLevel = apiLevel apiLevel = apiLevel
@ -1265,3 +1270,30 @@ data class ServerKeyResponse(
fun parseList(reader: JsonReader) = parseJsonArray(reader) { parse(reader) } fun parseList(reader: JsonReader) = parseJsonArray(reader) { parse(reader) }
} }
} }
data class ServerDhKey(val version: String, val key: ByteArray) {
companion object {
private const val VERSION = "v"
private const val KEY = "k"
fun parse(reader: JsonReader): ServerDhKey {
var version: String? = null
var key: ByteArray? = null
reader.beginObject()
while (reader.hasNext()) {
when (reader.nextName()) {
VERSION -> version = reader.nextString()
KEY -> key = reader.nextString().parseBase64()
else -> reader.skipValue()
}
}
reader.endObject()
return ServerDhKey(
version = version!!,
key = key!!
)
}
}
}

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -22,6 +22,8 @@ import android.view.ViewGroup
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import io.timelimit.android.async.Threads
import io.timelimit.android.coroutines.executeAndWait
import io.timelimit.android.coroutines.runAsync import io.timelimit.android.coroutines.runAsync
import io.timelimit.android.data.model.UserType import io.timelimit.android.data.model.UserType
import io.timelimit.android.databinding.SetChildPasswordDialogFragmentBinding import io.timelimit.android.databinding.SetChildPasswordDialogFragmentBinding
@ -77,10 +79,12 @@ class SetChildPasswordDialogFragment: BottomSheetDialogFragment() {
dismissAllowingStateLoss() dismissAllowingStateLoss()
runAsync { runAsync {
val dhKey = Threads.database.executeAndWait { auth.logic.database.config().getLastDhKeySync() }
auth.tryDispatchParentAction( auth.tryDispatchParentAction(
SetChildPasswordAction( SetChildPasswordAction(
childId = childId, childId = childId,
newPassword = ParentPassword.createCoroutine(password) newPassword = ParentPassword.createCoroutine(password, dhKey)
) )
) )
} }

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -83,8 +83,10 @@ class UpdateChildPasswordViewModel(application: Application): AndroidViewModel(a
PasswordHashing.hashSyncWithSalt(oldPassword, userEntry.secondPasswordSalt) PasswordHashing.hashSyncWithSalt(oldPassword, userEntry.secondPasswordSalt)
} }
val dhKey = Threads.database.executeAndWait { logic.database.config().getLastDhKeySync() }
val action = ChildChangePasswordAction( val action = ChildChangePasswordAction(
password = ParentPassword.createCoroutine(newPassword) password = ParentPassword.createCoroutine(newPassword, dhKey)
) )
ApplyActionUtil.applyChildAction( ApplyActionUtil.applyChildAction(

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -99,7 +99,7 @@ class ChangeParentPasswordViewModel(application: Application): AndroidViewModel(
PasswordHashing.hashSyncWithSalt(oldPassword, userEntry.secondPasswordSalt) PasswordHashing.hashSyncWithSalt(oldPassword, userEntry.secondPasswordSalt)
} }
val newPasswordHash = ParentPassword.createCoroutine(newPassword) val newPasswordHash = ParentPassword.createCoroutine(newPassword, null)
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Log.d(LOG_TAG, "created hashs") Log.d(LOG_TAG, "created hashs")

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 - 2020 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -78,7 +78,7 @@ class RestoreParentPasswordViewModel(application: Application): AndroidViewModel
runAsync { runAsync {
try { try {
val parentPassword = ParentPassword.createCoroutine(newPassword) val parentPassword = ParentPassword.createCoroutine(newPassword, null)
val api = logic.serverLogic.getServerConfigCoroutine().api val api = logic.serverLogic.getServerConfigCoroutine().api
api.recoverPasswordByMailToken(mailAuthToken!!, parentPassword) api.recoverPasswordByMailToken(mailAuthToken!!, parentPassword)

View file

@ -72,6 +72,7 @@ class SetupDeviceModel(application: Application): AndroidViewModel(application)
var realUserId = userId var realUserId = userId
var realAllowedAppsCategory = allowedAppsCategory var realAllowedAppsCategory = allowedAppsCategory
val defaultCategories = DefaultCategories.with(getApplication()) val defaultCategories = DefaultCategories.with(getApplication())
val dhKey = Threads.database.executeAndWait { logic.database.config().getLastDhKeySync() }
val isUserAnChild = when (userId) { val isUserAnChild = when (userId) {
SetupDeviceFragment.NEW_PARENT -> { SetupDeviceFragment.NEW_PARENT -> {
@ -85,7 +86,7 @@ class SetupDeviceModel(application: Application): AndroidViewModel(application)
name = username, name = username,
timeZone = logic.timeApi.getSystemTimeZone().id, timeZone = logic.timeApi.getSystemTimeZone().id,
userType = UserType.Parent, userType = UserType.Parent,
password = ParentPassword.createCoroutine(password) password = ParentPassword.createCoroutine(password, dhKey)
) )
) )
@ -103,7 +104,7 @@ class SetupDeviceModel(application: Application): AndroidViewModel(application)
timeZone = logic.timeApi.getSystemTimeZone().id, timeZone = logic.timeApi.getSystemTimeZone().id,
userType = UserType.Child, userType = UserType.Child,
password = if (password.isEmpty()) null else ParentPassword.createCoroutine( password = if (password.isEmpty()) null else ParentPassword.createCoroutine(
password password, dhKey
) )
) )
) )

View file

@ -93,7 +93,7 @@ class SetupParentModeModel(application: Application): AndroidViewModel(applicati
val registerResponse = api.createFamilyByMailToken( val registerResponse = api.createFamilyByMailToken(
mailToken = mailAuthToken.value!!, mailToken = mailAuthToken.value!!,
parentPassword = ParentPassword.createCoroutine(parentPassword), parentPassword = ParentPassword.createCoroutine(parentPassword, null),
parentDevice = NewDeviceInfo( parentDevice = NewDeviceInfo(
model = deviceModelName model = deviceModelName
), ),

View file

@ -1,5 +1,5 @@
/* /*
* TimeLimit Copyright <C> 2019 Jonas Lochmann * TimeLimit Copyright <C> 2019 - 2022 Jonas Lochmann
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -18,6 +18,8 @@ package io.timelimit.android.ui.user.create
import android.app.Application import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import io.timelimit.android.async.Threads
import io.timelimit.android.coroutines.executeAndWait
import io.timelimit.android.coroutines.runAsync import io.timelimit.android.coroutines.runAsync
import io.timelimit.android.data.IdGenerator import io.timelimit.android.data.IdGenerator
import io.timelimit.android.data.model.AppRecommendation import io.timelimit.android.data.model.AppRecommendation
@ -45,13 +47,15 @@ class AddUserModel(application: Application): AndroidViewModel(application) {
createUserLock.withLock { createUserLock.withLock {
statusInternal.value = AddUserModelStatus.Working statusInternal.value = AddUserModelStatus.Working
val dhKey = Threads.database.executeAndWait { logic.database.config().getLastDhKeySync() }
when (type) { when (type) {
UserType.Parent -> { UserType.Parent -> {
if( if(
model.tryDispatchParentAction( model.tryDispatchParentAction(
AddUserAction( AddUserAction(
name = name, name = name,
password = ParentPassword.createCoroutine(password), password = ParentPassword.createCoroutine(password, dhKey),
userType = UserType.Parent, userType = UserType.Parent,
userId = IdGenerator.generateId(), userId = IdGenerator.generateId(),
timeZone = logic.timeApi.getSystemTimeZone().id timeZone = logic.timeApi.getSystemTimeZone().id
@ -74,7 +78,7 @@ class AddUserModel(application: Application): AndroidViewModel(application) {
val actions = ArrayList<ParentAction>(listOf( val actions = ArrayList<ParentAction>(listOf(
AddUserAction( AddUserAction(
name = name, name = name,
password = if (password.isEmpty()) null else ParentPassword.createCoroutine(password), password = if (password.isEmpty()) null else ParentPassword.createCoroutine(password, dhKey),
userType = UserType.Child, userType = UserType.Child,
userId = childId, userId = childId,
timeZone = logic.timeApi.getSystemTimeZone().id timeZone = logic.timeApi.getSystemTimeZone().id