diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java index 8b47e2a2..a1818902 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java @@ -1009,6 +1009,25 @@ public class KeyRingInfo { return new KeyAccessor.SubKey(this, new SubkeyIdentifier(keys, keyId)).getPreferredCompressionAlgorithms(); } + /** + * Returns true, if the certificate has at least one usable encryption subkey. + * + * @return true if usable for encryption + */ + public boolean isUsableForEncryption() { + return isUsableForEncryption(EncryptionPurpose.ANY); + } + + /** + * Returns true, if the certificate has at least one usable encryption subkey for the given purpose. + * + * @param purpose purpose of encryption + * @return true if usable for encryption + */ + public boolean isUsableForEncryption(@Nonnull EncryptionPurpose purpose) { + return !getEncryptionSubkeys(purpose).isEmpty(); + } + private KeyAccessor getKeyAccessor(@Nullable String userId, long keyID) { if (getPublicKey(keyID) == null) { throw new NoSuchElementException("No subkey with key id " + Long.toHexString(keyID) + " found on this key."); diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java index 8379e0cc..bfececee 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java @@ -218,7 +218,7 @@ public class KeyRingInfoTest { PGPSecretKeyRing secretKeys = PGPainless.buildKeyRing() .setPrimaryKey(KeySpec.getBuilder( - KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER)) + KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER)) .addSubkey(KeySpec.getBuilder( KeyType.ECDH(EllipticCurve._BRAINPOOLP384R1), KeyFlag.ENCRYPT_STORAGE)) @@ -758,4 +758,102 @@ public class KeyRingInfoTest { List emails = info.getEmailAddresses(); assertEquals(emails, Arrays.asList("alice@email.tld", "alice@pgpainless.org", "alice@openpgp.org", "alice@rfc4880.spec")); } + + @Test + public void isUsableForEncryptionTest_base() throws IOException { + String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: 9B6A C43E A67C 11BB C023 4CC3 69D5 9A7C 29C0 F858\n" + + "Comment: Usable \n" + + "\n" + + "mDMEYiS54BYJKwYBBAHaRw8BAQdAr0FXsDQtIpF54UwfjQb+8XJ3jxt3LkpCh0e7\n" + + "lH59Vzy0HlVzYWJsZSA8dXNhYmxlQHBncGFpbmxlc3Mub3JnPoiPBBMWCgBBBQJi\n" + + "JLngCRBp1Zp8KcD4WBYhBJtqxD6mfBG7wCNMw2nVmnwpwPhYAp4BApsBBRYCAwEA\n" + + "BAsJCAcFFQoJCAsCmQEAACuNAQDX+7/ffM2B9qaW+F9MkeUJeq9u8MLk+BcaotQZ\n" + + "/c+8pQD/RhaVmKTLjm+RmpG2O1lrkta4L5CQQBXYdNMnebhlLAu4OARiJLngEgor\n" + + "BgEEAZdVAQUBAQdA8Et257jQXR0oJOimAWU9Z5Erq5OcfguBI28ixgw5z2IDAQgH\n" + + "iHUEGBYKAB0FAmIkueACngECmwwFFgIDAQAECwkIBwUVCgkICwAKCRBp1Zp8KcD4\n" + + "WDQYAQDtJG06gAiFk7D1EqdtoTgBeIXi6pdKJ8VQA17/Sel1PgEAjO7Gy+RishFG\n" + + "eT0WwimGAGWOFgyIB8GCmuk1sEN+9wO4MwRiJLngFgkrBgEEAdpHDwEBB0BNGWZx\n" + + "IiCzs6Acu/e7Di9E+uUZmEA7geObWgwPleedLYjVBBgWCgB9BQJiJLngAp4BApsC\n" + + "BRYCAwEABAsJCAcFFQoJCAtfIAQZFgoABgUCYiS54AAKCRBsyz3UPPzzw6bTAQCZ\n" + + "4NnXfhuyw2itPKNnVSvPl72GgHzfVb2MZi2QBPFJyQD+K7Xl6qNcaI9VyMos8zSy\n" + + "VT74iE7Sraqu2Fck27y1wgMACgkQadWafCnA+FjLFwEAxb/GFdAoUgmY6DGIbatO\n" + + "LOIorswrgSQVZ8B1yLh1gxcA/2K3XO1Tl68O961SW60CijoBY/16EFC+mkQIzxTT\n" + + "J5wP\n" + + "=nFoO\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + + PGPPublicKeyRing cert = PGPainless.readKeyRing().publicKeyRing(CERT); + KeyRingInfo info = PGPainless.inspectKeyRing(cert); + assertTrue(info.isUsableForEncryption()); + } + + @Test + public void isUsableForEncryptionTest_commsOnly() throws IOException { + String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: B2EE 493D 1DAC 943A 1CBD B151 5F15 42D1 ACB7 D26F\n" + + "Comment: Comms Only \n" + + "\n" + + "mG8EYiS7mhMFK4EEACIDAwTENCF226L9l1i24ZpHuTK9P9kEc7neMZ1cQbJFSX9p\n" + + "ZP89dp4dnjZcAop5jzdvqjU98BgX9STZB6q2qYEG46luZoanDA0dpwzm0TENAvcr\n" + + "KoeIMqjv6dkKs5k11qtFx/K0JkNvbW1zIE9ubHkgPGNvbW1zLW9ubHlAcGdwYWlu\n" + + "bGVzcy5vcmc+iK8EExMKAEEFAmIku5sJEF8VQtGst9JvFiEEsu5JPR2slDocvbFR\n" + + "XxVC0ay30m8CngECmwMFFgIDAQAECwkIBwUVCgkICwKZAQAA3u4BgOl888SnxXys\n" + + "Ft/sPRh/hT8n0ObrxDHUgaAR5J7Sc3097u1r3ecCYaY045FYKKb23QGAjGSEEFG1\n" + + "TLbM1JMsE5H7xjjjJ5tTM6l45vkkrk3uMhsCL+QLv9pp251ctTF/JSCvuHMEYiS7\n" + + "mxIFK4EEACIDAwToE6c42GWSI0zmalisYewWvV/2Sfdo9KKgxfzX3rfldrOWFkN1\n" + + "fkLy6b01AUt3RqfwEBIJK6OrSXOlmdCiRV1Oqf20f2MGsDNXAttDApSSDJIHwV24\n" + + "3i6qylin0ujQ9KIDAQgHiJUEGBMKAB0FAmIku5sCngECmwQFFgIDAQAECwkIBwUV\n" + + "CgkICwAKCRBfFULRrLfSbwoYAYCzcZ29xIRUEHzZvAXWeHselBLdLGztZSBZKd9T\n" + + "m045mewePa780jk5o2z5Nt4Bj0EBfRxoiWt/czpy0nWpyfEeTHOx32jHHoTStjIF\n" + + "2XO/hpB2T8VXFfFKwj7U9LGkX+ciLg==\n" + + "=etPP\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(CERT); + KeyRingInfo info = PGPainless.inspectKeyRing(publicKeys); + + assertTrue(info.isUsableForEncryption(EncryptionPurpose.COMMUNICATIONS)); + assertTrue(info.isUsableForEncryption(EncryptionPurpose.ANY)); + + assertFalse(info.isUsableForEncryption(EncryptionPurpose.STORAGE)); + } + + @Test + public void isUsableForEncryptionTest_encryptionKeyRevoked() throws IOException { + // encryption subkey is revoked + String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: CE65 608D 8639 E20C 61BF 077B F010 3226 1C64 5EA7\n" + + "Comment: Revoked \n" + + "\n" + + "mDMEYiS8+hYJKwYBBAHaRw8BAQdATvSKAaY5yvyOdJtZXBEXbyiWSsExOwnP2L35\n" + + "AyMPe7u0IFJldm9rZWQgPHJldm9rZWRAcGdwYWlubGVzcy5vcmc+iI8EExYKAEEF\n" + + "AmIkvPoJEPAQMiYcZF6nFiEEzmVgjYY54gxhvwd78BAyJhxkXqcCngECmwEFFgID\n" + + "AQAECwkIBwUVCgkICwKZAQAAYFQA/02fMgRnneYK17Vsxc8DJEj0pVmTDHIOQH8K\n" + + "O8BuTkvhAP9zXtnJ7BsWO3Kg/ajIlaZEzMl6/lK2FTnAzBhs1UtrD7g4BGIkvPoS\n" + + "CisGAQQBl1UBBQEBB0AO8Bzm66ydlFhKtesh9EX66k4yyODeO0X3y3JUbrAnFQMB\n" + + "CAeIdQQYFgoAHQUCYiS8+gKeAQKbDAUWAgMBAAQLCQgHBRUKCQgLAAoJEPAQMiYc\n" + + "ZF6nTB0BAPjF6pUUrS3wv8CvrIM3S4BCtCOp+oQyPsie72As+47SAP41KfnvzYF3\n" + + "Y0WBp94Dqiy1MkvMZ9Q2x8BQt/L1UsoTBIh7BCgWCgAtBQJiJLz8CRDwEDImHGRe\n" + + "pxYhBM5lYI2GOeIMYb8He/AQMiYcZF6nAocAAh0DAAABqgD/TJpSDZ5fX3zNHqmN\n" + + "4TOuJ1GEkiYpPjBhem2C+U9jHjoBAJxQqzDB2VMiUDfe2+LLVIYa4EwhT2rT12qg\n" + + "aJ+TXWAJuDMEYiS8+hYJKwYBBAHaRw8BAQdAR0y6K6GPt4ddNyaRX16duqDFZwQi\n" + + "jeflFZ+UGLQ5GgSI1QQYFgoAfQUCYiS8+gKeAQKbAgUWAgMBAAQLCQgHBRUKCQgL\n" + + "XyAEGRYKAAYFAmIkvPoACgkQCX8koK2POrbPywEA3mbeGX8vWwnENtiFeMBjXNox\n" + + "oHAIuULBsvOdc1xrH0QBALezsulAJoziQ/t+EUrNHgTELDq3F8Y8tmLAJykb/nQB\n" + + "AAoJEPAQMiYcZF6n6CAA/0HadYoqOUbMjgu3Tle0HSXiTCJfBrTox5trTOKUsQ8z\n" + + "AQCjeV+3VT+u1movwIYv4XkzB6gB+B2C+DK9nvG5sXZhBg==\n" + + "=uqmO\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(CERT); + KeyRingInfo info = PGPainless.inspectKeyRing(publicKeys); + + assertFalse(info.isUsableForEncryption()); + assertFalse(info.isUsableForEncryption(EncryptionPurpose.ANY)); + assertFalse(info.isUsableForEncryption(EncryptionPurpose.COMMUNICATIONS)); + assertFalse(info.isUsableForEncryption(EncryptionPurpose.STORAGE)); + } }