From 8fe9d250a8f3839a8d7b8b0f2e7dba16ecd3134f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 8 Sep 2023 14:57:58 +0200 Subject: [PATCH] Kotlin conversion: KeyInfo --- .../java/org/pgpainless/key/info/KeyInfo.java | 139 ------------------ .../extensions/PGPPublicKeyExtensions.kt | 37 +++++ .../extensions/PGPSecretKeyExtensions.kt | 29 ++++ .../kotlin/org/pgpainless/key/info/KeyInfo.kt | 75 ++++++++++ .../key/protection/UnlockSecretKey.kt | 4 +- 5 files changed, 143 insertions(+), 141 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/info/KeyInfo.java create mode 100644 pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt create mode 100644 pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyInfo.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyInfo.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyInfo.java deleted file mode 100644 index f455608e..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyInfo.java +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub , 2021 Flowcrypt a.s. -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.info; - -import org.bouncycastle.asn1.ASN1ObjectIdentifier; -import org.bouncycastle.asn1.gnu.GNUObjectIdentifiers; -import org.bouncycastle.bcpg.ECDHPublicBCPGKey; -import org.bouncycastle.bcpg.ECDSAPublicBCPGKey; -import org.bouncycastle.bcpg.ECPublicBCPGKey; -import org.bouncycastle.bcpg.EdDSAPublicBCPGKey; -import org.bouncycastle.bcpg.S2K; -import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.key.generation.type.eddsa.EdDSACurve; - -public class KeyInfo { - - private final PGPSecretKey secretKey; - private final PGPPublicKey publicKey; - - public KeyInfo(PGPSecretKey secretKey) { - this.secretKey = secretKey; - this.publicKey = secretKey.getPublicKey(); - } - - public KeyInfo(PGPPublicKey publicKey) { - this.publicKey = publicKey; - this.secretKey = null; - } - - public String getCurveName() { - return getCurveName(publicKey); - } - - /** - * Returns indication that a contained secret key is encrypted. - * - * @return true if secret key is encrypted, false if secret key is not encrypted or there is public key only. - */ - public boolean isEncrypted() { - return secretKey != null && isEncrypted(secretKey); - } - - /** - * Returns indication that a contained secret key is not encrypted. - * - * @return true if secret key is not encrypted or there is public key only, false if secret key is encrypted. - */ - public boolean isDecrypted() { - return secretKey == null || isDecrypted(secretKey); - } - - /** - * Returns indication that a contained secret key has S2K of a type GNU_DUMMY_S2K. - * - * @return true if secret key has S2K of a type GNU_DUMMY_S2K, false if there is public key only, - * or S2K on the secret key is absent or not of a type GNU_DUMMY_S2K. - */ - public boolean hasDummyS2K() { - return secretKey != null && hasDummyS2K(secretKey); - } - - public static String getCurveName(PGPPublicKey publicKey) { - PublicKeyAlgorithm algorithm = PublicKeyAlgorithm.requireFromId(publicKey.getAlgorithm()); - ECPublicBCPGKey key; - switch (algorithm) { - case ECDSA: { - key = (ECDSAPublicBCPGKey) publicKey.getPublicKeyPacket().getKey(); - break; - } - case ECDH: { - key = (ECDHPublicBCPGKey) publicKey.getPublicKeyPacket().getKey(); - break; - } - case EDDSA: { - key = (EdDSAPublicBCPGKey) publicKey.getPublicKeyPacket().getKey(); - break; - } - default: - throw new IllegalArgumentException("Not an elliptic curve public key (" + algorithm + ")"); - } - return getCurveName(key); - } - - public static String getCurveName(ECPublicBCPGKey key) { - ASN1ObjectIdentifier identifier = key.getCurveOID(); - - String curveName = ECUtil.getCurveName(identifier); - if (curveName != null) { - return curveName; - } - - // Workaround for ECUtil not recognizing ed25519 - // see https://github.com/bcgit/bc-java/issues/1087 - // UPDATE: Apparently 1087 is not fixed properly with BC 1.71 - // See https://github.com/bcgit/bc-java/issues/1142 - // TODO: Remove once BC comes out with a fix. - if (identifier.equals(GNUObjectIdentifiers.Ed25519)) { - return EdDSACurve._Ed25519.getName(); - } - - return null; - } - - /** - * Returns indication that a secret key is encrypted. - * - * @param secretKey A secret key to examine. - * @return true if secret key is encrypted, false otherwise. - */ - public static boolean isEncrypted(PGPSecretKey secretKey) { - return secretKey.getS2KUsage() != 0; - } - - /** - * Returns indication that a secret key is not encrypted. - * - * @param secretKey A secret key to examine. - * @return true if secret key is encrypted, false otherwise. - */ - public static boolean isDecrypted(PGPSecretKey secretKey) { - return secretKey.getS2KUsage() == 0; - } - - /** - * Returns indication that a secret key has S2K of a type GNU_DUMMY_S2K. - * - * @param secretKey A secret key to examine. - * @return true if secret key has S2K of a type GNU_DUMMY_S2K, false otherwise. - */ - public static boolean hasDummyS2K(PGPSecretKey secretKey) { - final S2K s2k = secretKey.getS2K(); - return s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt new file mode 100644 index 00000000..a1ed72fe --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.bouncycastle.extensions + +import org.bouncycastle.asn1.gnu.GNUObjectIdentifiers +import org.bouncycastle.bcpg.ECDHPublicBCPGKey +import org.bouncycastle.bcpg.ECDSAPublicBCPGKey +import org.bouncycastle.bcpg.EdDSAPublicBCPGKey +import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil +import org.bouncycastle.openpgp.PGPPublicKey +import org.pgpainless.algorithm.PublicKeyAlgorithm +import org.pgpainless.key.generation.type.eddsa.EdDSACurve + +/** + * For secret keys of types [PublicKeyAlgorithm.ECDSA], [PublicKeyAlgorithm.ECDH] and [PublicKeyAlgorithm.EDDSA], + * this method returns the name of the underlying elliptic curve. + * + * For other key types or unknown curves, this method throws an [IllegalArgumentException]. + * + * @return curve name + */ +fun PGPPublicKey.getCurveName(): String { + PublicKeyAlgorithm.requireFromId(algorithm) + .let { + when (it) { + PublicKeyAlgorithm.ECDSA -> publicKeyPacket.key as ECDSAPublicBCPGKey + PublicKeyAlgorithm.ECDH -> publicKeyPacket.key as ECDHPublicBCPGKey + PublicKeyAlgorithm.EDDSA -> publicKeyPacket.key as EdDSAPublicBCPGKey + else -> throw IllegalArgumentException("No an elliptic curve public key ($it).") + } + } + .let { if (it.curveOID == GNUObjectIdentifiers.Ed25519) return EdDSACurve._Ed25519.curveName else it.curveOID} + .let { it to ECUtil.getCurveName(it) } + .let { if (it.second != null) return it.second else throw IllegalArgumentException("Unknown curve: ${it.first}") } +} diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt new file mode 100644 index 00000000..cba7bdba --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyExtensions.kt @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.bouncycastle.extensions + +import org.bouncycastle.bcpg.S2K +import org.bouncycastle.openpgp.PGPSecretKey + +/** + * Returns indication that the secret key is encrypted. + * + * @return true if secret key is encrypted, false otherwise. + */ +fun PGPSecretKey?.isEncrypted(): Boolean = (this != null) && (s2KUsage != 0) + +/** + * Returns indication that the secret key is not encrypted. + * + * @return true if secret key is encrypted, false otherwise. + */ +fun PGPSecretKey?.isDecrypted(): Boolean = (this == null) || (s2KUsage == 0) + +/** + * Returns indication that the secret key has S2K of a type GNU_DUMMY_S2K. + * + * @return true if secret key has S2K of type GNU_DUMMY_S2K, false otherwise. + */ +fun PGPSecretKey?.hasDummyS2K(): Boolean = (this != null) && (s2K?.type == S2K.GNU_DUMMY_S2K) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyInfo.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyInfo.kt new file mode 100644 index 00000000..652cf22d --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyInfo.kt @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub , 2021 Flowcrypt a.s. +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.info + +import org.bouncycastle.extensions.getCurveName +import org.bouncycastle.extensions.hasDummyS2K +import org.bouncycastle.extensions.isDecrypted +import org.bouncycastle.extensions.isEncrypted +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSecretKey + +@Deprecated("Deprecated in favor of extension functions to PGPSecretKey and PGPPublicKey.") +class KeyInfo private constructor( + val secretKey: PGPSecretKey?, + val publicKey: PGPPublicKey) { + + constructor(secretKey: PGPSecretKey): this(secretKey, secretKey.publicKey) + constructor(publicKey: PGPPublicKey): this(null, publicKey) + + /** + * Return the name of the elliptic curve used by this key, or throw an [IllegalArgumentException] if the key + * is not based on elliptic curves, or on an unknown curve. + */ + @Deprecated("Deprecated in favor of calling getCurveName() on the PGPPublicKey itself.", + ReplaceWith("publicKey.getCurveName()")) + val curveName: String + get() = publicKey.getCurveName() + + /** + * Return true, if the secret key is encrypted. + * This method returns false, if the secret key is null. + */ + @Deprecated("Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.", + ReplaceWith("secretKey.isEncrypted()")) + val isEncrypted: Boolean + get() = secretKey?.isEncrypted() ?: false + + /** + * Return true, if the secret key is decrypted. + * This method returns true, if the secret key is null. + */ + @Deprecated("Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.", + ReplaceWith("secretKey.isDecrypted()")) + val isDecrypted: Boolean + get() = secretKey?.isDecrypted() ?: true + + /** + * Return true, if the secret key is using the GNU_DUMMY_S2K s2k type. + * This method returns false, if the secret key is null. + */ + @Deprecated("Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.", + ReplaceWith("secretKey.hasDummyS2K()")) + val hasDummyS2K: Boolean + @JvmName("hasDummyS2K") + get() = secretKey?.hasDummyS2K() ?: false + + companion object { + @JvmStatic + @Deprecated("Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.", + ReplaceWith("secretKey.isEncrypted()")) + fun isEncrypted(secretKey: PGPSecretKey?) = secretKey.isEncrypted() + + @JvmStatic + @Deprecated("Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.", + ReplaceWith("secretKey.isDecrypted()")) + fun isDecrypted(secretKey: PGPSecretKey?) = secretKey.isDecrypted() + + @JvmStatic + @Deprecated("Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.", + ReplaceWith("secretKey.hasDummyS2K()")) + fun hasDummyS2K(secretKey: PGPSecretKey?) = secretKey.hasDummyS2K() + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt index 1317ef05..84d7d0d7 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt @@ -6,6 +6,7 @@ package org.pgpainless.key.protection import openpgp.openPgpKeyId +import org.bouncycastle.extensions.isEncrypted import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPPrivateKey import org.bouncycastle.openpgp.PGPSecretKey @@ -13,7 +14,6 @@ import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor import org.pgpainless.PGPainless import org.pgpainless.exception.KeyIntegrityException import org.pgpainless.exception.WrongPassphraseException -import org.pgpainless.key.info.KeyInfo import org.pgpainless.key.util.PublicKeyParameterValidationUtil import org.pgpainless.util.Passphrase import kotlin.jvm.Throws @@ -25,7 +25,7 @@ class UnlockSecretKey { @JvmStatic @Throws(PGPException::class, KeyIntegrityException::class) fun unlockSecretKey(secretKey: PGPSecretKey, protector: SecretKeyRingProtector): PGPPrivateKey { - return if (KeyInfo.isEncrypted(secretKey)) { + return if (secretKey.isEncrypted()) { unlockSecretKey(secretKey, protector.getDecryptor(secretKey.keyID)) } else { unlockSecretKey(secretKey, null as PBESecretKeyDecryptor?)