Gitnuro/src/main/kotlin/com/jetpackduba/gitnuro/git/AppGpgSigner.kt
Abdelilah El Aissaoui 1bfa849f01
GPG: Added invalid password handling and caching
This change also introduces:
- Small refactor of credentials request state.
- Improvements to single field password dialog (like the one used in SSH) to be able to show the password as well as to add custom message errors.

Fixes #45
2023-02-26 23:47:03 +01:00

106 lines
3.5 KiB
Kotlin

package com.jetpackduba.gitnuro.git
import com.jetpackduba.gitnuro.credentials.GpgCredentialsProvider
import org.bouncycastle.openpgp.PGPException
import org.eclipse.jgit.api.errors.CanceledException
import org.eclipse.jgit.gpg.bc.internal.BouncyCastleGpgSigner
import org.eclipse.jgit.lib.CommitBuilder
import org.eclipse.jgit.lib.GpgConfig
import org.eclipse.jgit.lib.ObjectBuilder
import org.eclipse.jgit.lib.PersonIdent
import org.eclipse.jgit.transport.CredentialsProvider
import javax.inject.Inject
import javax.inject.Provider
private const val INVALID_PASSWORD_MESSAGE = "Is the entered passphrase correct?"
class AppGpgSigner @Inject constructor(
private val gpgCredentialsProvider: Provider<GpgCredentialsProvider>,
) : BouncyCastleGpgSigner() {
override fun sign(
commit: CommitBuilder,
gpgSigningKey: String,
committer: PersonIdent,
credentialsProvider: CredentialsProvider?
) {
super.sign(commit, gpgSigningKey, committer, gpgCredentialsProvider.get())
}
override fun canLocateSigningKey(
gpgSigningKey: String,
committer: PersonIdent,
credentialsProvider: CredentialsProvider?
): Boolean {
return super.canLocateSigningKey(gpgSigningKey, committer, gpgCredentialsProvider.get())
}
override fun canLocateSigningKey(
gpgSigningKey: String,
committer: PersonIdent,
credentialsProvider: CredentialsProvider?,
config: GpgConfig?
): Boolean {
return super.canLocateSigningKey(gpgSigningKey, committer, gpgCredentialsProvider.get(), config)
}
override fun signObject(
`object`: ObjectBuilder,
gpgSigningKey: String?,
committer: PersonIdent,
credentialsProvider: CredentialsProvider?,
config: GpgConfig?
) {
val gpgCredentialsProvider = gpgCredentialsProvider.get()
try {
retryIfWrongPassphrase { isRetry ->
gpgCredentialsProvider.isRetry = isRetry
super.signObject(`object`, gpgSigningKey, committer, gpgCredentialsProvider, config)
gpgCredentialsProvider.savePasswordInMemory()
}
} catch (ex: CanceledException) {
println("Signing cancelled")
}
}
private fun retryIfWrongPassphrase(block: (isRetry: Boolean) -> Unit) {
var isPasswordCorrect = false
var retries = 0
while (!isPasswordCorrect) {
isPasswordCorrect = true
try {
block(retries > 0)
} catch (ex: Exception) {
ex.printStackTrace()
val pgpException = getPgpExceptionTypeOrNull(ex)
val pgpMessage = pgpException?.message
if (pgpMessage != null && pgpMessage.contains(INVALID_PASSWORD_MESSAGE)) {
isPasswordCorrect = false // Only set it to false if we've got this specific message
retries++
} else
throw ex
}
}
}
private fun getPgpExceptionTypeOrNull(ex: Throwable?): PGPException? {
var currentException: Throwable? = ex
while (currentException != null) {
if (currentException is PGPException) {
return currentException
} else {
currentException = when (currentException.cause) {
currentException -> null // Cause can be the same as current exception, so just return null
else -> currentException.cause
}
}
}
return null
}
}