mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-03 09:49:25 +02:00
Add support for encrypted second password hashes
This commit is contained in:
parent
c135a640c6
commit
15168bed66
14 changed files with 207 additions and 35 deletions
79
app/src/main/java/io/timelimit/android/crypto/DH.kt
Normal file
79
app/src/main/java/io/timelimit/android/crypto/DH.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
),
|
),
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue