// SPDX-FileCopyrightText: 2023 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 package org.pgpainless.key.util import java.io.ByteArrayOutputStream import java.io.IOException import java.math.BigInteger import java.security.SecureRandom import org.bouncycastle.bcpg.* import org.bouncycastle.openpgp.* import org.bouncycastle.util.Arrays import org.bouncycastle.util.io.Streams import org.pgpainless.algorithm.HashAlgorithm import org.pgpainless.algorithm.PublicKeyAlgorithm.Companion.requireFromId import org.pgpainless.algorithm.SignatureType import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.bouncycastle.extensions.publicKeyAlgorithm import org.pgpainless.exception.KeyIntegrityException import org.pgpainless.implementation.ImplementationFactory.Companion.getInstance /** * Utility class to verify keys against Key Overwriting (KO) attacks. This class of attacks is only * possible if the attacker has access to the (encrypted) secret key material. To execute the * attack, they would modify the unauthenticated parameters of the users public key. Using the * modified public key in combination with the unmodified secret key material can then lead to the * extraction of secret key parameters via weakly crafted messages. * * @see Key Overwriting (KO) Attacks against OpenPGP */ class PublicKeyParameterValidationUtil { companion object { @JvmStatic @Throws(KeyIntegrityException::class) fun verifyPublicKeyParameterIntegrity(privateKey: PGPPrivateKey, publicKey: PGPPublicKey) { val algorithm = publicKey.publicKeyAlgorithm var valid = true val key = privateKey.privateKeyDataPacket when (privateKey.privateKeyDataPacket) { is RSASecretBCPGKey -> valid = verifyRSAKeyIntegrity( key as RSASecretBCPGKey, publicKey.publicKeyPacket.key as RSAPublicBCPGKey) is EdSecretBCPGKey -> valid = verifyEdDsaKeyIntegrity( key as EdSecretBCPGKey, publicKey.publicKeyPacket.key as EdDSAPublicBCPGKey) is DSASecretBCPGKey -> valid = verifyDsaKeyIntegrity( key as DSASecretBCPGKey, publicKey.publicKeyPacket.key as DSAPublicBCPGKey) is ElGamalSecretBCPGKey -> valid = verifyElGamalKeyIntegrity( key as ElGamalSecretBCPGKey, publicKey.publicKeyPacket.key as ElGamalPublicBCPGKey) } if (!valid) throw KeyIntegrityException() // Additional to the algorithm-specific tests further above, we also perform // generic functionality tests with the key, such as whether it is able to decrypt // encrypted data // or verify signatures. // These tests should be more or less constant time. if (algorithm.isSigningCapable()) { valid = verifyCanSign(privateKey, publicKey) } if (algorithm.isEncryptionCapable()) { valid = valid and verifyCanDecrypt(privateKey, publicKey) } if (!valid) throw KeyIntegrityException() } @JvmStatic @Throws(KeyIntegrityException::class) private fun verifyRSAKeyIntegrity( secretKey: RSASecretBCPGKey, publicKey: RSAPublicBCPGKey ): Boolean { // Verify that the public keys N is equal to private keys p*q return publicKey.modulus.equals(secretKey.primeP.multiply(secretKey.primeQ)) } @JvmStatic @Throws(KeyIntegrityException::class) private fun verifyEdDsaKeyIntegrity( secretKey: EdSecretBCPGKey, publicKey: EdDSAPublicBCPGKey ): Boolean { // TODO: Implement return true } @JvmStatic @Throws(KeyIntegrityException::class) private fun verifyDsaKeyIntegrity( privateKey: DSASecretBCPGKey, publicKey: DSAPublicBCPGKey ): Boolean { // Not sure what value to put here in order to have a "robust" primality check // I went with 40, since that's what SO recommends: // https://stackoverflow.com/a/6330138 val certainty = 40 val pG = publicKey.g val pP = publicKey.p val pQ = publicKey.q val pY = publicKey.y val sX = privateKey.x val pPrime = pP.isProbablePrime(certainty) if (!pPrime) { return false } val qPrime = pQ.isProbablePrime(certainty) if (!qPrime) { return false } // q > 160 bits val qLarge = pQ.bitLength() > 160 if (!qLarge) { return false } // q divides p - 1 val qDividesPminus1 = pP.subtract(BigInteger.ONE).mod(pQ) == BigInteger.ZERO if (!qDividesPminus1) { return false } // 1 < g < p val gInBounds = BigInteger.ONE.max(pG) == pG && pG.max(pP) == pP if (!gInBounds) { return false } // g^q = 1 mod p val gPowXModPEquals1 = pG.modPow(pQ, pP) == BigInteger.ONE if (!gPowXModPEquals1) { return false } // y = g^x mod p return pY == pG.modPow(sX, pP) } /** * Validate ElGamal public key parameters. * * Original implementation by the openpgpjs authors: