Gitnuro/src/main/kotlin/com/jetpackduba/gitnuro/credentials/CredentialsCacheRepository.kt
Abdelilah El Aissaoui 9e31145a86
Code cleanup
2023-12-24 21:30:09 +01:00

107 lines
3.6 KiB
Kotlin

package com.jetpackduba.gitnuro.credentials
import com.jetpackduba.gitnuro.extensions.lockUse
import kotlinx.coroutines.sync.Mutex
import org.eclipse.jgit.util.Base64
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import javax.inject.Inject
import javax.inject.Singleton
private const val KEY_LENGTH = 16
@Singleton
class CredentialsCacheRepository @Inject constructor() {
private val credentialsCached = mutableListOf<CredentialsType>()
private val credentialsLock = Mutex(false)
// having a random key to encrypt the password may help in case of a memory dump attack
private val encryptionKey = getRandomKey()
fun getCachedHttpCredentials(url: String): CredentialsType.HttpCredentials? {
val credentials = credentialsCached.filterIsInstance<CredentialsType.HttpCredentials>().firstOrNull {
it.url == url
}
return credentials?.copy(password = credentials.password.cipherDecrypt())
}
suspend fun cacheHttpCredentials(credentials: CredentialsType.HttpCredentials) {
cacheHttpCredentials(credentials.url, credentials.userName, credentials.password)
}
suspend fun cacheHttpCredentials(url: String, userName: String, password: String) {
val passwordEncrypted = password.cipherEncrypt()
credentialsLock.lockUse {
val previouslyCached = credentialsCached.any {
it is CredentialsType.HttpCredentials && it.url == url
}
if (!previouslyCached) {
val credentials = CredentialsType.HttpCredentials(url, userName, passwordEncrypted)
credentialsCached.add(credentials)
}
}
}
suspend fun cacheSshCredentials(sshKey: String, password: String) {
credentialsLock.lockUse {
val previouslyCached = credentialsCached.any {
it is CredentialsType.SshCredentials && it.sshKey == sshKey
}
if (!previouslyCached) {
val credentials = CredentialsType.SshCredentials(sshKey, password)
credentialsCached.add(credentials)
}
}
}
private fun String.cipherEncrypt(): String {
val secretKeySpec = SecretKeySpec(encryptionKey.toByteArray(), "AES")
val iv = encryptionKey.toByteArray()
val ivParameterSpec = IvParameterSpec(iv)
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)
val encryptedValue = cipher.doFinal(this.toByteArray())
return Base64.encodeBytes(encryptedValue)
}
private fun String.cipherDecrypt(): String {
val secretKeySpec = SecretKeySpec(encryptionKey.toByteArray(), "AES")
val iv = encryptionKey.toByteArray()
val ivParameterSpec = IvParameterSpec(iv)
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)
val decodedValue = Base64.decode(this)
val decryptedValue = cipher.doFinal(decodedValue)
return String(decryptedValue)
}
private fun getRandomKey(): String {
val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9') + "#!$%=?-_.,@µ*:;+~".toList()
return (1..KEY_LENGTH)
.map { allowedChars.random() }
.joinToString("")
}
}
sealed interface CredentialsType {
data class SshCredentials(
val sshKey: String,
val password: String,
) : CredentialsType
data class HttpCredentials(
val url: String,
val userName: String,
val password: String,
) : CredentialsType
}