aboutsummaryrefslogtreecommitdiff
path: root/crypto-pgpainless
diff options
context:
space:
mode:
authorTad Fisher <tadfisher@gmail.com>2022-10-09 16:11:28 -0700
committerTad Fisher <tadfisher@gmail.com>2022-10-09 16:17:25 -0700
commita716ac9514577110481434ac2afdc64d97e02375 (patch)
tree92c310139cef2032d2c9b809648e92f50e24d10f /crypto-pgpainless
parent75040136ae5ca6108335975430b411f8a560d0ba (diff)
Quick and dirty hardware key import
Diffstat (limited to 'crypto-pgpainless')
-rw-r--r--crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/KeyUtils.kt91
-rw-r--r--crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPKeyManager.kt11
-rw-r--r--crypto-pgpainless/src/main/kotlin/org/bouncycastle/bcpg/GnuExtendedS2K.kt17
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
+ }
+}