|
| 1 | +package org.bouncycastle.bcpg.test; |
| 2 | + |
| 3 | +import org.bouncycastle.bcpg.*; |
| 4 | +import org.bouncycastle.openpgp.PGPSignature; |
| 5 | +import org.bouncycastle.util.encoders.Hex; |
| 6 | + |
| 7 | +import java.io.ByteArrayInputStream; |
| 8 | +import java.io.ByteArrayOutputStream; |
| 9 | +import java.io.IOException; |
| 10 | +import java.security.SecureRandom; |
| 11 | + |
| 12 | +public class OnePassSignaturePacketTest |
| 13 | + extends AbstractPacketTest |
| 14 | +{ |
| 15 | + |
| 16 | + // Parse v6 OPS packet and compare its values to a known-good test vector |
| 17 | + private void testParseV6OnePassSignaturePacket() throws IOException { |
| 18 | + // Version 6 OnePassSignature packet |
| 19 | + // extracted from https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-inline-signed-messag |
| 20 | + byte[] encOPS = Hex.decode("c44606010a1b2076495f50218890f7f5e2ee3c1822514f70500f551d86e5c921e404e34a53fbaccb186c4f0609a697e4d52dfa6c722b0c1f1e27c18a56708f6525ec27bad9acc901"); |
| 21 | + // Issuer of the message |
| 22 | + byte[] issuerFp = Hex.decode("CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9"); |
| 23 | + // Salt used to generate the signature |
| 24 | + byte[] salt = Hex.decode("76495F50218890F7F5E2EE3C1822514F70500F551D86E5C921E404E34A53FBAC"); |
| 25 | + |
| 26 | + ByteArrayInputStream bIn = new ByteArrayInputStream(encOPS); |
| 27 | + BCPGInputStream pIn = new BCPGInputStream(bIn); |
| 28 | + |
| 29 | + // Parse and compare the OnePassSignature packet |
| 30 | + OnePassSignaturePacket ops = (OnePassSignaturePacket) pIn.readPacket(); |
| 31 | + isEquals("OPS packet MUST be of version 6", |
| 32 | + OnePassSignaturePacket.VERSION_6, ops.getVersion()); |
| 33 | + isEncodingEqual("OPS packet issuer fingerprint mismatch", |
| 34 | + issuerFp, ops.getFingerprint()); |
| 35 | + isTrue("OPS packet key-ID mismatch", |
| 36 | + // key-ID are the first 8 octets of the fingerprint |
| 37 | + Hex.toHexString(issuerFp).startsWith(Long.toHexString(ops.getKeyID()))); |
| 38 | + isEncodingEqual("OPS packet salt mismatch", |
| 39 | + salt, ops.getSalt()); |
| 40 | + isTrue("OPS packet isContaining mismatch", |
| 41 | + ops.isContaining()); |
| 42 | + |
| 43 | + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); |
| 44 | + BCPGOutputStream pOut = new BCPGOutputStream(bOut, true); |
| 45 | + ops.encode(pOut); |
| 46 | + pOut.close(); |
| 47 | + |
| 48 | + isEncodingEqual("OPS Packet encoding mismatch", encOPS, bOut.toByteArray()); |
| 49 | + } |
| 50 | + |
| 51 | + private void roundtripV3Packet() throws IOException { |
| 52 | + OnePassSignaturePacket before = new OnePassSignaturePacket( |
| 53 | + PGPSignature.BINARY_DOCUMENT, |
| 54 | + HashAlgorithmTags.SHA256, |
| 55 | + PublicKeyAlgorithmTags.RSA_GENERAL, |
| 56 | + 123L, |
| 57 | + true); |
| 58 | + |
| 59 | + isEquals("Expected OPS version 3", |
| 60 | + OnePassSignaturePacket.VERSION_3, before.getVersion()); |
| 61 | + isEquals("Signature type mismatch", |
| 62 | + PGPSignature.BINARY_DOCUMENT, before.getSignatureType()); |
| 63 | + isEquals("Hash Algorithm mismatch", |
| 64 | + HashAlgorithmTags.SHA256, before.getHashAlgorithm()); |
| 65 | + isEquals("Pulic Key Algorithm mismatch", |
| 66 | + PublicKeyAlgorithmTags.RSA_GENERAL, before.getKeyAlgorithm()); |
| 67 | + isEquals("Key-ID mismatch", |
| 68 | + 123L, before.getKeyID()); |
| 69 | + isFalse("OPS is expected to be non-containing", |
| 70 | + before.isContaining()); |
| 71 | + isNull("OPS v3 MUST NOT have a fingerprint", |
| 72 | + before.getFingerprint()); |
| 73 | + isNull("OPS v3 MUST NOT have salt", |
| 74 | + before.getSalt()); |
| 75 | + |
| 76 | + for (boolean newTypeIdFormat : new boolean[] {true, false}) { |
| 77 | + // round-trip the packet by encoding and decoding it |
| 78 | + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); |
| 79 | + BCPGOutputStream pOut = new BCPGOutputStream(bOut, newTypeIdFormat); |
| 80 | + before.encode(pOut); |
| 81 | + pOut.close(); |
| 82 | + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); |
| 83 | + BCPGInputStream pIn = new BCPGInputStream(bIn); |
| 84 | + OnePassSignaturePacket after = (OnePassSignaturePacket) pIn.readPacket(); |
| 85 | + |
| 86 | + isEquals("round-tripped OPS version mismatch", |
| 87 | + before.getVersion(), after.getVersion()); |
| 88 | + isEquals("round-tripped OPS signature type mismatch", |
| 89 | + before.getSignatureType(), after.getSignatureType()); |
| 90 | + isEquals("round-tripped OPS hash algorithm mismatch", |
| 91 | + before.getHashAlgorithm(), after.getHashAlgorithm()); |
| 92 | + isEquals("round-tripped OPS public key algorithm mismatch", |
| 93 | + before.getKeyAlgorithm(), after.getKeyAlgorithm()); |
| 94 | + isEquals("round-tripped OPS key-id mismatch", |
| 95 | + before.getKeyID(), after.getKeyID()); |
| 96 | + isEquals("round-tripped OPS nested flag mismatch", |
| 97 | + before.isContaining(), after.isContaining()); |
| 98 | + isNull("round-tripped OPS v3 MUST NOT have fingerprint", |
| 99 | + after.getFingerprint()); |
| 100 | + isNull("round-tripped OPS v3 MUST NOT have salt", |
| 101 | + after.getSalt()); |
| 102 | + |
| 103 | + isEncodingEqual("Packet encoding mismatch", |
| 104 | + before, after); |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + private void roundtripV6Packet() throws IOException { |
| 109 | + byte[] salt = new byte[32]; |
| 110 | + byte[] fingerprint = Hex.decode("CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9"); |
| 111 | + long keyID = ((fingerprint[0] & 0xffL) << 56) | |
| 112 | + ((fingerprint[1] & 0xffL) << 48) | |
| 113 | + ((fingerprint[2] & 0xffL) << 40) | |
| 114 | + ((fingerprint[3] & 0xffL) << 32) | |
| 115 | + ((fingerprint[4] & 0xffL) << 24) | |
| 116 | + ((fingerprint[5] & 0xffL) << 16) | |
| 117 | + ((fingerprint[6] & 0xffL) << 8) | |
| 118 | + ((fingerprint[7] & 0xffL)); |
| 119 | + |
| 120 | + new SecureRandom().nextBytes(salt); |
| 121 | + OnePassSignaturePacket before = new OnePassSignaturePacket( |
| 122 | + PGPSignature.CANONICAL_TEXT_DOCUMENT, |
| 123 | + HashAlgorithmTags.SHA512, |
| 124 | + PublicKeyAlgorithmTags.EDDSA_LEGACY, |
| 125 | + salt, |
| 126 | + fingerprint, |
| 127 | + false); |
| 128 | + |
| 129 | + isEquals("Expected OPS version 6", |
| 130 | + OnePassSignaturePacket.VERSION_6, before.getVersion()); |
| 131 | + isEquals("Signature type mismatch", |
| 132 | + PGPSignature.CANONICAL_TEXT_DOCUMENT, before.getSignatureType()); |
| 133 | + isEquals("Hash algorithm mismatch", |
| 134 | + HashAlgorithmTags.SHA512, before.getHashAlgorithm()); |
| 135 | + isEquals("Public key algorithm mismatch", |
| 136 | + PublicKeyAlgorithmTags.EDDSA_LEGACY, before.getKeyAlgorithm()); |
| 137 | + isEncodingEqual("Salt mismatch", |
| 138 | + salt, before.getSalt()); |
| 139 | + isEncodingEqual("Fingerprint mismatch", |
| 140 | + fingerprint, before.getFingerprint()); |
| 141 | + isEquals("Derived key-ID mismatch", |
| 142 | + keyID, before.getKeyID()); |
| 143 | + isTrue("non-nested OPS is expected to be containing", |
| 144 | + before.isContaining()); |
| 145 | + |
| 146 | + for (boolean newTypeIdFormat : new boolean[] {true, false}) { |
| 147 | + // round-trip the packet by encoding and decoding it |
| 148 | + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); |
| 149 | + BCPGOutputStream pOut = new BCPGOutputStream(bOut, newTypeIdFormat); |
| 150 | + before.encode(pOut); |
| 151 | + pOut.close(); |
| 152 | + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); |
| 153 | + BCPGInputStream pIn = new BCPGInputStream(bIn); |
| 154 | + OnePassSignaturePacket after = (OnePassSignaturePacket) pIn.readPacket(); |
| 155 | + |
| 156 | + isEquals("round-tripped OPS version mismatch", |
| 157 | + before.getVersion(), after.getVersion()); |
| 158 | + isEquals("round-tripped OPS signature type mismatch", |
| 159 | + before.getSignatureType(), after.getSignatureType()); |
| 160 | + isEquals("round-tripped OPS hash algorithm mismatch", |
| 161 | + before.getHashAlgorithm(), after.getHashAlgorithm()); |
| 162 | + isEquals("round-tripped OPS public key algorithm mismatch", |
| 163 | + before.getKeyAlgorithm(), after.getKeyAlgorithm()); |
| 164 | + isEquals("round-tripped OPS key-id mismatch", |
| 165 | + before.getKeyID(), after.getKeyID()); |
| 166 | + isEquals("round-tripped OPS nested flag mismatch", |
| 167 | + before.isContaining(), after.isContaining()); |
| 168 | + isEncodingEqual("round-tripped OPS fingerprint mismatch", |
| 169 | + before.getFingerprint(), after.getFingerprint()); |
| 170 | + isEncodingEqual("round-tripped OPS salt mismatch", |
| 171 | + before.getSalt(), after.getSalt()); |
| 172 | + |
| 173 | + isEncodingEqual(before, after); |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + private void roundtripV6PacketWithZeroLengthSalt() throws IOException { |
| 178 | + byte[] salt = new byte[0]; |
| 179 | + byte[] fingerprint = Hex.decode("CB186C4F0609A697E4D52DFA6C722B0C1F1E27C18A56708F6525EC27BAD9ACC9"); |
| 180 | + |
| 181 | + OnePassSignaturePacket before = new OnePassSignaturePacket( |
| 182 | + PGPSignature.CANONICAL_TEXT_DOCUMENT, |
| 183 | + HashAlgorithmTags.SHA512, |
| 184 | + PublicKeyAlgorithmTags.EDDSA_LEGACY, |
| 185 | + salt, |
| 186 | + fingerprint, |
| 187 | + false); |
| 188 | + |
| 189 | + isEncodingEqual("Salt mismatch", |
| 190 | + salt, before.getSalt()); |
| 191 | + |
| 192 | + for (boolean newTypeIdFormat : new boolean[] {true, false}) { |
| 193 | + // round-trip the packet by encoding and decoding it |
| 194 | + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); |
| 195 | + BCPGOutputStream pOut = new BCPGOutputStream(bOut, newTypeIdFormat); |
| 196 | + before.encode(pOut); |
| 197 | + pOut.close(); |
| 198 | + ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray()); |
| 199 | + BCPGInputStream pIn = new BCPGInputStream(bIn); |
| 200 | + OnePassSignaturePacket after = (OnePassSignaturePacket) pIn.readPacket(); |
| 201 | + |
| 202 | + isEquals("round-tripped OPS version mismatch", |
| 203 | + before.getVersion(), after.getVersion()); |
| 204 | + isEquals("round-tripped OPS signature type mismatch", |
| 205 | + before.getSignatureType(), after.getSignatureType()); |
| 206 | + isEquals("round-tripped OPS hash algorithm mismatch", |
| 207 | + before.getHashAlgorithm(), after.getHashAlgorithm()); |
| 208 | + isEquals("round-tripped OPS public key algorithm mismatch", |
| 209 | + before.getKeyAlgorithm(), after.getKeyAlgorithm()); |
| 210 | + isEquals("round-tripped OPS key-id mismatch", |
| 211 | + before.getKeyID(), after.getKeyID()); |
| 212 | + isEquals("round-tripped OPS nested flag mismatch", |
| 213 | + before.isContaining(), after.isContaining()); |
| 214 | + isEncodingEqual("round-tripped OPS fingerprint mismatch", |
| 215 | + before.getFingerprint(), after.getFingerprint()); |
| 216 | + isEncodingEqual("round-tripped OPS salt mismatch", |
| 217 | + before.getSalt(), after.getSalt()); |
| 218 | + } |
| 219 | + } |
| 220 | + |
| 221 | + private void parsingOfPacketWithUnknownVersionFails() { |
| 222 | + // Version 0x99 OnePassSignature packet |
| 223 | + byte[] encOPS = Hex.decode("c44699010a1b2076495f50218890f7f5e2ee3c1822514f70500f551d86e5c921e404e34a53fbaccb186c4f0609a697e4d52dfa6c722b0c1f1e27c18a56708f6525ec27bad9acc901"); |
| 224 | + |
| 225 | + ByteArrayInputStream bIn = new ByteArrayInputStream(encOPS); |
| 226 | + BCPGInputStream pIn = new BCPGInputStream(bIn); |
| 227 | + |
| 228 | + try { |
| 229 | + pIn.readPacket(); |
| 230 | + fail("Expected UnsupportedPacketVersionException"); |
| 231 | + } catch (IOException e) { |
| 232 | + fail("Expected UnsupportedPacketVersionException", e); |
| 233 | + } catch (UnsupportedPacketVersionException e) { |
| 234 | + // expected |
| 235 | + } |
| 236 | + } |
| 237 | + |
| 238 | + private void parsingOfPacketWithTruncatedFingerprintFails() { |
| 239 | + // Version 6 OnePassSignature packet with truncated fingerprint field (20 bytes instead of 32) |
| 240 | + // This error would happen, if a v6 OPS packet was generated with a v4 fingerprint. |
| 241 | + // extracted from https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-sample-inline-signed-messag |
| 242 | + byte[] encOPS = Hex.decode("c44606010a1b2076495f50218890f7f5e2ee3c1822514f70500f551d86e5c921e404e34a53fbaccb186c4f0609a697e4d52dfa6c722b0c1f1e27c101"); |
| 243 | + |
| 244 | + ByteArrayInputStream bIn = new ByteArrayInputStream(encOPS); |
| 245 | + BCPGInputStream pIn = new BCPGInputStream(bIn); |
| 246 | + |
| 247 | + try { |
| 248 | + pIn.readPacket(); |
| 249 | + fail("Expected IOException"); |
| 250 | + } catch (IOException e) { |
| 251 | + // expected |
| 252 | + } |
| 253 | + } |
| 254 | + |
| 255 | + @Override |
| 256 | + public String getName() { |
| 257 | + return "OnePassSignaturePacketTest"; |
| 258 | + } |
| 259 | + |
| 260 | + @Override |
| 261 | + public void performTest() throws Exception { |
| 262 | + testParseV6OnePassSignaturePacket(); |
| 263 | + roundtripV3Packet(); |
| 264 | + roundtripV6Packet(); |
| 265 | + parsingOfPacketWithUnknownVersionFails(); |
| 266 | + parsingOfPacketWithTruncatedFingerprintFails(); |
| 267 | + roundtripV6PacketWithZeroLengthSalt(); |
| 268 | + } |
| 269 | + |
| 270 | + public static void main(String[] args) { |
| 271 | + runTest(new OnePassSignaturePacketTest()); |
| 272 | + } |
| 273 | +} |
0 commit comments