Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 136199d

Browse files
bartonjsstephentoub
authored andcommitted
Correctly deserialize constructed octet and bit strings (#29389)
When a BIT STRING or OCTET STRING is implicitly tagged using a tag from the context-specific, application, or private tag classes and has a constructed representation, the outer tag is the specified tag and the inner tags are 03 or 04. In the deserializer the correct tag was used for TryGetPrimitive[Type]Bytes, but that returns false for indefinite length encodings (because the content bytes are not contiguous). During the fallback to TryCopy[Type]Bytes the expected tag value was not passed along, so the read operation failed with a tag mismatch. Now we correctly pass the expected tag, so TryCopy[Type]Bytes matches the expected outer tag and continues with the constructed encoding (definite or indefinite length) rules for the BIT STRING or OCTET STRING value. Character string types also have TryGetPrimitive overloads, but these aren't used in the deserializer, currently only the string-allocating forms are supported.
1 parent ecbece3 commit 136199d

File tree

3 files changed

+82
-2
lines changed

3 files changed

+82
-2
lines changed

src/Common/src/System/Security/Cryptography/Asn1V2.Serializer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -879,7 +879,7 @@ private static Deserializer GetSimpleDeserializer(
879879

880880
try
881881
{
882-
if (reader.TryCopyBitStringBytes(rented, out _, out int bytesWritten))
882+
if (reader.TryCopyBitStringBytes(expectedTag, rented, out _, out int bytesWritten))
883883
{
884884
return new ReadOnlyMemory<byte>(rented.AsSpan(0, bytesWritten).ToArray());
885885
}
@@ -910,7 +910,7 @@ private static Deserializer GetSimpleDeserializer(
910910

911911
try
912912
{
913-
if (reader.TryCopyOctetStringBytes(rented, out int bytesWritten))
913+
if (reader.TryCopyOctetStringBytes(expectedTag, rented, out int bytesWritten))
914914
{
915915
return new ReadOnlyMemory<byte>(rented.AsSpan(0, bytesWritten).ToArray());
916916
}

src/System.Security.Cryptography.Encoding/tests/Asn1/Serializer/SimpleDeserialize.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,34 @@ public static void TooMuchDataForValue()
470470
Assert.Throws<CryptographicException>(
471471
() => AsnSerializer.Deserialize<OptionalValues>(inputData, AsnEncodingRules.BER));
472472
}
473+
474+
[Fact]
475+
public static void ReadIndefiniteLengthCustomTaggedStrings()
476+
{
477+
byte[] inputData = (
478+
// (constructed) SEQUENCE (indefinite)
479+
"3080" +
480+
// (constructed) CONTEXT-SPECIFIC 0 (indefinite)
481+
"A080" +
482+
// OCTET STRING (3): 020100
483+
"0403020100" +
484+
// EoC ([0])
485+
"0000" +
486+
// (constructed) CONTEXT-SPECIFIC 1 (indefinite)
487+
"A180" +
488+
// BIT STRING (4) (0 unused bits): 010203
489+
"030400010203" +
490+
// EoC ([1])
491+
"0000" +
492+
// EoC (SEQUENCE)
493+
"0000").HexToByteArray();
494+
495+
CustomTaggedBinaryStrings parsed =
496+
AsnSerializer.Deserialize<CustomTaggedBinaryStrings>(inputData, AsnEncodingRules.BER);
497+
498+
Assert.Equal("020100", parsed.OctetString.ByteArrayToHex());
499+
Assert.Equal("010203", parsed.BitString.ByteArrayToHex());
500+
}
473501
}
474502

475503
// RFC 3280 / ITU-T X.509
@@ -841,4 +869,16 @@ public struct OptionalValues
841869
[IA5String, OptionalValue]
842870
public string IA5String;
843871
}
872+
873+
[StructLayout(LayoutKind.Sequential)]
874+
public struct CustomTaggedBinaryStrings
875+
{
876+
[OctetString]
877+
[ExpectedTag(0)]
878+
public ReadOnlyMemory<byte> OctetString;
879+
880+
[BitString]
881+
[ExpectedTag(1)]
882+
public ReadOnlyMemory<byte> BitString;
883+
}
844884
}

src/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/GeneralTests.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,46 @@ private static void VerifyRecipients3(byte[] encodedMessage)
113113
Assert.Equal<string>(expectedIssuers, actualIssuers);
114114
}
115115

116+
117+
[Fact]
118+
public static void DecodeAllIndefinite()
119+
{
120+
byte[] encrypted = Convert.FromBase64String(
121+
@"
122+
MIAGCSqGSIb3DQEHA6CAMIACAQAxggFXMIIBUwIBADA7MDMxGTAXBgNVBAoMEERh
123+
dGEgSW50ZXJjaGFuZ2UxFjAUBgNVBAMMDVVubyBUZXN0IFJvb3QCBFqG6RQwDQYJ
124+
KoZIhvcNAQEBBQAEggEAUPilAHUe67HG5vDCO/JBmof44G/XnDLtiDrbxD4QekGq
125+
mdPqazZiLDKEewlBy2uFJr/JijeYx6qNKTXs/EShw/lYnKisaK5ue6JZ7ssMunM9
126+
HpkiDfM+iyN7PxnC1riZ/Kg2JExY8pf5R1Zuvu29JSLhM9ajWk9C1pBzQRJ4vkY2
127+
OvFKR2th0Vgw7mTmc2X6HUK4tosB3LGKDVNd6BVoMQMvfkseCqeZOe1KIiBFmhyk
128+
E+B2UZcD6Z6kLnCk4LNGyoyxW6Thv5s/lwP9p7trVVbPXbuep1l8uMCGj6vjTD66
129+
AamEIRmTFvEVHzyO2MGG9V0bM+8UpqPAVFNCXOm6mjCABgkqhkiG9w0BBwEwFAYI
130+
KoZIhvcNAwcECJ01qtX2EKx6oIAEEM7op+R2U3GQbYwlEj5X+h0AAAAAAAAAAAAA
131+
");
132+
EnvelopedCms cms = new EnvelopedCms();
133+
cms.Decode(encrypted);
134+
135+
RecipientInfoCollection recipientInfos = cms.RecipientInfos;
136+
137+
Assert.Equal(1, recipientInfos.Count);
138+
Assert.Equal(
139+
SubjectIdentifierType.IssuerAndSerialNumber,
140+
recipientInfos[0].RecipientIdentifier.Type);
141+
142+
string expectedContentHex = "CEE8A7E4765371906D8C25123E57FA1D";
143+
144+
if (PlatformDetection.IsFullFramework)
145+
{
146+
// .NET Framework over-counts encrypted content.
147+
expectedContentHex += "000000000000";
148+
}
149+
150+
// Still encrypted.
151+
Assert.Equal(
152+
expectedContentHex,
153+
cms.ContentInfo.Content.ByteArrayToHex());
154+
}
155+
116156
[Fact]
117157
public static void TestGetContentTypeEnveloped()
118158
{

0 commit comments

Comments
 (0)