Skip to content

Commit 45c6b99

Browse files
committed
Patch #1 for 2.3
- TLS: fix timing side-channel for RSA key exchange - fix method Write(ReadOnlySpan<byte>) in LimitedBuffer - ASN.1: Limit OID contents to 4096 bytes - EdDSA: fix verification infinite loop - EC: restrict m value in F2m curves
1 parent cf1829b commit 45c6b99

File tree

16 files changed

+2456
-232
lines changed

16 files changed

+2456
-232
lines changed

crypto/Contributors.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,9 +268,9 @@ <h3>Code Contributors:</h3>
268268
<li>
269269
<p>Matthew Sitton (https://github.com/mdsitton) - Addition of missing ALPN Protocol names.</p>
270270
</li>
271-
<li>
272-
<p>Jozef Gajdo&scaron; (https://github.com/harrison314) - Time constructor optimization, RevokedStatus fix, improved thread-safe singleton code (e.g. X509Certificate/X509Crl cached encoding), SubjectPublicKeyInfo support in OpenSsl.PemWriter.</p>
273-
</li>
271+
<li>
272+
<p>Jozef Gajdo&scaron; (https://github.com/harrison314) - Time constructor optimization, RevokedStatus fix, improved thread-safe singleton code (e.g. X509Certificate/X509Crl cached encoding), SubjectPublicKeyInfo support in OpenSsl.PemWriter, fixed PSS raw signing over spans.</p>
273+
</li>
274274
<li>
275275
<p>Ben Adams (https://github.com/benaadams) - Performance optimization for AES-NI.</p>
276276
</li>

crypto/Readme.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ <h3><a class="mozTocH3" name="mozTocId685176"></a>Contents:<br /></h3>
3131
<li>
3232
<a href="#mozTocId3413">Notes:</a>
3333
<ol>
34+
<li>
35+
<a href="#mozTocId85332">Release 2.3.1</a>
3436
<li>
3537
<a href="#mozTocId85331">Release 2.3.0</a>
3638
<li>
@@ -329,6 +331,27 @@ <h3><a class="mozTocH3" name="mozTocId358608"></a>For first time users.</h3>
329331
<hr style="WIDTH: 100%; HEIGHT: 2px">
330332
<h3><a class="mozTocH3" name="mozTocId3413"></a>Notes:</h3>
331333

334+
<h4><a class="mozTocH4" name="mozTocId85332"></a>Release 2.3.1, Tuesday May 7, 2024</h4>
335+
<h5>Defects Fixed</h5>
336+
<ul>
337+
<li>TLS: Fixed timing side-channel for RSA key exchange ("The Marvin Attack").</li>
338+
<li>PSS: Fixed regression in 2.3.0 when updating signer from a span.</li>
339+
<li>EdDSA: Fixed verification infinite loop (regression in 2.1.0)
340+
- see <a href="https://github.com/bcgit/bc-java/issues/1599">corresponding bc-java issue</a>.</li>
341+
</ul>
342+
<h5>Additional Features and Functionality</h5>
343+
<ul>
344+
<li>ASN.1: Limited OID contents to 4096 bytes.</li>
345+
<li>EC: Restricted m value in F2m curves.</li>
346+
</ul>
347+
<h5>Additional Notes</h5>
348+
<ul>
349+
<li>
350+
See the (cumulative) list of GitHub pull requests that we have accepted at
351+
<a href="https://github.com/bcgit/bc-csharp/pulls?q=is%3Apr+is%3Aclosed">bcgit/bc-csharp</a>.
352+
</li>
353+
</ul>
354+
332355
<h4><a class="mozTocH4" name="mozTocId85331"></a>Release 2.3.0, Monday February 5, 2024</h4>
333356
<h5>Defects Fixed</h5>
334357
<ul>

crypto/src/asn1/Asn1InputStream.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,9 @@ internal static Asn1Object CreatePrimitiveDerObject(int tagNo, DefiniteLengthInp
377377
switch (tagNo)
378378
{
379379
case Asn1Tags.BmpString:
380+
{
380381
return CreateDerBmpString(defIn);
382+
}
381383
case Asn1Tags.Boolean:
382384
{
383385
GetBuffer(defIn, tmpBuffers, out var contents);
@@ -390,9 +392,16 @@ internal static Asn1Object CreatePrimitiveDerObject(int tagNo, DefiniteLengthInp
390392
}
391393
case Asn1Tags.ObjectIdentifier:
392394
{
395+
DerObjectIdentifier.CheckContentsLength(defIn.Remaining);
393396
bool usedBuffer = GetBuffer(defIn, tmpBuffers, out var contents);
394397
return DerObjectIdentifier.CreatePrimitive(contents, clone: usedBuffer);
395398
}
399+
case Asn1Tags.RelativeOid:
400+
{
401+
Asn1RelativeOid.CheckContentsLength(defIn.Remaining);
402+
bool usedBuffer = GetBuffer(defIn, tmpBuffers, out var contents);
403+
return Asn1RelativeOid.CreatePrimitive(contents, clone: usedBuffer);
404+
}
396405
}
397406

398407
byte[] bytes = defIn.ToArray();
@@ -421,8 +430,6 @@ internal static Asn1Object CreatePrimitiveDerObject(int tagNo, DefiniteLengthInp
421430
return Asn1OctetString.CreatePrimitive(bytes);
422431
case Asn1Tags.PrintableString:
423432
return DerPrintableString.CreatePrimitive(bytes);
424-
case Asn1Tags.RelativeOid:
425-
return Asn1RelativeOid.CreatePrimitive(bytes, false);
426433
case Asn1Tags.T61String:
427434
return DerT61String.CreatePrimitive(bytes);
428435
case Asn1Tags.UniversalString:

crypto/src/asn1/Asn1RelativeOid.cs

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ internal override Asn1Object FromImplicitPrimitive(DerOctetString octetString)
2222
}
2323
}
2424

25+
/// <summary>Implementation limit on the length of the contents octets for a Relative OID.</summary>
26+
/// <remarks>
27+
/// We adopt the same value used by OpenJDK for Object Identifier. In theory there is no limit on the length of
28+
/// the contents, or the number of subidentifiers, or the length of individual subidentifiers. In practice,
29+
/// supporting arbitrary lengths can lead to issues, e.g. denial-of-service attacks when attempting to convert a
30+
/// parsed value to its (decimal) string form.
31+
/// </remarks>
32+
private const int MaxContentsLength = 4096;
33+
private const int MaxIdentifierLength = MaxContentsLength * 4 - 1;
34+
2535
public static Asn1RelativeOid FromContents(byte[] contents)
2636
{
2737
if (contents == null)
@@ -68,14 +78,18 @@ public static bool TryFromID(string identifier, out Asn1RelativeOid oid)
6878
{
6979
if (identifier == null)
7080
throw new ArgumentNullException(nameof(identifier));
71-
if (!IsValidIdentifier(identifier, 0))
81+
if (identifier.Length <= MaxIdentifierLength && IsValidIdentifier(identifier, from: 0))
7282
{
73-
oid = default;
74-
return false;
83+
byte[] contents = ParseIdentifier(identifier);
84+
if (contents.Length <= MaxContentsLength)
85+
{
86+
oid = new Asn1RelativeOid(contents, identifier);
87+
return true;
88+
}
7589
}
7690

77-
oid = new Asn1RelativeOid(ParseIdentifier(identifier), identifier);
78-
return true;
91+
oid = default;
92+
return false;
7993
}
8094

8195
private const long LongLimit = (long.MaxValue >> 7) - 0x7F;
@@ -85,31 +99,13 @@ public static bool TryFromID(string identifier, out Asn1RelativeOid oid)
8599

86100
public Asn1RelativeOid(string identifier)
87101
{
88-
if (identifier == null)
89-
throw new ArgumentNullException("identifier");
90-
if (!IsValidIdentifier(identifier, 0))
91-
throw new FormatException("string " + identifier + " not a relative OID");
92-
93-
m_contents = ParseIdentifier(identifier);
94-
m_identifier = identifier;
95-
}
96-
97-
private Asn1RelativeOid(Asn1RelativeOid oid, string branchID)
98-
{
99-
if (!IsValidIdentifier(branchID, 0))
100-
throw new FormatException("string " + branchID + " not a valid relative OID branch");
101-
102-
m_contents = Arrays.Concatenate(oid.m_contents, ParseIdentifier(branchID));
103-
m_identifier = oid.GetID() + "." + branchID;
104-
}
102+
CheckIdentifier(identifier);
105103

106-
private Asn1RelativeOid(byte[] contents, bool clone)
107-
{
108-
if (!IsValidContents(contents))
109-
throw new ArgumentException("invalid relative OID contents", nameof(contents));
104+
byte[] contents = ParseIdentifier(identifier);
105+
CheckContentsLength(contents.Length);
110106

111-
m_contents = clone ? Arrays.Clone(contents) : contents;
112-
m_identifier = null;
107+
m_contents = contents;
108+
m_identifier = identifier;
113109
}
114110

115111
private Asn1RelativeOid(byte[] contents, string identifier)
@@ -120,7 +116,14 @@ private Asn1RelativeOid(byte[] contents, string identifier)
120116

121117
public virtual Asn1RelativeOid Branch(string branchID)
122118
{
123-
return new Asn1RelativeOid(this, branchID);
119+
CheckIdentifier(branchID);
120+
121+
byte[] branchContents = ParseIdentifier(branchID);
122+
CheckContentsLength(m_contents.Length + branchContents.Length);
123+
124+
return new Asn1RelativeOid(
125+
contents: Arrays.Concatenate(m_contents, branchContents),
126+
identifier: GetID() + "." + branchID);
124127
}
125128

126129
public string GetID()
@@ -165,9 +168,30 @@ internal sealed override DerEncoding GetEncodingDerImplicit(int tagClass, int ta
165168
return new PrimitiveDerEncoding(tagClass, tagNo, m_contents);
166169
}
167170

171+
internal static void CheckContentsLength(int contentsLength)
172+
{
173+
if (contentsLength > MaxContentsLength)
174+
throw new ArgumentException("exceeded relative OID contents length limit");
175+
}
176+
177+
internal static void CheckIdentifier(string identifier)
178+
{
179+
if (identifier == null)
180+
throw new ArgumentNullException(nameof(identifier));
181+
if (identifier.Length > MaxIdentifierLength)
182+
throw new ArgumentException("exceeded relative OID contents length limit");
183+
if (!IsValidIdentifier(identifier, from: 0))
184+
throw new FormatException("string " + identifier + " not a valid relative OID");
185+
}
186+
168187
internal static Asn1RelativeOid CreatePrimitive(byte[] contents, bool clone)
169188
{
170-
return new Asn1RelativeOid(contents, clone);
189+
CheckContentsLength(contents.Length);
190+
191+
if (!IsValidContents(contents))
192+
throw new ArgumentException("invalid relative OID contents", nameof(contents));
193+
194+
return new Asn1RelativeOid(clone ? Arrays.Clone(contents) : contents, identifier: null);
171195
}
172196

173197
internal static bool IsValidContents(byte[] contents)

crypto/src/asn1/DerObjectIdentifier.cs

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ internal override Asn1Object FromImplicitPrimitive(DerOctetString octetString)
2323
}
2424
}
2525

26+
/// <summary>Implementation limit on the length of the contents octets for an Object Identifier.</summary>
27+
/// <remarks>
28+
/// We adopt the same value used by OpenJDK. In theory there is no limit on the length of the contents, or the
29+
/// number of subidentifiers, or the length of individual subidentifiers. In practice, supporting arbitrary
30+
/// lengths can lead to issues, e.g. denial-of-service attacks when attempting to convert a parsed value to its
31+
/// (decimal) string form.
32+
/// </remarks>
33+
private const int MaxContentsLength = 4096;
34+
private const int MaxIdentifierLength = MaxContentsLength * 4 + 1;
35+
2636
public static DerObjectIdentifier FromContents(byte[] contents)
2737
{
2838
if (contents == null)
@@ -86,14 +96,18 @@ public static bool TryFromID(string identifier, out DerObjectIdentifier oid)
8696
{
8797
if (identifier == null)
8898
throw new ArgumentNullException(nameof(identifier));
89-
if (!IsValidIdentifier(identifier))
99+
if (identifier.Length <= MaxIdentifierLength && IsValidIdentifier(identifier))
90100
{
91-
oid = default;
92-
return false;
101+
byte[] contents = ParseIdentifier(identifier);
102+
if (contents.Length <= MaxContentsLength)
103+
{
104+
oid = new DerObjectIdentifier(contents, identifier);
105+
return true;
106+
}
93107
}
94108

95-
oid = new DerObjectIdentifier(ParseIdentifier(identifier), identifier);
96-
return true;
109+
oid = default;
110+
return false;
97111
}
98112

99113
private const long LongLimit = (long.MaxValue >> 7) - 0x7F;
@@ -105,22 +119,13 @@ public static bool TryFromID(string identifier, out DerObjectIdentifier oid)
105119

106120
public DerObjectIdentifier(string identifier)
107121
{
108-
if (identifier == null)
109-
throw new ArgumentNullException("identifier");
110-
if (!IsValidIdentifier(identifier))
111-
throw new FormatException("string " + identifier + " not an OID");
122+
CheckIdentifier(identifier);
112123

113-
m_contents = ParseIdentifier(identifier);
114-
m_identifier = identifier;
115-
}
124+
byte[] contents = ParseIdentifier(identifier);
125+
CheckContentsLength(contents.Length);
116126

117-
private DerObjectIdentifier(byte[] contents, bool clone)
118-
{
119-
if (!Asn1RelativeOid.IsValidContents(contents))
120-
throw new ArgumentException("invalid OID contents", nameof(contents));
121-
122-
m_contents = clone ? Arrays.Clone(contents) : contents;
123-
m_identifier = null;
127+
m_contents = contents;
128+
m_identifier = identifier;
124129
}
125130

126131
private DerObjectIdentifier(byte[] contents, string identifier)
@@ -131,11 +136,13 @@ private DerObjectIdentifier(byte[] contents, string identifier)
131136

132137
public virtual DerObjectIdentifier Branch(string branchID)
133138
{
134-
if (!Asn1RelativeOid.IsValidIdentifier(branchID, 0))
135-
throw new FormatException("string " + branchID + " not a valid OID branch");
139+
Asn1RelativeOid.CheckIdentifier(branchID);
140+
141+
byte[] branchContents = Asn1RelativeOid.ParseIdentifier(branchID);
142+
CheckContentsLength(m_contents.Length + branchContents.Length);
136143

137144
return new DerObjectIdentifier(
138-
contents: Arrays.Concatenate(m_contents, Asn1RelativeOid.ParseIdentifier(branchID)),
145+
contents: Arrays.Concatenate(m_contents, branchContents),
139146
identifier: GetID() + "." + branchID);
140147
}
141148

@@ -195,9 +202,27 @@ internal sealed override DerEncoding GetEncodingDerImplicit(int tagClass, int ta
195202
return new PrimitiveDerEncoding(tagClass, tagNo, m_contents);
196203
}
197204

205+
internal static void CheckContentsLength(int contentsLength)
206+
{
207+
if (contentsLength > MaxContentsLength)
208+
throw new ArgumentException("exceeded OID contents length limit");
209+
}
210+
211+
internal static void CheckIdentifier(string identifier)
212+
{
213+
if (identifier == null)
214+
throw new ArgumentNullException(nameof(identifier));
215+
if (identifier.Length > MaxIdentifierLength)
216+
throw new ArgumentException("exceeded OID contents length limit");
217+
if (!IsValidIdentifier(identifier))
218+
throw new FormatException("string " + identifier + " not a valid OID");
219+
}
220+
198221
internal static DerObjectIdentifier CreatePrimitive(byte[] contents, bool clone)
199222
{
200-
int index = Arrays.GetHashCode(contents);
223+
CheckContentsLength(contents.Length);
224+
225+
uint index = (uint)Arrays.GetHashCode(contents);
201226

202227
index ^= index >> 20;
203228
index ^= index >> 10;
@@ -207,7 +232,10 @@ internal static DerObjectIdentifier CreatePrimitive(byte[] contents, bool clone)
207232
if (originalEntry != null && Arrays.AreEqual(contents, originalEntry.m_contents))
208233
return originalEntry;
209234

210-
var newEntry = new DerObjectIdentifier(contents, clone);
235+
if (!Asn1RelativeOid.IsValidContents(contents))
236+
throw new ArgumentException("invalid OID contents", nameof(contents));
237+
238+
var newEntry = new DerObjectIdentifier(clone ? Arrays.Clone(contents) : contents, identifier: null);
211239

212240
var exchangedEntry = Interlocked.CompareExchange(ref Cache[index], newEntry, originalEntry);
213241
if (exchangedEntry != originalEntry)
@@ -228,7 +256,7 @@ private static bool IsValidIdentifier(string identifier)
228256
if (first < '0' || first > '2')
229257
return false;
230258

231-
if (!Asn1RelativeOid.IsValidIdentifier(identifier, 2))
259+
if (!Asn1RelativeOid.IsValidIdentifier(identifier, from: 2))
232260
return false;
233261

234262
if (first == '2')

0 commit comments

Comments
 (0)