From fffadcf48d839c3c3c6d3067007a91680300628c Mon Sep 17 00:00:00 2001 From: pkgagent Date: Wed, 5 Aug 2026 14:40:22 +0800 Subject: [PATCH] Fix CVE-2026-58061, CVE-2026-59640 --- bouncycastle-1.80-CVE-2026-58061-1.patch | 104 +++++++++ bouncycastle-1.80-CVE-2026-58061-2.patch | 271 +++++++++++++++++++++++ bouncycastle-1.80-CVE-2026-59640.patch | 180 +++++++++++++++ bouncycastle.spec | 9 +- 4 files changed, 563 insertions(+), 1 deletion(-) create mode 100644 bouncycastle-1.80-CVE-2026-58061-1.patch create mode 100644 bouncycastle-1.80-CVE-2026-58061-2.patch create mode 100644 bouncycastle-1.80-CVE-2026-59640.patch diff --git a/bouncycastle-1.80-CVE-2026-58061-1.patch b/bouncycastle-1.80-CVE-2026-58061-1.patch new file mode 100644 index 0000000..bd10077 --- /dev/null +++ b/bouncycastle-1.80-CVE-2026-58061-1.patch @@ -0,0 +1,104 @@ +From cd4a5ab3ad619ff03c7767c1b8b19d5dea2970af Mon Sep 17 00:00:00 2001 +From: David Hook +Date: Mon, 22 Jun 2026 19:10:00 +1000 +Subject: [PATCH] Verify the CCM tag before exposing decrypted plaintext + +Adapted-by: PkgAgent/deepseek-v4 (modified to adapt to opencloudos-stream) +--- + .../crypto/modes/CCMBlockCipher.java | 19 ++++++++--- + .../org/bouncycastle/crypto/test/CCMTest.java | 34 +++++++++++++++++++ + docs/releasenotes.html | 1 + + 3 files changed, 50 insertions(+), 4 deletions(-) + +diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/CCMBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/CCMBlockCipher.java +index 54092e1..b3cbe09 100644 +--- a/core/src/main/java/org/bouncycastle/crypto/modes/CCMBlockCipher.java ++++ b/core/src/main/java/org/bouncycastle/crypto/modes/CCMBlockCipher.java +@@ -340,10 +340,18 @@ public int processPacket(byte[] in, int inOff, int inLen, byte[] output, int out + macBlock[i] = 0; + } + ++ // Decrypt into a private buffer and verify the MAC before writing any plaintext to the ++ // caller's output: on a tag-check failure the caller's buffer must not be left holding ++ // unverified CTR plaintext (NIST SP 800-38C 6.2 returns FAIL without revealing P; this ++ // matches GCMSIVBlockCipher). CCM is non-streaming, so the whole payload is buffered here ++ // regardless. ++ byte[] plain = new byte[outputLen]; ++ int plainIndex = 0; ++ + while (inIndex < (inOff + outputLen - blockSize)) + { +- ctrCipher.processBlock(in, inIndex, output, outIndex); +- outIndex += blockSize; ++ ctrCipher.processBlock(in, inIndex, plain, plainIndex); ++ plainIndex += blockSize; + inIndex += blockSize; + } + +@@ -353,16 +361,19 @@ public int processPacket(byte[] in, int inOff, int inLen, byte[] output, int out + + ctrCipher.processBlock(block, 0, block, 0); + +- System.arraycopy(block, 0, output, outIndex, outputLen - (inIndex - inOff)); ++ System.arraycopy(block, 0, plain, plainIndex, outputLen - (inIndex - inOff)); + + byte[] calculatedMacBlock = new byte[blockSize]; + +- calculateMac(output, outOff, outputLen, calculatedMacBlock); ++ calculateMac(plain, 0, outputLen, calculatedMacBlock); + + if (!Arrays.constantTimeAreEqual(macBlock, calculatedMacBlock)) + { ++ Arrays.clear(plain); + throw new InvalidCipherTextException("mac check in CCM failed"); + } ++ ++ System.arraycopy(plain, 0, output, outOff, outputLen); + } + + return outputLen; +diff --git a/core/src/test/java/org/bouncycastle/crypto/test/CCMTest.java b/core/src/test/java/org/bouncycastle/crypto/test/CCMTest.java +index b6511e2..16179fd 100644 +--- a/core/src/test/java/org/bouncycastle/crypto/test/CCMTest.java ++++ b/core/src/test/java/org/bouncycastle/crypto/test/CCMTest.java +@@ -204,6 +204,40 @@ public void performTest() + new KeyParameter(K1), 32, N2)); + AEADTestUtil.testBufferSizeChecks(this, new CCMBlockCipher(AESEngine.newInstance()), new AEADParameters( + new KeyParameter(K1), 32, N2)); ++ ++ noUnverifiedPlaintextOnFailure(); ++ } ++ ++ private void noUnverifiedPlaintextOnFailure() ++ throws Exception ++ { ++ CCMBlockCipher ccm = new CCMBlockCipher(AESEngine.newInstance()); ++ ccm.init(false, new AEADParameters(new KeyParameter(K2), 48, N2, A2)); ++ ++ // Corrupt the authentication tag so verification fails; the ciphertext body is unchanged. ++ byte[] tampered = Arrays.clone(C2); ++ tampered[tampered.length - 1] ^= 0x01; ++ ++ byte[] output = new byte[ccm.getOutputSize(tampered.length)]; ++ Arrays.fill(output, (byte)0x55); ++ ++ try ++ { ++ ccm.processPacket(tampered, 0, tampered.length, output, 0); ++ fail("tampered CCM ciphertext must not verify"); ++ } ++ catch (InvalidCipherTextException e) ++ { ++ // On a tag-check failure the caller's output buffer must not be left holding the ++ // unverified CTR plaintext. ++ for (int i = 0; i != output.length; i++) ++ { ++ if (output[i] != (byte)0x55) ++ { ++ fail("CCM left unverified plaintext in the output buffer on tag failure"); ++ } ++ } ++ } + } + + private boolean isEqual(byte[] exp, byte[] other, int off) diff --git a/bouncycastle-1.80-CVE-2026-58061-2.patch b/bouncycastle-1.80-CVE-2026-58061-2.patch new file mode 100644 index 0000000..2fffec3 --- /dev/null +++ b/bouncycastle-1.80-CVE-2026-58061-2.patch @@ -0,0 +1,271 @@ +From 08d675106bb663ddcc6ec0a4af6f6f62f512697b Mon Sep 17 00:00:00 2001 +From: David Hook +Date: Mon, 22 Jun 2026 19:27:08 +1000 +Subject: [PATCH] Verify the tag before exposing plaintext in KCCM/KGCM + decryption + +Adapted-by: PkgAgent/deepseek-v4 (modified to adapt to opencloudos-stream) +--- + .../crypto/modes/KCCMBlockCipher.java | 39 ++++++----- + .../crypto/modes/KGCMBlockCipher.java | 41 ++++++----- + .../crypto/test/DSTU7624Test.java | 69 +++++++++++++++++++ + docs/releasenotes.html | 1 + + 4 files changed, 113 insertions(+), 37 deletions(-) + +diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/KCCMBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/KCCMBlockCipher.java +index 18238ca..9667753 100755 +--- a/core/src/main/java/org/bouncycastle/crypto/modes/KCCMBlockCipher.java ++++ b/core/src/main/java/org/bouncycastle/crypto/modes/KCCMBlockCipher.java +@@ -311,20 +311,33 @@ public int processPacket(byte[] in, int inOff, int len, byte[] out, int outOff) + throw new DataLengthException("partial blocks not supported"); + } + +- engine.processBlock(nonce, 0, s, 0); ++ int dataLen = len - macSize; + +- int blocks = len / engine.getBlockSize(); ++ engine.processBlock(nonce, 0, s, 0); + +- for (int blockNum = 0; blockNum < blocks; blockNum++) ++ // Recover the plaintext into a private buffer and verify the MAC before writing any of it ++ // to the caller's output: on a tag failure the caller's buffer must not be left holding ++ // unverified gamma plaintext (matches CCMBlockCipher / GCMSIVBlockCipher). ++ byte[] plain = new byte[dataLen]; ++ byte[] recoveredMac = new byte[macSize]; ++ int inPos = inOff; ++ int plainPos = 0; ++ int dataLeft = dataLen; ++ while (dataLeft > 0) + { +- ProcessBlock(in, inOff, len, out, outOff); +- +- inOff += engine.getBlockSize(); +- outOff += engine.getBlockSize(); ++ int blockLen = Math.min(dataLeft, engine.getBlockSize()); ++ ProcessBlock(in, inPos, blockLen, plain, plainPos); ++ dataLeft -= blockLen; ++ inPos += blockLen; ++ plainPos += blockLen; + } + +- if (len > inOff) ++ // recover the appended (masked) MAC using the next keystream blocks ++ int macLeft = macSize; ++ int macPos = 0; ++ while (macLeft > 0) + { ++ int blockLen = Math.min(macLeft, engine.getBlockSize()); + for (int byteIndex = 0; byteIndex < counter.length; byteIndex++) + { + s[byteIndex] += counter[byteIndex]; +@@ -332,38 +345,34 @@ public int processPacket(byte[] in, int inOff, int len, byte[] out, int outOff) + + engine.processBlock(s, 0, buffer, 0); + +- for (int byteIndex = 0; byteIndex < macSize; byteIndex++) ++ for (int byteIndex = 0; byteIndex < blockLen; byteIndex++) + { +- out[outOff + byteIndex] = (byte)(buffer[byteIndex] ^ in[inOff + byteIndex]); ++ recoveredMac[macPos + byteIndex] = (byte)(buffer[byteIndex] ^ in[inPos + byteIndex]); + } +- outOff += macSize; ++ macLeft -= blockLen; ++ inPos += blockLen; ++ macPos += blockLen; + } + +- for (int byteIndex = 0; byteIndex < counter.length; byteIndex++) +- { +- s[byteIndex] += counter[byteIndex]; +- } +- +- engine.processBlock(s, 0, buffer, 0); +- +- System.arraycopy(out, outOff - macSize, buffer, 0, macSize); +- +- CalculateMac(out, 0, outOff - macSize); ++ // recompute the MAC over the recovered plaintext and compare ++ CalculateMac(plain, 0, dataLen); + + System.arraycopy(macBlock, 0, mac, 0, macSize); + +- byte[] calculatedMac = new byte[macSize]; +- +- System.arraycopy(buffer, 0, calculatedMac, 0, macSize); +- +- if (!Arrays.constantTimeAreEqual(mac, calculatedMac)) ++ if (!Arrays.constantTimeAreEqual(mac, recoveredMac)) + { ++ Arrays.clear(plain); + throw new InvalidCipherTextException("mac check failed"); + } + ++ // Only now (MAC verified) expose the recovered plaintext in the caller's output - the ++ // same buffer layout the encrypt side and callers expect. ++ System.arraycopy(plain, 0, out, outOff, dataLen); ++ System.arraycopy(recoveredMac, 0, out, outOff + dataLen, macSize); ++ + reset(); + +- return len - macSize; ++ return dataLen; + } + } + +diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/KGCMBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/KGCMBlockCipher.java +index 2c8f4b9..09898d4 100755 +--- a/core/src/main/java/org/bouncycastle/crypto/modes/KGCMBlockCipher.java ++++ b/core/src/main/java/org/bouncycastle/crypto/modes/KGCMBlockCipher.java +@@ -235,28 +235,12 @@ public int doFinal(byte[] out, int outOff) + resultLen += ctrEngine.doFinal(out, outOff + resultLen); + + calculateMac(out, outOff, len, lenAAD); +- } +- else +- { +- int ctLen = len - macSize; +- if (out.length - outOff < ctLen) ++ ++ if (macBlock == null) + { +- throw new OutputLengthException("Output buffer too short"); ++ throw new IllegalStateException("mac is not calculated"); + } + +- calculateMac(data.getBuffer(), 0, ctLen, lenAAD); +- +- resultLen = ctrEngine.processBytes(data.getBuffer(), 0, ctLen, out, outOff); +- resultLen += ctrEngine.doFinal(out, outOff + resultLen); +- } +- +- if (macBlock == null) +- { +- throw new IllegalStateException("mac is not calculated"); +- } +- +- if (forEncryption) +- { + System.arraycopy(macBlock, 0, out, outOff + resultLen, macSize); + + reset(); +@@ -265,6 +249,22 @@ public int doFinal(byte[] out, int outOff) + } + else + { ++ int ctLen = len - macSize; ++ if (out.length - outOff < ctLen) ++ { ++ throw new OutputLengthException("Output buffer too short"); ++ } ++ ++ // KGCM authenticates the ciphertext, so verify the tag BEFORE decrypting: a forged ++ // ciphertext is rejected without ever writing unverified CTR plaintext to the caller's ++ // output buffer (matches CCMBlockCipher / GCMSIVBlockCipher). ++ calculateMac(data.getBuffer(), 0, ctLen, lenAAD); ++ ++ if (macBlock == null) ++ { ++ throw new IllegalStateException("mac is not calculated"); ++ } ++ + byte[] mac = new byte[macSize]; + System.arraycopy(data.getBuffer(), len - macSize, mac, 0, macSize); + +@@ -276,6 +276,9 @@ public int doFinal(byte[] out, int outOff) + throw new InvalidCipherTextException("mac verification failed"); + } + ++ resultLen = ctrEngine.processBytes(data.getBuffer(), 0, ctLen, out, outOff); ++ resultLen += ctrEngine.doFinal(out, outOff + resultLen); ++ + reset(); + + return resultLen; +diff --git a/core/src/test/java/org/bouncycastle/crypto/test/DSTU7624Test.java b/core/src/test/java/org/bouncycastle/crypto/test/DSTU7624Test.java +index 2af7f79..560e825 100755 +--- a/core/src/test/java/org/bouncycastle/crypto/test/DSTU7624Test.java ++++ b/core/src/test/java/org/bouncycastle/crypto/test/DSTU7624Test.java +@@ -2,6 +2,7 @@ + + import java.security.SecureRandom; + ++import org.bouncycastle.crypto.InvalidCipherTextException; + import org.bouncycastle.crypto.engines.DSTU7624Engine; + import org.bouncycastle.crypto.engines.DSTU7624WrapEngine; + import org.bouncycastle.crypto.macs.DSTU7624Mac; +@@ -96,6 +97,75 @@ public void performTest() + CCMModeTests(); + XTSModeTests(); + GCMModeTests(); ++ kccmKgcmNoUnverifiedPlaintextOnFailure(); ++ } ++ ++ private void kccmKgcmNoUnverifiedPlaintextOnFailure() ++ throws Exception ++ { ++ byte[] key = Hex.decode("000102030405060708090A0B0C0D0E0F"); ++ byte[] iv = Hex.decode("101112131415161718191A1B1C1D1E1F"); ++ byte[] plaintext = Hex.decode("303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F"); ++ ++ // KCCM: plaintext is recovered into the output before the MAC is checked, so a tag failure ++ // must not leave it there. ++ { ++ AEADParameters params = new AEADParameters(new KeyParameter(key), 128, iv); ++ KCCMBlockCipher ccm = new KCCMBlockCipher(new DSTU7624Engine(128)); ++ ccm.init(true, params); ++ byte[] ct = new byte[ccm.getOutputSize(plaintext.length)]; ++ ccm.doFinal(ct, ccm.processBytes(plaintext, 0, plaintext.length, ct, 0)); ++ ++ ct[ct.length - 1] ^= 0x01; // corrupt the (masked) MAC ++ ++ ccm.init(false, params); ++ byte[] out = new byte[ct.length]; ++ Arrays.fill(out, (byte)0x55); ++ try ++ { ++ ccm.doFinal(out, ccm.processBytes(ct, 0, ct.length, out, 0)); ++ fail("tampered KCCM ciphertext must not verify"); ++ } ++ catch (InvalidCipherTextException e) ++ { ++ checkNoUnverifiedPlaintext("KCCM", out, plaintext.length); ++ } ++ } ++ ++ // KGCM ++ { ++ AEADParameters params = new AEADParameters(new KeyParameter(key), 128, iv); ++ KGCMBlockCipher gcm = new KGCMBlockCipher(new DSTU7624Engine(128)); ++ gcm.init(true, params); ++ byte[] ct = new byte[gcm.getOutputSize(plaintext.length)]; ++ gcm.doFinal(ct, gcm.processBytes(plaintext, 0, plaintext.length, ct, 0)); ++ ++ ct[ct.length - 1] ^= 0x01; // corrupt the tag ++ ++ gcm.init(false, params); ++ byte[] out = new byte[ct.length]; ++ Arrays.fill(out, (byte)0x55); ++ try ++ { ++ gcm.doFinal(out, gcm.processBytes(ct, 0, ct.length, out, 0)); ++ fail("tampered KGCM ciphertext must not verify"); ++ } ++ catch (InvalidCipherTextException e) ++ { ++ checkNoUnverifiedPlaintext("KGCM", out, plaintext.length); ++ } ++ } ++ } ++ ++ private void checkNoUnverifiedPlaintext(String name, byte[] out, int plaintextLen) ++ { ++ for (int i = 0; i != plaintextLen; i++) ++ { ++ if (out[i] != (byte)0x55) ++ { ++ fail(name + " left unverified plaintext in the output buffer on tag failure"); ++ } ++ } + } + + public static void main( diff --git a/bouncycastle-1.80-CVE-2026-59640.patch b/bouncycastle-1.80-CVE-2026-59640.patch new file mode 100644 index 0000000..61bd44b --- /dev/null +++ b/bouncycastle-1.80-CVE-2026-59640.patch @@ -0,0 +1,180 @@ +From 6b94b1c146cec1f565d9a85847fae511af77503e Mon Sep 17 00:00:00 2001 +From: David Hook +Date: Mon, 22 Jun 2026 20:08:46 +1000 +Subject: [PATCH] Suppress the CFB quick-check oracle on the OpenPGP + session-key/public-key path + +Adapted-by: PkgAgent/deepseek-v4 (modified to adapt to opencloudos-stream) +--- + docs/releasenotes.html | 1 + + .../openpgp/PGPEncryptedData.java | 17 ++++++-- + .../openpgp/PGPPublicKeyEncryptedData.java | 4 +- + .../openpgp/PGPSessionKeyEncryptedData.java | 9 ++++ + .../openpgp/PGPSymmetricKeyEncryptedData.java | 13 +++++- + .../openpgp/test/PGPSessionKeyTest.java | 42 +++++++++++++++++++ + 6 files changed, 80 insertions(+), 6 deletions(-) + +diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedData.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedData.java +index fba7395..bdf5858 100644 +--- a/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedData.java ++++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPEncryptedData.java +@@ -271,7 +271,7 @@ public int getAlgorithm() + throw new UnsupportedOperationException("not supported - override required"); + } + +- boolean processSymmetricEncIntegrityPacketDataStream(boolean withIntegrityPacket, PGPDataDecryptor dataDecryptor, BCPGInputStream encIn) ++ boolean processSymmetricEncIntegrityPacketDataStream(boolean withIntegrityPacket, PGPDataDecryptor dataDecryptor, BCPGInputStream encIn, boolean publicKeyEncrypted) + throws IOException + { + encStream = new BCPGInputStream(dataDecryptor.getInputStream(encIn)); +@@ -306,8 +306,19 @@ boolean processSymmetricEncIntegrityPacketDataStream(boolean withIntegrityPacket + throw new EOFException("unexpected end of stream."); + } + +- // Note: the oracle attack on "quick check" bytes is not deemed +- // a security risk for PBE (see PGPPublicKeyEncryptedData) ++ // For a public-key / session-key decryption there is no multi-SKESK passphrase retry to ++ // drive, so the CFB "quick check" on the two repeated prefix bytes serves no purpose here and ++ // only re-creates the Mister-Zuccherato oracle (the reason PGPPublicKeyEncryptedData suppresses ++ // it). Consume the check bytes (done above) but do not signal a mismatch; for SEIPD v1 the MDC ++ // is the integrity check. This path is reached for PKESK session keys via the high-level API. ++ if (publicKeyEncrypted) ++ { ++ return false; ++ } ++ ++ // Note: the oracle attack on "quick check" bytes is not deemed a security risk for PBE; the ++ // quick check is retained on the PBE path because its failure + stream reset is what lets the ++ // decryptor detect a wrong passphrase and rewind to try the next SKESK packet. + + boolean repeatCheckPassed = iv[iv.length - 2] == (byte)v1 + && iv[iv.length - 1] == (byte)v2; +diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java +index 5f375c7..171d31f 100644 +--- a/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java ++++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java +@@ -234,7 +234,7 @@ private InputStream getDataStream( + + BCPGInputStream encIn = encData.getInputStream(); + +- processSymmetricEncIntegrityPacketDataStream(true, dataDecryptor, encIn); ++ processSymmetricEncIntegrityPacketDataStream(true, dataDecryptor, encIn, true); + } + // SEIPD v2 (OpenPGP v6 AEAD) + else +@@ -253,7 +253,7 @@ private InputStream getDataStream( + + BCPGInputStream encIn = encData.getInputStream(); + +- processSymmetricEncIntegrityPacketDataStream(false, dataDecryptor, encIn); ++ processSymmetricEncIntegrityPacketDataStream(false, dataDecryptor, encIn, true); + } + + // +diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSessionKeyEncryptedData.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSessionKeyEncryptedData.java +index 40c2cfa..daefa6e 100644 +--- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSessionKeyEncryptedData.java ++++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSessionKeyEncryptedData.java +@@ -62,4 +62,13 @@ public InputStream getDataStream( + + return encStream; + } ++ ++ // Decryption from an already-recovered session key (including PKESK/public-key via the high-level ++ // API): no multi-SKESK passphrase retry, so the CFB quick check is suppressed to avoid the ++ // Mister-Zuccherato oracle. Integrity is enforced by the SEIPD v1 MDC. ++ @Override ++ protected boolean isPublicKeyEncrypted() ++ { ++ return true; ++ } + } +diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSymmetricKeyEncryptedData.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSymmetricKeyEncryptedData.java +index ac6f3d2..aa4583d 100644 +--- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSymmetricKeyEncryptedData.java ++++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSymmetricKeyEncryptedData.java +@@ -78,7 +78,7 @@ private InputStream getDataStream( + { + BCPGInputStream encIn = encData.getInputStream(); + encIn.mark(dataDecryptor.getBlockSize() + 2); // iv + 2 octets checksum +- if (processSymmetricEncIntegrityPacketDataStream(withIntegrityPacket, dataDecryptor, encIn)) ++ if (processSymmetricEncIntegrityPacketDataStream(withIntegrityPacket, dataDecryptor, encIn, isPublicKeyEncrypted())) + { + encIn.reset(); + throw new PGPDataValidationException("data check failed."); +@@ -95,4 +95,15 @@ private InputStream getDataStream( + throw new PGPException("Exception creating cipher", e); + } + } ++ ++ /** ++ * Whether this data was decrypted from a public-key (or otherwise already-recovered session key) ++ * rather than from a password (PBE). The CFB "quick check" is suppressed on the public-key / ++ * session-key path (it would only re-create the Mister-Zuccherato oracle and there is no ++ * multi-SKESK passphrase retry to drive); PBE keeps it. Overridden by {@link PGPSessionKeyEncryptedData}. ++ */ ++ protected boolean isPublicKeyEncrypted() ++ { ++ return false; ++ } + } +diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPSessionKeyTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPSessionKeyTest.java +index eeceef6..81bf3c1 100644 +--- a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPSessionKeyTest.java ++++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPSessionKeyTest.java +@@ -12,6 +12,7 @@ + import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; + import org.bouncycastle.jce.provider.BouncyCastleProvider; + import org.bouncycastle.openpgp.PGPCompressedData; ++import org.bouncycastle.openpgp.PGPDataValidationException; + import org.bouncycastle.openpgp.PGPEncryptedDataList; + import org.bouncycastle.openpgp.PGPException; + import org.bouncycastle.openpgp.PGPLiteralData; +@@ -113,6 +114,47 @@ public void performTest() + testSessionKeyFromString(); + + decryptMessageWithoutEskUsingSessionKey(); ++ ++ sessionKeyDecryptionSuppressesQuickCheckOracle(); ++ } ++ ++ private void sessionKeyDecryptionSuppressesQuickCheckOracle() ++ throws IOException, PGPException ++ { ++ ByteArrayInputStream msgIn = new ByteArrayInputStream(Strings.toByteArray(PK_ENC_MESSAGE)); ++ ArmoredInputStream msgArmorIn = new ArmoredInputStream(msgIn); ++ PGPObjectFactory objectFactory = new BcPGPObjectFactory(msgArmorIn); ++ PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList)objectFactory.nextObject(); ++ PGPSessionKeyEncryptedData encryptedData = encryptedDataList.extractSessionKeyEncryptedData(); ++ ++ // Decrypt with a WRONG session key: the CFB "quick check" prefix will mismatch. On the ++ // session-key / public-key path (reached by the high-level API for PKESK messages) the quick ++ // check must NOT raise the early, distinguishable PGPDataValidationException("data check ++ // failed.") - that is the Mister-Zuccherato oracle. Integrity is left to the SEIPD v1 MDC. ++ byte[] wrongKey = Hex.decode(PK_ENC_SESSIONKEY); ++ wrongKey[0] ^= 0x01; ++ SessionKeyDataDecryptorFactory decryptorFactory = ++ new BcSessionKeyDataDecryptorFactory(new PGPSessionKey(PK_ENC_SESSIONKEY_ALG, wrongKey)); ++ ++ try ++ { ++ InputStream decrypted = encryptedData.getDataStream(decryptorFactory); ++ // getDataStream must not have thrown the quick-check oracle. Reading the (garbage) stream ++ // may fail later (MDC / parse) - that is fine and not a distinguishable early signal. ++ try ++ { ++ Streams.drain(decrypted); ++ decrypted.close(); ++ } ++ catch (Exception eLater) ++ { ++ // acceptable: a wrong key surfaces as a later failure, not an early quick-check throw ++ } ++ } ++ catch (PGPDataValidationException e) ++ { ++ fail("session-key decryption must not expose the CFB quick-check oracle (early data check)"); ++ } + } + + private void verifyPublicKeyDecryptionYieldsCorrectSessionData() diff --git a/bouncycastle.spec b/bouncycastle.spec index 7601e39..f05c115 100644 --- a/bouncycastle.spec +++ b/bouncycastle.spec @@ -4,12 +4,15 @@ Summary: Bouncy Castle Cryptography APIs for Java Name: bouncycastle Version: 1.80 -Release: 3%{?dist} +Release: 4%{?dist} License: MIT URL: http://www.bouncycastle.org Source0: https://github.com/bcgit/bc-java/archive/%{gittag}.tar.gz Patch0001: bouncycastle-1.80-CVE-2025-14813-1.patch Patch0002: bouncycastle-1.80-CVE-2025-14813-2.patch +Patch0003: bouncycastle-1.80-CVE-2026-58061-1.patch +Patch0004: bouncycastle-1.80-CVE-2026-58061-2.patch +Patch0005: bouncycastle-1.80-CVE-2026-59640.patch BuildRequires: javapackages-local BuildRequires: ant ant-junit BuildArch: noarch @@ -238,6 +241,10 @@ fi %changelog +* Wed Aug 05 2026 PkgAgent Robot - 1.80-4 +- [Type] security +- [DESC] Fix CVE-2026-58061, CVE-2026-59640 + * Wed Jul 22 2026 Zhao Zhen - 1.80-3 - [Type] bugfix - [DESC] add Maven-style Provides for jdk15on/jdk18on coordinates so packages -- Gitee