From e8c70f99c2a2b73ff48752e2228ea2df8666fa87 Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Wed, 21 May 2025 23:09:59 +0700 Subject: [PATCH 1/2] Patch #1 for 2.6 [f5abebeb0484895ff182f23c9caa92682f71728b] CMS: Fix generation of certs-only signed-data [62b88690178304a3ddfac28ee2ac11bbb3365f43] Update Readme for 2.6.1 --- crypto/Readme.html | 8 ++++++++ crypto/src/cms/CMSSignedDataGenerator.cs | 4 ++-- crypto/src/cms/CMSUtils.cs | 10 +++++++--- crypto/test/src/cms/test/SignedDataTest.cs | 19 +++++++++++++++++++ 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/crypto/Readme.html b/crypto/Readme.html index 566c31ca1..66f03c465 100644 --- a/crypto/Readme.html +++ b/crypto/Readme.html @@ -31,6 +31,8 @@

Contents:

  • Notes:
      +
    1. + Release 2.6.1
    2. Release 2.6.0
    3. @@ -339,6 +341,12 @@

      For first time users.


      Notes:

      +

      Release 2.6.1, Thursday May 22, 2025

      +
      Defects Fixed
      +
        +
      • CMS: Fix generation of certs-only signed-data (regression in 2.6.0).
      • +
      +

      Release 2.6.0, Thursday May 15, 2025

      Defects Fixed
        diff --git a/crypto/src/cms/CMSSignedDataGenerator.cs b/crypto/src/cms/CMSSignedDataGenerator.cs index ac6849ea1..0fee4bfc2 100644 --- a/crypto/src/cms/CMSSignedDataGenerator.cs +++ b/crypto/src/cms/CMSSignedDataGenerator.cs @@ -574,9 +574,9 @@ public CmsSignedData Generate( } } - Asn1Set certificates = _certs.ToAsn1Set(_useDerForCerts, _useDefiniteLength); + Asn1Set certificates = _certs.ToAsn1SetOptional(_useDerForCerts, _useDefiniteLength); - Asn1Set crls = _crls.ToAsn1Set(_useDerForCrls, _useDefiniteLength); + Asn1Set crls = _crls.ToAsn1SetOptional(_useDerForCrls, _useDefiniteLength); Asn1OctetString encapContent = null; if (encapsulate) diff --git a/crypto/src/cms/CMSUtils.cs b/crypto/src/cms/CMSUtils.cs index 769735dce..9c5203d63 100644 --- a/crypto/src/cms/CMSUtils.cs +++ b/crypto/src/cms/CMSUtils.cs @@ -256,15 +256,19 @@ internal static void CollectOtherRevocationInfos(List result, internal static Asn1Set ToAsn1Set(this IReadOnlyCollection elements, bool useDer, bool useDL) { - return elements.Count < 1 - ? null - : useDer + return useDer ? ToDerSet(elements) : useDL ? ToDLSet(elements) : ToBerSet(elements); } + internal static Asn1Set ToAsn1SetOptional(this IReadOnlyCollection elements, bool useDer, + bool useDL) + { + return elements.Count < 1 ? null : ToAsn1Set(elements, useDer, useDL); + } + internal static Asn1Set ToBerSet(this IReadOnlyCollection elements) => BerSet.FromCollection(elements); diff --git a/crypto/test/src/cms/test/SignedDataTest.cs b/crypto/test/src/cms/test/SignedDataTest.cs index 6ed56e330..806aacb67 100644 --- a/crypto/test/src/cms/test/SignedDataTest.cs +++ b/crypto/test/src/cms/test/SignedDataTest.cs @@ -2335,6 +2335,25 @@ public void TestCounterSig() VerifySignatures(sig); } + [Test] + public void TestCertificateManagement() + { + var x509Certs = CmsTestUtil.MakeCertStore(SignCert, OrigCert); + + CmsSignedDataGenerator sGen = new CmsSignedDataGenerator(); + sGen.AddCertificates(x509Certs); + + // TODO[cms] Note that in bc-java you can force absent encapContent, even with encapsulate=true + //CmsSignedData sData = sGen.Generate(new CmsAbsentContent(), true); + CmsSignedData sData = sGen.Generate(null, encapsulate: false); + + CmsSignedData rsData = new CmsSignedData(sData.GetEncoded()); + + var certs = new List(rsData.GetCertificates().EnumerateMatches(null)); + + Assert.AreEqual(2, certs.Count); + } + [Test] public void TestEncryptionAlgECPublicKey() { From b4f2f6ad76bcd1f11f365ee50cc7447fbce79077 Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Thu, 22 May 2025 22:00:57 +0700 Subject: [PATCH 2/2] Patch #2 for 2.6 [1943e2f724bc8df46a2e4ca0e4730fc6d7d906c2] Fix tagging of certIssuer field (CHOICE type) [e8764e0c4aad6db4004e797773534b0c07f0ffeb] fix(jks): Handle `keytool` JKS ASN.1 format change for key entries as of `v20` [b898e6397d067a4f483ae167040a10b03b4e263e] Fix the issue that bodyLen may exceed int.MaxValue [b497d429e49b527573e2ace6c9bf3c8533548693] Restore old raw RSA behaviour (RSA, NONEwithRSA, etc.) [72f926777638eb1ad5c2d091d671923ab47b02fc] Update Readme for 2.6.2 --- crypto/Readme.html | 11 ++++++ crypto/src/asn1/bc/LinkedCertificate.cs | 4 +-- crypto/src/bcpg/BcpgInputStream.cs | 35 +++++++++---------- crypto/src/bcpg/SignatureSubpacketsReader.cs | 8 ++--- crypto/src/bcpg/StreamUtilities.cs | 24 +++++++------ .../src/bcpg/UserAttributeSubpacketsReader.cs | 5 ++- crypto/src/security/JksStore.cs | 8 +++-- crypto/src/security/SignerUtilities.cs | 3 +- .../src/asn1/test/LinkedCertificateTest.cs | 18 ++++------ 9 files changed, 64 insertions(+), 52 deletions(-) diff --git a/crypto/Readme.html b/crypto/Readme.html index 66f03c465..0a4b5802c 100644 --- a/crypto/Readme.html +++ b/crypto/Readme.html @@ -31,6 +31,8 @@

        Contents:

      • Notes:
          +
        1. + Release 2.6.2
        2. Release 2.6.1
        3. @@ -341,6 +343,15 @@

          For first time users.


          Notes:

          +

          Release 2.6.2, Thursday July 31, 2025

          +
          Defects Fixed
          +
            +
          • ASN.1: Fixed tagging of LinkedCertificate.certIssuer field (CHOICE type).
          • +
          • JKS: Handle `keytool` ASN.1 format change for key entries as of `v20`.
          • +
          • OpenPGP: Fix the issue that bodyLen may exceed int.MaxValue.
          • +
          • Restore old raw RSA behaviour (RSA, NONEwithRSA, etc.) in SignerUtilities (regression in 2.6.0).
          • +
          +

          Release 2.6.1, Thursday May 22, 2025

          Defects Fixed
            diff --git a/crypto/src/asn1/bc/LinkedCertificate.cs b/crypto/src/asn1/bc/LinkedCertificate.cs index 0c194ac15..c99318bd0 100644 --- a/crypto/src/asn1/bc/LinkedCertificate.cs +++ b/crypto/src/asn1/bc/LinkedCertificate.cs @@ -59,7 +59,7 @@ private LinkedCertificate(Asn1Sequence seq) m_digest = DigestInfo.GetInstance(seq[pos++]); m_certLocation = GeneralName.GetInstance(seq[pos++]); - m_certIssuer = Asn1Utilities.ReadOptionalContextTagged(seq, ref pos, 0, false, X509Name.GetTagged); + m_certIssuer = Asn1Utilities.ReadOptionalContextTagged(seq, ref pos, 0, true, X509Name.GetTagged); // CHOICE m_cACerts = Asn1Utilities.ReadOptionalContextTagged(seq, ref pos, 1, false, GeneralNames.GetTagged); if (pos != count) @@ -78,7 +78,7 @@ public override Asn1Object ToAsn1Object() { Asn1EncodableVector v = new Asn1EncodableVector(4); v.Add(m_digest, m_certLocation); - v.AddOptionalTagged(false, 0, m_certIssuer); + v.AddOptionalTagged(true, 0, m_certIssuer); // CHOICE v.AddOptionalTagged(false, 1, m_cACerts); return new DerSequence(v); } diff --git a/crypto/src/bcpg/BcpgInputStream.cs b/crypto/src/bcpg/BcpgInputStream.cs index 623696ceb..1f382bb37 100644 --- a/crypto/src/bcpg/BcpgInputStream.cs +++ b/crypto/src/bcpg/BcpgInputStream.cs @@ -120,8 +120,8 @@ public Packet ReadPacket() bool newPacket = (hdr & 0x40) != 0; PacketTag tag = 0; - // TODO[pgp] Is the length field supposed to support full uint range? - int bodyLen; + + uint bodyLen; bool partial = false; if (newPacket) @@ -145,10 +145,10 @@ public Packet ReadPacket() bodyLen = StreamUtilities.RequireUInt16BE(this); break; case 2: - bodyLen = (int)StreamUtilities.RequireUInt32BE(this); + bodyLen = StreamUtilities.RequireUInt32BE(this); break; case 3: - bodyLen = 0; + bodyLen = 0U; partial = true; break; default: @@ -157,7 +157,7 @@ public Packet ReadPacket() } BcpgInputStream objStream; - if (bodyLen == 0 && partial) + if (bodyLen == 0U && partial) { objStream = this; } @@ -238,19 +238,18 @@ protected override void Dispose(bool disposing) /// /// A stream that overlays our input stream, allowing the user to only read a segment of it. - /// NB: dataLength will be negative if the segment length is in the upper range above 2**31. /// private class PartialInputStream : BaseInputStream { private BcpgInputStream m_in; private bool partial; - private int dataLength; + private uint dataLength; internal PartialInputStream( BcpgInputStream bcpgIn, bool partial, - int dataLength) + uint dataLength) { this.m_in = bcpgIn; this.partial = partial; @@ -261,7 +260,7 @@ public override int ReadByte() { do { - if (dataLength != 0) + if (dataLength > 0U) { int ch = m_in.ReadByte(); if (ch < 0) @@ -282,14 +281,14 @@ public override int Read(byte[] buffer, int offset, int count) do { - if (dataLength != 0) + if (dataLength > 0U) { - int readLen = (dataLength > count || dataLength < 0) ? count : dataLength; + int readLen = (uint)count < dataLength ? count : (int)dataLength; int len = m_in.Read(buffer, offset, readLen); if (len < 1) throw new EndOfStreamException("Premature end of stream in PartialInputStream"); - dataLength -= len; + dataLength -= (uint)len; return len; } } @@ -303,15 +302,15 @@ public override int Read(Span buffer) { do { - if (dataLength != 0) + if (dataLength > 0U) { int count = buffer.Length; - int readLen = (dataLength > count || dataLength < 0) ? count : dataLength; + int readLen = (uint)count < dataLength ? count : (int)dataLength; int len = m_in.Read(buffer[..readLen]); if (len < 1) throw new EndOfStreamException("Premature end of stream in PartialInputStream"); - dataLength -= len; + dataLength -= (uint)len; return len; } } @@ -323,11 +322,11 @@ public override int Read(Span buffer) private bool ReadPartialDataLength() { - int bodyLen = StreamUtilities.ReadBodyLen(m_in, out var streamFlags); - if (bodyLen < 0) + uint bodyLen = StreamUtilities.ReadBodyLen(m_in, out var streamFlags); + if (streamFlags.HasFlag(StreamUtilities.StreamFlags.Eof)) { partial = false; - dataLength = 0; + dataLength = 0U; return false; } diff --git a/crypto/src/bcpg/SignatureSubpacketsReader.cs b/crypto/src/bcpg/SignatureSubpacketsReader.cs index 966a2bd82..80bc164bf 100644 --- a/crypto/src/bcpg/SignatureSubpacketsReader.cs +++ b/crypto/src/bcpg/SignatureSubpacketsReader.cs @@ -20,8 +20,8 @@ public SignatureSubpacketsParser(Stream input) public SignatureSubpacket ReadPacket() { - int bodyLen = StreamUtilities.ReadBodyLen(m_input, out var streamFlags); - if (bodyLen < 0) + uint bodyLen = StreamUtilities.ReadBodyLen(m_input, out var streamFlags); + if (streamFlags.HasFlag(StreamUtilities.StreamFlags.Eof)) return null; if (streamFlags.HasFlag(StreamUtilities.StreamFlags.Partial)) @@ -29,11 +29,11 @@ public SignatureSubpacket ReadPacket() bool isLongLength = streamFlags.HasFlag(StreamUtilities.StreamFlags.LongLength); - if (bodyLen < 1) + // see below about miscoding... see bc-java for configurable upper limit + if (bodyLen < 1U) throw new EndOfStreamException("out of range data found in signature sub packet"); int tag = StreamUtilities.RequireByte(m_input); - byte[] data = new byte[bodyLen - 1]; // diff --git a/crypto/src/bcpg/StreamUtilities.cs b/crypto/src/bcpg/StreamUtilities.cs index 352a80906..1a04d8f0c 100644 --- a/crypto/src/bcpg/StreamUtilities.cs +++ b/crypto/src/bcpg/StreamUtilities.cs @@ -6,7 +6,7 @@ namespace Org.BouncyCastle.Bcpg { - internal static class StreamUtilities + internal static class StreamUtilities { [Flags] internal enum StreamFlags @@ -14,39 +14,43 @@ internal enum StreamFlags None = 0, LongLength = 1, Partial = 2, + Eof = 4 } - internal static int ReadBodyLen(Stream s, out StreamFlags flags) + internal static uint ReadBodyLen(Stream s, out StreamFlags flags) { flags = StreamFlags.None; int b0 = s.ReadByte(); if (b0 < 0) - return -1; + { + flags = StreamFlags.Eof; + return 0U; + } if (b0 < 192) - return b0; + return (uint)b0; if (b0 < 224) { int b1 = RequireByte(s); - return ((b0 - 192) << 8) + b1 + 192; + return (uint)(((b0 - 192) << 8) + b1 + 192); } if (b0 == 255) { flags |= StreamFlags.LongLength; - return (int)RequireUInt32BE(s); + return RequireUInt32BE(s); } flags |= StreamFlags.Partial; - return 1 << (b0 & 0x1F); + return 1U << (b0 & 0x1F); } - internal static int RequireBodyLen(Stream s, out StreamFlags flags) + internal static uint RequireBodyLen(Stream s, out StreamFlags streamFlags) { - int bodyLen = ReadBodyLen(s, out flags); - if (bodyLen < 0) + uint bodyLen = ReadBodyLen(s, out streamFlags); + if (streamFlags.HasFlag(StreamFlags.Eof)) throw new EndOfStreamException(); return bodyLen; } diff --git a/crypto/src/bcpg/UserAttributeSubpacketsReader.cs b/crypto/src/bcpg/UserAttributeSubpacketsReader.cs index 1d3844ae5..c567d2a51 100644 --- a/crypto/src/bcpg/UserAttributeSubpacketsReader.cs +++ b/crypto/src/bcpg/UserAttributeSubpacketsReader.cs @@ -1,7 +1,6 @@ using System.IO; using Org.BouncyCastle.Bcpg.Attr; -using Org.BouncyCastle.Utilities.IO; namespace Org.BouncyCastle.Bcpg { @@ -19,8 +18,8 @@ public UserAttributeSubpacketsParser(Stream input) public virtual UserAttributeSubpacket ReadPacket() { - int bodyLen = StreamUtilities.ReadBodyLen(m_input, out var streamFlags); - if (bodyLen < 0) + uint bodyLen = StreamUtilities.ReadBodyLen(m_input, out var streamFlags); + if (streamFlags.HasFlag(StreamUtilities.StreamFlags.Eof)) return null; if (streamFlags.HasFlag(StreamUtilities.StreamFlags.Partial)) diff --git a/crypto/src/security/JksStore.cs b/crypto/src/security/JksStore.cs index 7f9e80cae..905a5340f 100644 --- a/crypto/src/security/JksStore.cs +++ b/crypto/src/security/JksStore.cs @@ -24,6 +24,10 @@ public class JksStore private static readonly AlgorithmIdentifier JksObfuscationAlg = new AlgorithmIdentifier( new DerObjectIdentifier("1.3.6.1.4.1.42.2.17.1.1"), DerNull.Instance); + // keytool >=v20 removed the null parameter from the AlgorithmIdentifier + private static readonly AlgorithmIdentifier JksObfuscationAlgV20 = new AlgorithmIdentifier( + new DerObjectIdentifier("1.3.6.1.4.1.42.2.17.1.1")); + private readonly Dictionary m_certificateEntries = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary m_keyEntries = @@ -63,7 +67,7 @@ public AsymmetricKeyParameter GetKey(string alias, char[] password) return null; var epki = keyEntry.keyInfo; - if (!JksObfuscationAlg.Equals(epki.EncryptionAlgorithm)) + if (!JksObfuscationAlg.Equals(epki.EncryptionAlgorithm) && !JksObfuscationAlgV20.Equals(epki.EncryptionAlgorithm)) throw new IOException("unknown encryption algorithm"); byte[] encryptedData = epki.GetEncryptedData(); @@ -105,7 +109,7 @@ public AsymmetricKeyParameter GetKey(string alias, ReadOnlySpan password) return null; var epki = keyEntry.keyInfo; - if (!JksObfuscationAlg.Equals(epki.EncryptionAlgorithm)) + if (!JksObfuscationAlg.Equals(epki.EncryptionAlgorithm) && !JksObfuscationAlgV20.Equals(epki.EncryptionAlgorithm)) throw new IOException("unknown encryption algorithm"); byte[] encryptedData = epki.GetEncryptedData(); diff --git a/crypto/src/security/SignerUtilities.cs b/crypto/src/security/SignerUtilities.cs index b039ad9e8..8faeb75f6 100644 --- a/crypto/src/security/SignerUtilities.cs +++ b/crypto/src/security/SignerUtilities.cs @@ -16,6 +16,7 @@ using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Encodings; using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Signers; @@ -761,7 +762,7 @@ private static ISigner GetSignerForMechanism(string mechanism) if (mechanism.Equals("RSA")) { - return (new RsaDigestSigner(new NullDigest(), (AlgorithmIdentifier)null)); + return new GenericSigner(new Pkcs1Encoding(new RsaBlindedEngine()), new NullDigest()); } if (mechanism.Equals("RAWRSASSA-PSS")) { diff --git a/crypto/test/src/asn1/test/LinkedCertificateTest.cs b/crypto/test/src/asn1/test/LinkedCertificateTest.cs index ff475eeca..695caa9a5 100644 --- a/crypto/test/src/asn1/test/LinkedCertificateTest.cs +++ b/crypto/test/src/asn1/test/LinkedCertificateTest.cs @@ -12,15 +12,13 @@ namespace Org.BouncyCastle.Asn1.Tests public class LinkedCertificateTest : Asn1UnitTest { - public override string Name - { - get { return "LinkedCertificate"; } - } + public override string Name => "LinkedCertificate"; public override void PerformTest() { DigestInfo digInfo = new DigestInfo(new AlgorithmIdentifier(NistObjectIdentifiers.IdSha256), new byte[32]); - GeneralName certLocation = new GeneralName(GeneralName.UniformResourceIdentifier, "https://www.bouncycastle.org/certs"); + GeneralName certLocation = new GeneralName(GeneralName.UniformResourceIdentifier, + "https://www.bouncycastle.org/certs"); X509Name certIssuer = null; GeneralNames cACerts = null; @@ -39,14 +37,14 @@ public override void PerformTest() if (linked != null) { - Fail("null getInstance() failed."); + Fail("null GetInstance() failed."); } try { LinkedCertificate.GetInstance(new object()); - Fail("getInstance() failed to detect bad object."); + Fail("GetInstance() failed to detect bad object."); } catch (ArgumentException) { @@ -63,11 +61,7 @@ private void CheckConstruction(LinkedCertificate linked, DigestInfo digestInfo, CheckValues(linked, digestInfo, certLocation, certIssuer, caCerts); - Asn1InputStream aIn = new Asn1InputStream(linked.ToAsn1Object().GetEncoded()); - - Asn1Sequence seq = (Asn1Sequence)aIn.ReadObject(); - - linked = LinkedCertificate.GetInstance(seq); + linked = LinkedCertificate.GetInstance(linked.GetEncoded()); CheckValues(linked, digestInfo, certLocation, certIssuer, caCerts); }