diff --git a/src/libraries/Common/src/System/Security/Cryptography/Pkcs/Pkcs12SafeContents.cs b/src/libraries/Common/src/System/Security/Cryptography/Pkcs/Pkcs12SafeContents.cs index df61f77e9814d1..c6c4b225a845a7 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Pkcs/Pkcs12SafeContents.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Pkcs/Pkcs12SafeContents.cs @@ -9,6 +9,11 @@ using System.Security.Cryptography.X509Certificates; using Internal.Cryptography; +using NestedSafeContentsStack = System.Collections.Generic.Stack<( + System.ReadOnlyMemory Serialized, + System.Security.Cryptography.Pkcs.Pkcs12SafeContentsBag Bag, + System.Collections.Generic.List Container)>; + namespace System.Security.Cryptography.Pkcs { #if BUILDING_PKCS @@ -30,6 +35,12 @@ public Pkcs12SafeContents() ConfidentialityMode = Pkcs12ConfidentialityMode.None; } + private Pkcs12SafeContents(bool isReadOnly) + { + IsReadOnly = isReadOnly; + ConfidentialityMode = Pkcs12ConfidentialityMode.None; + } + internal Pkcs12SafeContents(ReadOnlyMemory serialized) { IsReadOnly = true; @@ -278,6 +289,50 @@ public IEnumerable GetBags() } private static List ReadBags(ReadOnlyMemory serialized) + { + NestedSafeContentsStack pendingContents = new NestedSafeContentsStack(); + List result = ReadBagsCore(serialized, pendingContents); + + while (pendingContents.Count > 0) + { + (ReadOnlyMemory nestedSerialized, Pkcs12SafeContentsBag bag, List container) = pendingContents.Pop(); + bool replace = true; + + try + { + Pkcs12SafeContents? target = bag.SafeContents; + Debug.Assert(target is not null); + target._bags = ReadBagsCore(nestedSerialized, pendingContents); + replace = false; + } + catch (AsnContentException) + { + } + catch (CryptographicException) + { + } + + if (replace) + { + // Replace the deferred bag with an unknown bag, since the contents couldn't be parsed. + Pkcs12SafeBag.UnknownBag unknownBag = new(Oids.Pkcs12SafeContentsBag, nestedSerialized) + { + Attributes = bag.Attributes, + }; + + int idx = container.IndexOf(bag); + Debug.Assert(idx != -1); + + container[idx] = unknownBag; + } + } + + return result; + } + + private static List ReadBagsCore( + ReadOnlyMemory serialized, + NestedSafeContentsStack pendingSafeContents) { List serializedBags = new List(); @@ -330,8 +385,14 @@ private static List ReadBags(ReadOnlyMemory serialized) bag = Pkcs12SecretBag.DecodeValue(bagValue); break; case Oids.Pkcs12SafeContentsBag: - bag = Pkcs12SafeContentsBag.Decode(bagValue); + { + Pkcs12SafeContents deferredContents = new(isReadOnly: true); + Pkcs12SafeContentsBag nested; + nested = Pkcs12SafeContentsBag.CreateWithDeferredContents(bagValue, deferredContents); + bag = nested; + pendingSafeContents.Push((bagValue, nested, bags)); break; + } } } catch (AsnContentException) diff --git a/src/libraries/Common/src/System/Security/Cryptography/Pkcs/Pkcs12SafeContentsBag.cs b/src/libraries/Common/src/System/Security/Cryptography/Pkcs/Pkcs12SafeContentsBag.cs index 610d63e332d299..6a628291453237 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Pkcs/Pkcs12SafeContentsBag.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Pkcs/Pkcs12SafeContentsBag.cs @@ -30,7 +30,7 @@ internal static Pkcs12SafeContentsBag Create(Pkcs12SafeContents copyFrom) return Decode(writer.Encode()); } - internal static Pkcs12SafeContentsBag Decode(ReadOnlyMemory encodedValue) + private static Pkcs12SafeContentsBag Decode(ReadOnlyMemory encodedValue) { Pkcs12SafeContents contents = new Pkcs12SafeContents(encodedValue); @@ -39,5 +39,15 @@ internal static Pkcs12SafeContentsBag Decode(ReadOnlyMemory encodedValue) SafeContents = contents }; } + + internal static Pkcs12SafeContentsBag CreateWithDeferredContents( + ReadOnlyMemory encodedValue, + Pkcs12SafeContents deferredContents) + { + return new Pkcs12SafeContentsBag(encodedValue) + { + SafeContents = deferredContents + }; + } } } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/tests/Pkcs12/Pkcs12InfoTests.cs b/src/libraries/System.Security.Cryptography.Pkcs/tests/Pkcs12/Pkcs12InfoTests.cs index d88ffb629dfdf2..69fb9b6c8f52d4 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/tests/Pkcs12/Pkcs12InfoTests.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/tests/Pkcs12/Pkcs12InfoTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Formats.Asn1; using System.Linq; using System.Security.Cryptography.X509Certificates; using System.Text; @@ -286,6 +287,141 @@ public static void DecodeWithTooManyPbeIterations() } } + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void DecodeDeeplyNestedPfx(bool withDecodeError) + { + const int Depth = 3333; + int errorTarget = withDecodeError ? Depth / 4 * 3 : -1; + byte[] pfx = BuildPfx(Depth, errorTarget); + + Pkcs12Info info = Pkcs12Info.Decode(pfx, out _, skipCopy: true); + Pkcs12SafeContents contents = Assert.Single(info.AuthenticatedSafe); + Pkcs12SafeContents deepContents = contents; + + int count = withDecodeError ? errorTarget - 1: Depth; + + for (int i = 0; i < count; i++) + { + Pkcs12SafeBag bag = Assert.Single(contents.GetBags()); + Pkcs12SafeContentsBag typedBag = Assert.IsType(bag); + contents = typedBag.SafeContents; + } + + Pkcs12SafeBag finalBag = Assert.Single(contents.GetBags()); + + if (withDecodeError) + { + Assert.IsNotType(finalBag); + Assert.Equal("1.2.840.113549.1.12.10.1.6", finalBag.GetBagId().Value); + + ValueAsnReader reader = new ValueAsnReader(finalBag.EncodedBagValue.Span, AsnEncodingRules.BER); + ValueAsnReader inner = reader.ReadSequence(); + reader.ThrowIfNotEmpty(); + reader = inner; + + inner = reader.ReadSequence(); + reader.ThrowIfNotEmpty(); + reader = inner; + + reader.ReadObjectIdentifier(); + inner = reader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 0)); + AssertExtensions.TrueExpression(inner.ReadBoolean()); + } + else + { + Pkcs12SecretBag secretBag = Assert.IsType(finalBag); + Assert.Equal("1.2.840.113549.1.9.21", secretBag.GetSecretType().Value); + ReadOnlySpan expectedValue = [0x04, 0x03, 0x01, 0x02, 0x03]; + AssertExtensions.SequenceEqual(expectedValue, secretBag.SecretValue.Span); + } + + Pkcs12SafeContents newContents = new Pkcs12SafeContents(); + // Assert.NoThrow + newContents.AddNestedContents(deepContents); + + static byte[] BuildPfx(int depth, int errorDepth) + { + // If depth is 0, the output is 80 bytes. + // Each nested bag adds a 13 byte OID, and 3 CONSTRUCTED values each with a 1-byte tag. + // As the depth increases, the length-length increases for the CONSTRUCTED values, up + // to 3 length-value bytes for the worst case (assuming we don't pass a big enough depth + // to exceed 16.7MB). + // So, call each nested bag 13 + 3 * (1 + 1 + 3) = 28 bytes. + // The 80 bytes of overhead can be ignored, because of the over-counting for the low iteration count. + // For 3333 the estimate is 93,324, and the actual output is 85,520. + AsnWriter builder = new AsnWriter(AsnEncodingRules.DER, checked(28 * depth)); + Asn1Tag context0 = new Asn1Tag(TagClass.ContextSpecific, 0); + const string Pkcs7Data = "1.2.840.113549.1.7.1"; + const string Pkcs12SafeContents = "1.2.840.113549.1.12.10.1.6"; + + using (builder.PushSequence()) + { + builder.WriteInteger(3); + + using (builder.PushSequence()) + { + builder.WriteObjectIdentifier(Pkcs7Data); + + using (builder.PushSequence(context0)) + using (builder.PushOctetString()) + using (builder.PushSequence()) + { + using (builder.PushSequence()) + { + builder.WriteObjectIdentifier(Pkcs7Data); + + using (builder.PushSequence(context0)) + using (builder.PushOctetString()) + using (builder.PushSequence()) + { + for (int i = 0; i < depth; i++) + { + builder.PushSequence(); + builder.WriteObjectIdentifier(Pkcs12SafeContents); + builder.PushSequence(context0); + + if (i == errorDepth) + { + builder.WriteBoolean(true); + } + + builder.PushSequence(); + } + + using (builder.PushSequence()) + { + builder.WriteObjectIdentifier("1.2.840.113549.1.12.10.1.5"); + + using (builder.PushSequence(context0)) + using (builder.PushSequence()) + { + builder.WriteObjectIdentifier("1.2.840.113549.1.9.21"); + + using (builder.PushSequence(context0)) + { + builder.WriteOctetString([0x01, 0x02, 0x03]); + } + } + } + + for (int i = 0; i < depth; i++) + { + builder.PopSequence(); + builder.PopSequence(context0); + builder.PopSequence(); + } + } + } + } + } + } + + return builder.Encode(); + } + } + public static IEnumerable DecodeWithHighPbeIterationsData { get