diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/SelfCertifyingRevocationTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/SelfCertifyingRevocationTest.java new file mode 100644 index 00000000..5923f352 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/key/SelfCertifyingRevocationTest.java @@ -0,0 +1,179 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.key.info.KeyRingInfo; +import org.pgpainless.util.CollectionUtils; + +/** + * Test for v6-style self-certifying revocations. + * @see + * OpenPGP v6 Revocation Certificate + */ +public class SelfCertifyingRevocationTest { + + private static final String SECRET_KEY = "" + + "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: 8139 D35E 40CB 1A6E 0E0F B769 7FD2 60B1 95C2 A78F\n" + + "Comment: Alice \n" + + "\n" + + "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\n" + + "sI1nYaqkdT6UC4PRnl3P\n" + + "=isPD\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + + private static final String SELF_CERTIFYING_REVOCATION = "" + + "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: 8139 D35E 40CB 1A6E 0E0F B769 7FD2 60B1 95C2 A78F\n" + + "\n" + + "mQGNBGSIp4gBDADhFvBqIHyyYdyhiXH0UZNDDIcKY3g3KLRFN0DZU1LNUkm1E9Cc\n" + + "y81hyvEFDrSQGyn8bKmTQF/P3RzU4WTyQerwSmegycsOVr6c9b/s+Pz+EWaP7hHS\n" + + "RN2Hr83GUcVRXZO4Q6wFJItkiJxgbigb7Xlkd45/synVqcLuDQNUvREITKvqLtTI\n" + + "ZIWoRI5RYUqr4qIX3zDGd8c2/6W2z1LwFpiK5j68q40mDsK/z0lei3Up9SWho3Vk\n" + + "qPOcWVPWQBoxOtfFTNeZntuL48KicEnwFB5HOXMbo98/j1mhk/je6HedxkkKIndw\n" + + "1T7//eqi/+fVOBkGTzLby1ZaDVZXKEU7Sg8WhLPAD/s30PhUR8s7av3rj+ZbJcho\n" + + "L7TeC80ndQw0OHbHnCTAZFYpeZZq93lAm544CrRlkzSQyjpyvfViHcWcB37iWZld\n" + + "TV1Y7mwM4h7Lcc7U6ZvgDFzNWImol6G3vpSGtSdaD3ZUsBQsyJKa3kczB+r0MGJw\n" + + "XgmCufWgj/sB+zMAEQEAAYkBtgQgAQoAKgUCZIinigkQf9JgsZXCp48WIQSBOdNe\n" + + "QMsabg4Pt2l/0mCxlcKnjwKHAAAA35EL/3Qubi7A9rkFxcz+YF2AoLOqRJA4p5VA\n" + + "oxUGQSIyOvRKBgoZJugvM330DP2shJmJWSgsZqt/obXkZNbPCdjQ0hXR+Ih1/tdh\n" + + "Mf4lCD9vPx9V88xFo37BYZCnaEk93NTDGrJ6sjvo9GQkm9x2Rlrzek//jmQ2wqvV\n" + + "GviYDBADhWzuJzyibdo1Re/4ESRlGpzN0bFf0j1/uSvEBq41Yq3UXXZqg/NX01id\n" + + "t4P0dxGYfQTXwzTxyYxn7rbdFJUAc5uxoGyDZzzVgoXwgNu8aE92faztALajagxF\n" + + "P3NpfT+TlZ51hsNZcIz8tnQFUyYLNeAycYI4iCC/Vd5RPhK8t9Py9Sow2Tk4Pdf4\n" + + "5l8MSIBsrtHiL/ElwFlnMP5Ffwuqfhnb/29yK0tWVpbRkE199tvU3C8iggyhdZYC\n" + + "/8NvYSU/RysdDwgDH1j1WWxuFTnaRCpzP/jPZ9sA6NFQtbOYjN6FLO+78gDZbuDv\n" + + "Il9nFzjaEHbjZ7QVqINZxSCQe8NYmjfeDQ==\n" + + "=zlRL\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + + @Test + public void parseSelfCertifyingRevocation() throws IOException { + PGPPublicKeyRing revocation = PGPainless.readKeyRing().publicKeyRing(SELF_CERTIFYING_REVOCATION); + assertNotNull(revocation); + assertEquals(1, CollectionUtils.iteratorToList(revocation.getPublicKeys()).size()); + } + + @Test + public void selfCertifyingRevocationIsRevoked() throws IOException { + PGPPublicKeyRing revocation = PGPainless.readKeyRing().publicKeyRing(SELF_CERTIFYING_REVOCATION); + assertNotNull(revocation); + KeyRingInfo info = PGPainless.inspectKeyRing(revocation); + assertTrue(info.getRevocationState().isHardRevocation()); + } + + @Test + public void mergeCertificatesResultsInRevokedKey() throws IOException, PGPException { + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(SECRET_KEY); + assertNotNull(secretKeys); + PGPPublicKeyRing publicKeys = PGPainless.extractCertificate(secretKeys); + KeyRingInfo info = PGPainless.inspectKeyRing(publicKeys); + assertTrue(info.getRevocationState().isNotRevoked()); + + PGPPublicKeyRing revocation = PGPainless.readKeyRing().publicKeyRing(SELF_CERTIFYING_REVOCATION); + assertNotNull(revocation); + + PGPPublicKeyRing merged = PGPainless.mergeCertificate(publicKeys, revocation); + info = PGPainless.inspectKeyRing(merged); + assertTrue(info.getRevocationState().isHardRevocation()); + } +}