107 lines
3.6 KiB
Kotlin
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
|
|
}
|