This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/9.0.x by this push:
new d66d5b4c79 Review "Fix DER INTEGER encoding in
Asn1Writer.writeInteger()"
d66d5b4c79 is described below
commit d66d5b4c7990c1c8d7c7777636a13e79ff73ab17
Author: Mark Thomas <[email protected]>
AuthorDate: Mon Nov 2 16:38:29 2026 +0000
Review "Fix DER INTEGER encoding in Asn1Writer.writeInteger()"
Ensure lengths are written with the minimum number of bytes
Co-authored by: Claude / Sonnet 5.5
---
java/org/apache/tomcat/util/buf/Asn1Writer.java | 46 +++++++------
.../org/apache/tomcat/util/buf/TestAsn1Writer.java | 76 ++++++++++++++++++++++
2 files changed, 98 insertions(+), 24 deletions(-)
diff --git a/java/org/apache/tomcat/util/buf/Asn1Writer.java
b/java/org/apache/tomcat/util/buf/Asn1Writer.java
index 838bc2842b..2c9e22cff5 100644
--- a/java/org/apache/tomcat/util/buf/Asn1Writer.java
+++ b/java/org/apache/tomcat/util/buf/Asn1Writer.java
@@ -106,37 +106,35 @@ public class Asn1Writer {
*/
public static byte[] writeTag(byte tagId, byte[] data) {
int dataSize = data.length;
- // How many bytes to write the length?
- int lengthSize = 1;
- if (dataSize > 127) {
- // 1 byte we have is now used to record how many bytes we need to
- // record a length > 127
- // Result is lengthSize = 1 + number of bytes to record length
- // Note the limit is required to terminate the loop since the shift
- // distance is evaluated modulo 32. Four bytes is the most any Java
- // array length (a non-negative int) can need.
- do {
- lengthSize++;
- } while (lengthSize < 5 && (dataSize >>> ((lengthSize - 1) * 8)) >
0);
- }
- // 1 for tag + lengthSize + dataSize
- byte[] result = new byte[1 + lengthSize + dataSize];
- result[0] = tagId;
+ byte[] result;
+ int headerSize;
if (dataSize < 128) {
+ // Short form. The length is a single byte.
+ headerSize = 2;
+ result = new byte[headerSize + dataSize];
result[1] = (byte) dataSize;
} else {
- // lengthSize is 1 + number of bytes for length
- result[1] = (byte) (127 + lengthSize);
- int i = lengthSize;
- while (dataSize > 0) {
- result[i] = (byte) (dataSize & 0xFF);
- dataSize = dataSize >> 8;
- i--;
+ /*
+ * Long form. The first length byte is 0x80 plus the number of
bytes required to record the length. DER
+ * (X.690 10.1) requires the minimum number of length bytes. No
more than 4 are required for a Java array
+ * length. The limit is also required to terminate the loop since
the shift distance is evaluated modulo
+ * 32.
+ */
+ int lengthBytes = 1;
+ while (lengthBytes < 4 && (dataSize >>> (lengthBytes * 8)) != 0) {
+ lengthBytes++;
+ }
+ headerSize = 2 + lengthBytes;
+ result = new byte[headerSize + dataSize];
+ result[1] = (byte) (0x80 + lengthBytes);
+ for (int i = 0; i < lengthBytes; i++) {
+ result[2 + i] = (byte) (dataSize >>> (8 * (lengthBytes - 1 -
i)));
}
}
+ result[0] = tagId;
- System.arraycopy(data, 0, result, 1 + lengthSize, data.length);
+ System.arraycopy(data, 0, result, headerSize, data.length);
return result;
}
diff --git a/test/org/apache/tomcat/util/buf/TestAsn1Writer.java
b/test/org/apache/tomcat/util/buf/TestAsn1Writer.java
index 517ce39462..95de40dff2 100644
--- a/test/org/apache/tomcat/util/buf/TestAsn1Writer.java
+++ b/test/org/apache/tomcat/util/buf/TestAsn1Writer.java
@@ -131,6 +131,82 @@ public class TestAsn1Writer {
}
+ // DER (X.690 10.1) requires the length to be encoded using the minimum
number of octets: the short form up to
+ // 127 and then the fewest length octets that can hold the value.
+ @Test
+ public void testWriteTagShortFormLengths() {
+ assertLengthEncoding(0, new byte[] { 0x04, 0x00 });
+ assertLengthEncoding(1, new byte[] { 0x04, 0x01 });
+ assertLengthEncoding(127, new byte[] { 0x04, 0x7F });
+ }
+
+
+ @Test
+ public void testWriteTagOneLengthOctet() {
+ assertLengthEncoding(128, new byte[] { 0x04, (byte) 0x81, (byte) 0x80
});
+ assertLengthEncoding(200, new byte[] { 0x04, (byte) 0x81, (byte) 0xC8
});
+ assertLengthEncoding(255, new byte[] { 0x04, (byte) 0x81, (byte) 0xFF
});
+ }
+
+
+ @Test
+ public void testWriteTagTwoLengthOctets() {
+ assertLengthEncoding(256, new byte[] { 0x04, (byte) 0x82, 0x01, 0x00
});
+ assertLengthEncoding(300, new byte[] { 0x04, (byte) 0x82, 0x01, 0x2C
});
+ assertLengthEncoding(0xFFFF, new byte[] { 0x04, (byte) 0x82, (byte)
0xFF, (byte) 0xFF });
+ }
+
+
+ @Test
+ public void testWriteTagThreeLengthOctets() {
+ assertLengthEncoding(0x10000, new byte[] { 0x04, (byte) 0x83, 0x01,
0x00, 0x00 });
+ assertLengthEncoding(0xFFFFFF, new byte[] { 0x04, (byte) 0x83, (byte)
0xFF, (byte) 0xFF, (byte) 0xFF });
+ }
+
+
+ @Test
+ public void testWriteTagLengthRoundTrip() {
+ // Every length around the short form, one octet and two octet
boundaries and the transition to three octets.
+ // Check the size of the length field is the minimum and that the
parser reads back the original content.
+ for (int size = 0; size <= 0x10100; size++) {
+ byte[] data = new byte[size];
+ Arrays.fill(data, (byte) 0x5A);
+ byte[] encoded = Asn1Writer.writeOctetString(data);
+
+ int lengthOctets;
+ if (size < 128) {
+ lengthOctets = 1;
+ } else {
+ // 1 octet for the number of length octets plus the unsigned
minimal length octets
+ lengthOctets = 1 + (BigInteger.valueOf(size).bitLength() + 7)
/ 8;
+ }
+ Assert.assertEquals("Unexpected total length for " + size, 1 +
lengthOctets + size, encoded.length);
+
+ Assert.assertArrayEquals("Round-trip failed for " + size, data,
+ new Asn1Parser(encoded).parseOctetString());
+ }
+ }
+
+
+ @Test
+ public void testWriteSequenceLengthEncoding() {
+ // A SEQUENCE of 200 bytes of content must use a single length octet
+ byte[] encoded = Asn1Writer.writeSequence(new byte[200]);
+ Assert.assertEquals(0x30, encoded[0] & 0xFF);
+ Assert.assertEquals(0x81, encoded[1] & 0xFF);
+ Assert.assertEquals(200, encoded[2] & 0xFF);
+ Assert.assertEquals(3 + 200, encoded.length);
+ }
+
+
+ private static void assertLengthEncoding(int size, byte[] expectedHeader) {
+ byte[] encoded = Asn1Writer.writeOctetString(new byte[size]);
+ Assert.assertEquals("Unexpected total length for " + size,
expectedHeader.length + size, encoded.length);
+ Assert.assertArrayEquals("Unexpected header for " + size,
expectedHeader,
+ Arrays.copyOfRange(encoded, 0, expectedHeader.length));
+ }
+
+
private static void assertIntegerEncoding(int value, byte[] expected) {
Assert.assertArrayEquals("Unexpected encoding for " + value, expected,
Asn1Writer.writeInteger(value));
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]