// SPDX-FileCopyrightText: 2021 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 package org.pgpainless.signature; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import org.bouncycastle.openpgp.PGPCompressedData; import org.bouncycastle.openpgp.PGPEncryptedData; import org.bouncycastle.openpgp.PGPEncryptedDataList; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPLiteralData; import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPOnePassSignature; import org.bouncycastle.openpgp.PGPOnePassSignatureList; import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureList; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; import org.bouncycastle.util.io.Streams; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.pgpainless.PGPainless; import org.pgpainless.algorithm.DocumentSignatureType; import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.PublicKeyAlgorithm; import org.pgpainless.algorithm.SignatureType; import org.pgpainless.encryption_signing.EncryptionOptions; import org.pgpainless.encryption_signing.EncryptionStream; import org.pgpainless.encryption_signing.ProducerOptions; import org.pgpainless.encryption_signing.SigningOptions; import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.key.protection.UnlockSecretKey; import org.pgpainless.util.TestAllImplementations; public class OnePassSignatureBracketingTest { @TestTemplate @ExtendWith(TestAllImplementations.class) public void onePassSignaturePacketsAndSignaturesAreBracketedTest() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { PGPSecretKeyRing key1 = PGPainless.generateKeyRing().modernKeyRing("Alice"); PGPSecretKeyRing key2 = PGPainless.generateKeyRing().modernKeyRing("Bob"); PGPPublicKeyRing cert1 = PGPainless.extractCertificate(key1); ByteArrayOutputStream out = new ByteArrayOutputStream(); EncryptionStream encryptionStream = PGPainless.encryptAndOrSign() .onOutputStream(out) .withOptions(ProducerOptions.signAndEncrypt( EncryptionOptions.encryptCommunications() .addRecipient(cert1), SigningOptions.get() .addInlineSignature(SecretKeyRingProtector.unprotectedKeys(), key1, DocumentSignatureType.BINARY_DOCUMENT) .addInlineSignature(SecretKeyRingProtector.unprotectedKeys(), key2, DocumentSignatureType.BINARY_DOCUMENT) ).setAsciiArmor(true)); ByteArrayInputStream data = new ByteArrayInputStream("Hello, World!".getBytes(StandardCharsets.UTF_8)); Streams.pipeAll(data, encryptionStream); encryptionStream.close(); ByteArrayInputStream ciphertextIn = new ByteArrayInputStream(out.toByteArray()); InputStream inputStream = PGPUtil.getDecoderStream(ciphertextIn); PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(inputStream); PGPOnePassSignatureList onePassSignatures = null; PGPSignatureList signatures = null; outerloop: while (true) { Object next = objectFactory.nextObject(); if (next == null) { break; } if (next instanceof PGPEncryptedDataList) { PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList) next; for (PGPEncryptedData encryptedData : encryptedDataList) { if (encryptedData instanceof PGPPublicKeyEncryptedData) { PGPPublicKeyEncryptedData publicKeyEncryptedData = (PGPPublicKeyEncryptedData) encryptedData; PGPSecretKey secretKey = key1.getSecretKey(publicKeyEncryptedData.getKeyID()); PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKey, SecretKeyRingProtector.unprotectedKeys()); PublicKeyDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance().getPublicKeyDataDecryptorFactory(privateKey); InputStream decryptionStream = publicKeyEncryptedData.getDataStream(decryptorFactory); objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(decryptionStream); continue outerloop; } } } else if (next instanceof PGPOnePassSignatureList) { onePassSignatures = (PGPOnePassSignatureList) next; continue outerloop; } else if (next instanceof PGPCompressedData) { PGPCompressedData compressed = (PGPCompressedData) next; InputStream decompressor = compressed.getDataStream(); objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(decompressor); continue outerloop; } else if (next instanceof PGPLiteralData) { continue outerloop; } else if (next instanceof PGPSignatureList) { signatures = (PGPSignatureList) next; continue outerloop; } } assertNotNull(onePassSignatures); assertNotNull(signatures); assertEquals(signatures.size(), onePassSignatures.size()); assertEquals(2, signatures.size()); for (int i = 0; i < signatures.size(); i++) { // CHECK BRACKETING // OnePassSignatures and Signatures are bracketed // eg. (OPS1, OPS2, LiteralData, Sig2, Sig1) PGPOnePassSignature onePassSignature = onePassSignatures.get(i); PGPSignature signature = signatures.get(signatures.size() - 1 - i); assertEquals(onePassSignature.getKeyID(), signature.getKeyID()); byte[] encoded = onePassSignature.getEncoded(); // CHECK NESTED-NESS // 0,1 are header // 2 is version number assertEquals(3, encoded[2]); // 3 is sig type assertEquals(SignatureType.BINARY_DOCUMENT.getCode(), encoded[3]); // 4 is hash algo assertEquals(HashAlgorithm.SHA512.getAlgorithmId(), encoded[4]); // 5 is public key algo assertEquals(PublicKeyAlgorithm.EDDSA.getAlgorithmId(), encoded[5]); // [6,7,8,9,10,11,12,13] are key-id boolean last = i == signatures.size() - 1; // 14 is nested assertEquals(last ? 1 : 0, encoded[14]); } } }