diff --git a/pg/src/test/java/org/bouncycastle/bcpg/test/AllTests.java b/pg/src/test/java/org/bouncycastle/bcpg/test/AllTests.java index 3c9a76ba1a..ef6670986c 100644 --- a/pg/src/test/java/org/bouncycastle/bcpg/test/AllTests.java +++ b/pg/src/test/java/org/bouncycastle/bcpg/test/AllTests.java @@ -18,7 +18,9 @@ public void testPacketParsing() Security.addProvider(new BouncyCastleProvider()); org.bouncycastle.util.test.Test[] tests = new org.bouncycastle.util.test.Test[] { - new V6SignaturePacketTest() + new SignaturePacketTest(), + new OnePassSignaturePacketTest(), + new OpenPgpMessageTest() }; for (int i = 0; i != tests.length; i++) diff --git a/pg/src/test/java/org/bouncycastle/bcpg/test/OnePassSignaturePacketTest.java b/pg/src/test/java/org/bouncycastle/bcpg/test/OnePassSignaturePacketTest.java new file mode 100644 index 0000000000..98f7f6a27f --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/bcpg/test/OnePassSignaturePacketTest.java @@ -0,0 +1,69 @@ +package org.bouncycastle.bcpg.test; + +import org.bouncycastle.bcpg.*; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.test.SimpleTest; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class OnePassSignaturePacketTest + extends SimpleTest +{ + + // Version 6 OnePassSignature packet + // extracted from https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-inline-signed-messag + public static final byte[] OPS_V6 = Hex.decode("c44606010a1b2076495f50218890f7f5e2ee3c1822514f70500f551d86e5c921e404e34a53fbaccb186c4f0609a697e4d52dfa6c722b0c1f1e27c18a56708f6525ec27bad9acc901"); + // Issuer of the message + public static byte[] ISSUER = Hex.decode("CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9"); + // Salt used to generate the signature + public static byte[] SALT = Hex.decode("76495F50218890F7F5E2EE3C1822514F70500F551D86E5C921E404E34A53FBAC"); + + // Parse v6 OPS packet and compare its values to a known-good test vector + private void testParseV6OnePassSignaturePacket() throws IOException { + ByteArrayInputStream bIn = new ByteArrayInputStream(OPS_V6); + BCPGInputStream pIn = new BCPGInputStream(bIn); + + // Parse and compare the OnePassSignature packet + OnePassSignaturePacket ops = (OnePassSignaturePacket) pIn.readPacket(); + isEquals("OPS packet MUST be of version 6", + OnePassSignaturePacket.VERSION_6, ops.getVersion()); + isTrue("OPS packet issuer fingerprint mismatch", + Arrays.areEqual(ISSUER, ops.getFingerprint())); + isTrue("OPS packet salt mismatch", + Arrays.areEqual(SALT, ops.getSalt())); + isTrue("OPS packet isContaining mismatch", + ops.isContaining()); + } + + private void testEncodeV6OPS() throws IOException { + ByteArrayInputStream bIn = new ByteArrayInputStream(OPS_V6); + BCPGInputStream pIn = new BCPGInputStream(bIn); + OnePassSignaturePacket ops = (OnePassSignaturePacket) pIn.readPacket(); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pOut = new BCPGOutputStream(bOut, true); + ops.encode(pOut); + pOut.close(); + + isTrue("Encoding mismatch of OPS v6 packet", + Arrays.areEqual(OPS_V6, bOut.toByteArray())); + } + + @Override + public String getName() { + return OnePassSignaturePacketTest.class.getSimpleName(); + } + + @Override + public void performTest() throws Exception { + testParseV6OnePassSignaturePacket(); + testEncodeV6OPS(); + } + + public static void main(String[] args) { + runTest(new OnePassSignaturePacketTest()); + } +} diff --git a/pg/src/test/java/org/bouncycastle/bcpg/test/V6SignaturePacketTest.java b/pg/src/test/java/org/bouncycastle/bcpg/test/OpenPgpMessageTest.java similarity index 84% rename from pg/src/test/java/org/bouncycastle/bcpg/test/V6SignaturePacketTest.java rename to pg/src/test/java/org/bouncycastle/bcpg/test/OpenPgpMessageTest.java index 9c48beedc5..95762a8fb7 100644 --- a/pg/src/test/java/org/bouncycastle/bcpg/test/V6SignaturePacketTest.java +++ b/pg/src/test/java/org/bouncycastle/bcpg/test/OpenPgpMessageTest.java @@ -14,8 +14,7 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; -public class V6SignaturePacketTest extends SimpleTest -{ +public class OpenPgpMessageTest extends SimpleTest { /* Inline-signed message using a version 6 signature @@ -51,8 +50,7 @@ public class V6SignaturePacketTest extends SimpleTest "/FvLFuGWMbKAdA+epq7V4HOtAPlBWmU8QOd6aud+aSunHQaaEJ+iTFjP2OMW0KBr\n" + "NK2ay45cX1IVAQ==\n" + "-----END PGP SIGNATURE-----"; - // Only the hex-encoded signature packet - public static final byte[] SIGNATURE = Hex.decode("c29806011b0a0000002905826398a363222106cb186c4f0609a697e4d52dfa6c722b0c1f1e27c18a56708f6525ec27bad9acc90000000069362076495f50218890f7f5e2ee3c1822514f70500f551d86e5c921e404e34a53fbac27d06fb80aa8fc5bcb16e19631b280740f9ea6aed5e073ad00f9415a653c40e77a6ae77e692ba71d069a109fa24c58cfd8e316d0a06b34ad9acb8e5c5f521501"); + // Content of the message's LiteralData packet public static final String CONTENT = "What we need from the grocery store:\n" + "\n" + @@ -64,20 +62,6 @@ public class V6SignaturePacketTest extends SimpleTest // Salt used to generate the signature public static byte[] SALT = Hex.decode("76495F50218890F7F5E2EE3C1822514F70500F551D86E5C921E404E34A53FBAC"); - @Override - public String getName() - { - return V6SignaturePacketTest.class.getSimpleName(); - } - - @Override - public void performTest() - throws Exception - { - testParseV6InlineSignedMessage(); - testParseV6CleartextSignedMessage(); - testParseAndReencodeSignature(); - } private void testParseV6CleartextSignedMessage() throws IOException @@ -131,18 +115,6 @@ private void testParseV6InlineSignedMessage() compareSignature(sig); } - private void testParseAndReencodeSignature() throws IOException { - BCPGInputStream pIn = new BCPGInputStream(new ByteArrayInputStream(SIGNATURE)); - - // Parse and compare the Signature packet - SignaturePacket sig = (SignaturePacket) pIn.readPacket(); - ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - BCPGOutputStream bcOut = new BCPGOutputStream(bOut, true); - sig.encode(bcOut); - bcOut.close(); - isTrue("Signature packet encoding mismatch", - Arrays.areEqual(bOut.toByteArray(), SIGNATURE)); - } private void compareLiteralData(LiteralDataPacket lit) throws IOException @@ -183,8 +155,18 @@ private void compareSignature(SignaturePacket sig) 0, sig.getUnhashedSubPackets().length); } - public static void main(String[] args) - { - runTest(new V6SignaturePacketTest()); + @Override + public String getName() { + return OpenPgpMessageTest.class.getSimpleName(); + } + + @Override + public void performTest() throws Exception { + testParseV6CleartextSignedMessage(); + testParseV6InlineSignedMessage(); + } + + public static void main(String[] args) { + runTest(new OpenPgpMessageTest()); } } diff --git a/pg/src/test/java/org/bouncycastle/bcpg/test/SignaturePacketTest.java b/pg/src/test/java/org/bouncycastle/bcpg/test/SignaturePacketTest.java new file mode 100644 index 0000000000..b03f22fc03 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/bcpg/test/SignaturePacketTest.java @@ -0,0 +1,139 @@ +package org.bouncycastle.bcpg.test; + +import org.bouncycastle.bcpg.*; +import org.bouncycastle.bcpg.sig.IssuerFingerprint; +import org.bouncycastle.bcpg.sig.IssuerKeyID; +import org.bouncycastle.bcpg.sig.SignatureCreationTime; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.test.SimpleTest; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Array; + +public class SignaturePacketTest extends SimpleTest +{ + @Override + public String getName() + { + return SignaturePacketTest.class.getSimpleName(); + } + + @Override + public void performTest() + throws Exception + { + testParseV6Signature(); + testParseV4Ed25519LegacySignature(); + } + + private void testParseV6Signature() + throws IOException + { + // Hex-encoded OpenPGP v6 signature packet + // Extracted from https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-inline-signed-messag + byte[] encSigPacket = Hex.decode("c29806011b0a0000002905826398a363222106cb186c4f0609a697e4d52dfa6c722b0c1f1e27c18a56708f6525ec27bad9acc90000000069362076495f50218890f7f5e2ee3c1822514f70500f551d86e5c921e404e34a53fbac27d06fb80aa8fc5bcb16e19631b280740f9ea6aed5e073ad00f9415a653c40e77a6ae77e692ba71d069a109fa24c58cfd8e316d0a06b34ad9acb8e5c5f521501"); + // Issuer of the message + byte[] issuerFP = Hex.decode("CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9"); + // Salt used to generate the signature + byte[] salt = Hex.decode("76495F50218890F7F5E2EE3C1822514F70500F551D86E5C921E404E34A53FBAC"); + + ByteArrayInputStream bIn = new ByteArrayInputStream(encSigPacket); + BCPGInputStream pIn = new BCPGInputStream(bIn); + SignaturePacket sig = (SignaturePacket) pIn.readPacket(); + + isEquals("SignaturePacket version mismatch", + SignaturePacket.VERSION_6, sig.getVersion()); + isEquals("SignaturePacket signature type mismatch", + PGPSignature.CANONICAL_TEXT_DOCUMENT, sig.getSignatureType()); + isEquals("SignaturePacket key algorithm mismatch", + PublicKeyAlgorithmTags.Ed25519, sig.getKeyAlgorithm()); + isEquals("SignaturePacket hash algorithm mismatch", + HashAlgorithmTags.SHA512, sig.getHashAlgorithm()); + isTrue("SignaturePacket salt mismatch", + Arrays.areEqual(salt, sig.getSalt())); + // hashed subpackets + isEquals("SignaturePacket number of hashed packets mismatch", + 2, sig.getHashedSubPackets().length); + SignatureCreationTime creationTimeSubpacket = (SignatureCreationTime) sig.getHashedSubPackets()[0]; + isEquals("SignaturePacket signature creation time mismatch", + 1670947683000L, creationTimeSubpacket.getTime().getTime()); + IssuerFingerprint issuerSubpacket = (IssuerFingerprint) sig.getHashedSubPackets()[1]; + isTrue("SignaturePacket issuer fingerprint mismatch", + Arrays.areEqual(issuerFP, issuerSubpacket.getFingerprint())); + // unhashed subpackets + isEquals("SignaturePacket number of unhashed packets mismatch", + 0, sig.getUnhashedSubPackets().length); + + // v6 Ed25519 signatures (not LEGACY) do not use MPI encoding for the raw signature + // but rather encode into octet strings + isTrue("MPI encoding MUST be null", + sig.getSignature() == null); + isTrue("Octet string encoding mismatch", Arrays.areEqual( + Hex.decode("27d06fb80aa8fc5bcb16e19631b280740f9ea6aed5e073ad00f9415a653c40e77a6ae77e692ba71d069a109fa24c58cfd8e316d0a06b34ad9acb8e5c5f521501"), + sig.getSignatureBytes())); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pOut = new BCPGOutputStream(bOut, true); + sig.encode(pOut); + pOut.close(); + + isTrue("SignaturePacket encoding mismatch", + Arrays.areEqual(encSigPacket, bOut.toByteArray())); + } + + private void testParseV4Ed25519LegacySignature() throws IOException { + // Hex-encoded v4 test signature + // see https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-v4-ed25519legacy-sig + byte[] encSigPacket = Hex.decode("885e040016080006050255f95f95000a09108cfde12197965a9af62200ff56f90cca98e2102637bd983fdb16c131dfd27ed82bf4dde5606e0d756aed33660100d09c4fa11527f038e0f57f2201d82f2ea2c9033265fa6ceb489e854bae61b404"); + ByteArrayInputStream bIn = new ByteArrayInputStream(encSigPacket); + BCPGInputStream pIn = new BCPGInputStream(bIn); + SignaturePacket sig = (SignaturePacket) pIn.readPacket(); + + isEquals("SignaturePacket version mismatch", + SignaturePacket.VERSION_4, sig.getVersion()); + isEquals("SignaturePacket signature type mismatch", + PGPSignature.BINARY_DOCUMENT, sig.getSignatureType()); + isEquals("SignaturePacket public key algorithm mismatch", + PublicKeyAlgorithmTags.EDDSA_LEGACY, sig.getKeyAlgorithm()); + isEquals("SignaturePacket hash algorithm mismatch", + HashAlgorithmTags.SHA256, sig.getHashAlgorithm()); + isEquals("SignaturePacket number of hashed subpackets mismatch", + 1, sig.getHashedSubPackets().length); + SignatureCreationTime creationTimeSubpacket = (SignatureCreationTime) sig.getHashedSubPackets()[0]; + isEquals("SignaturePacket creationTime mismatch", + 1442406293000L, creationTimeSubpacket.getTime().getTime()); + isEquals("SignaturePacket number of unhashed subpackets mismatch", + 1, sig.getUnhashedSubPackets().length); + IssuerKeyID issuerKeyID = (IssuerKeyID) sig.getUnhashedSubPackets()[0]; + isEquals("SignaturePacket issuer key-id mismatch", + -8287220204898461030L, issuerKeyID.getKeyID()); + + // EDDSA_LEGACY uses MPI encoding for the raw signature value + MPInteger[] mpInts = sig.getSignature(); + isEquals("Signature MPI encoding mismatch", + 2, mpInts.length); + isTrue("MPI encoding in signatureBytes field mismatch", Arrays.areEqual( + Hex.decode("00ff56f90cca98e2102637bd983fdb16c131dfd27ed82bf4dde5606e0d756aed33660100d09c4fa11527f038e0f57f2201d82f2ea2c9033265fa6ceb489e854bae61b404"), + sig.getSignatureBytes())); + + // v4 signatures do not have salt + isTrue("Salt MUST be null", sig.getSalt() == null); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BCPGOutputStream pOut = new BCPGOutputStream(bOut, false); + sig.encode(pOut); + pOut.close(); + + isTrue("SignaturePacket encoding mismatch", + Arrays.areEqual(encSigPacket, bOut.toByteArray())); + } + + public static void main(String[] args) + { + runTest(new SignaturePacketTest()); + } +}