diff options
Diffstat (limited to 'crypto-pgpainless')
3 files changed, 116 insertions, 3 deletions
diff --git a/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/KeyUtils.kt b/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/KeyUtils.kt index d1487903..bd303e66 100644 --- a/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/KeyUtils.kt +++ b/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/KeyUtils.kt @@ -9,7 +9,15 @@ import app.passwordstore.crypto.GpgIdentifier.KeyId import app.passwordstore.crypto.GpgIdentifier.UserId import com.github.michaelbull.result.get import com.github.michaelbull.result.runCatching +import java.io.ByteArrayOutputStream +import org.bouncycastle.bcpg.GnuExtendedS2K +import org.bouncycastle.bcpg.S2K +import org.bouncycastle.bcpg.SecretKeyPacket +import org.bouncycastle.bcpg.SecretSubkeyPacket +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags import org.bouncycastle.openpgp.PGPKeyRing +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.PGPSecretKey import org.bouncycastle.openpgp.PGPSecretKeyRing import org.pgpainless.algorithm.EncryptionPurpose @@ -37,6 +45,29 @@ public object KeyUtils { val keyRing = tryParseKeyring(key) ?: return null return UserId(keyRing.publicKey.userIDs.next()) } + + public fun tryCreateStubKey( + publicKey: PGPKey, + serial: ByteArray, + stubFingerprints: List<OpenPgpFingerprint> + ): PGPKey? { + val keyRing = tryParseKeyring(publicKey) as? PGPPublicKeyRing ?: return null + val secretKeyRing = + keyRing + .fold(PGPSecretKeyRing(emptyList())) { ring, key -> + PGPSecretKeyRing.insertSecretKey( + ring, + if (stubFingerprints.any { it == OpenPgpFingerprint.parseFromBinary(key.fingerprint) }) { + toCardSecretKey(key, serial) + } else { + toDummySecretKey(key) + } + ) + } + + return PGPKey(secretKeyRing.encoded) + } + public fun tryGetEncryptionKeyFingerprint(key: PGPKey): OpenPgpFingerprint? { val keyRing = tryParseKeyring(key) ?: return null val encryptionSubkey = @@ -59,3 +90,63 @@ public object KeyUtils { return info.getSecretKey(encryptionKey.keyID) } } + +private fun toDummySecretKey(publicKey: PGPPublicKey): PGPSecretKey { + + return PGPSecretKey( + if (publicKey.isMasterKey) { + SecretKeyPacket( + publicKey.publicKeyPacket, + SymmetricKeyAlgorithmTags.NULL, + SecretKeyPacket.USAGE_CHECKSUM, + GnuExtendedS2K(S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY), + byteArrayOf(), + byteArrayOf() + ) + } else { + SecretSubkeyPacket( + publicKey.publicKeyPacket, + SymmetricKeyAlgorithmTags.NULL, + SecretKeyPacket.USAGE_CHECKSUM, + GnuExtendedS2K(S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY), + byteArrayOf(), + byteArrayOf() + ) + }, + publicKey + ) +} + +@Suppress("MagicNumber") +private fun toCardSecretKey(publicKey: PGPPublicKey, serial: ByteArray): PGPSecretKey { + return PGPSecretKey( + if (publicKey.isMasterKey) { + SecretKeyPacket( + publicKey.publicKeyPacket, + SymmetricKeyAlgorithmTags.NULL, + SecretKeyPacket.USAGE_CHECKSUM, + GnuExtendedS2K(S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD), + ByteArray(8), + encodeSerial(serial), + ) + } else { + SecretSubkeyPacket( + publicKey.publicKeyPacket, + SymmetricKeyAlgorithmTags.NULL, + SecretKeyPacket.USAGE_CHECKSUM, + GnuExtendedS2K(S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD), + ByteArray(8), + encodeSerial(serial), + ) + }, + publicKey + ) +} + +@Suppress("MagicNumber") +private fun encodeSerial(serial: ByteArray): ByteArray { + val out = ByteArrayOutputStream() + out.write(serial.size) + out.write(serial, 0, minOf(16, serial.size)) + return out.toByteArray() +} diff --git a/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPKeyManager.kt b/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPKeyManager.kt index be2ec474..786694d4 100644 --- a/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPKeyManager.kt +++ b/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPKeyManager.kt @@ -9,12 +9,12 @@ package app.passwordstore.crypto import androidx.annotation.VisibleForTesting import app.passwordstore.crypto.KeyUtils.tryGetId import app.passwordstore.crypto.KeyUtils.tryParseKeyring -import app.passwordstore.crypto.errors.InvalidKeyException import app.passwordstore.crypto.errors.KeyAlreadyExistsException import app.passwordstore.crypto.errors.KeyDeletionFailedException import app.passwordstore.crypto.errors.KeyDirectoryUnavailableException import app.passwordstore.crypto.errors.KeyNotFoundException import app.passwordstore.crypto.errors.NoKeysAvailableException +import app.passwordstore.crypto.errors.NoSecretKeyException import app.passwordstore.util.coroutines.runSuspendCatching import com.github.michaelbull.result.Result import com.github.michaelbull.result.unwrap @@ -40,12 +40,17 @@ constructor( withContext(dispatcher) { runSuspendCatching { if (!keyDirExists()) throw KeyDirectoryUnavailableException - val incomingKeyRing = tryParseKeyring(key) ?: throw InvalidKeyException + val incomingKeyRing = tryParseKeyring(key) + + if (incomingKeyRing is PGPPublicKeyRing) { + throw NoSecretKeyException(tryGetId(key)?.toString() ?: "Failed to retrieve key ID") + } + val keyFile = File(keyDir, "${tryGetId(key)}.$KEY_EXTENSION") if (keyFile.exists()) { val existingKeyBytes = keyFile.readBytes() val existingKeyRing = - tryParseKeyring(PGPKey(existingKeyBytes)) ?: throw InvalidKeyException + tryParseKeyring(PGPKey(existingKeyBytes)) when { existingKeyRing is PGPPublicKeyRing && incomingKeyRing is PGPSecretKeyRing -> { keyFile.writeBytes(key.contents) diff --git a/crypto-pgpainless/src/main/kotlin/org/bouncycastle/bcpg/GnuExtendedS2K.kt b/crypto-pgpainless/src/main/kotlin/org/bouncycastle/bcpg/GnuExtendedS2K.kt new file mode 100644 index 00000000..a5ebad78 --- /dev/null +++ b/crypto-pgpainless/src/main/kotlin/org/bouncycastle/bcpg/GnuExtendedS2K.kt @@ -0,0 +1,17 @@ +package org.bouncycastle.bcpg + +/** + * Add a constructor for GNU-extended S2K + * + * This extension is documented on GnuPG documentation DETAILS file, + * section "GNU extensions to the S2K algorithm". Its support is + * already present in S2K class but lack for a constructor. + * + * @author LĂ©onard Dallot <leonard.dallot@taztag.com> + */ +public class GnuExtendedS2K(mode: Int) : S2K(SIMPLE) { + init { + this.type = GNU_DUMMY_S2K + this.protectionMode = mode + } +} |