Skip to content

Commit fb63f4d

Browse files
committed
Support parsing of nested objects with indefinite lengths
1 parent 59e0602 commit fb63f4d

File tree

3 files changed

+154
-14
lines changed

3 files changed

+154
-14
lines changed

lib/asn1/asn1_parser.dart

+9-4
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,14 @@ class ASN1Parser {
5555
// Get the length of the value bytes for the current object
5656
var length = ASN1Utils.decodeLength(bytes!.sublist(_position));
5757

58-
var valueStartPosition =
59-
ASN1Utils.calculateValueStartPosition(bytes!.sublist(_position));
60-
if (_position < length + valueStartPosition) {
58+
var valueStartPosition = ASN1Utils.calculateValueStartPosition(bytes!.sublist(_position));
59+
60+
var isIndefiniteLength = false;
61+
62+
if (length == -1) {
63+
length = ASN1Utils.calculateIndefiniteLength(bytes!, _position) + 2;
64+
isIndefiniteLength = true;
65+
} else if (_position < length + valueStartPosition) {
6166
length = length + valueStartPosition;
6267
} else {
6368
length = bytes!.length - _position;
@@ -83,7 +88,7 @@ class ASN1Parser {
8388
}
8489

8590
// Update the position
86-
_position = _position + obj.totalEncodedByteLength;
91+
_position = _position + obj.totalEncodedByteLength + (isIndefiniteLength ? 2 : 0);
8792
return obj;
8893
}
8994

lib/asn1/asn1_utils.dart

+42-10
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,44 @@ class ASN1Utils {
119119
return false;
120120
}
121121

122-
static Uint8List getBytesFromPEMString(String pem,
123-
{bool checkHeader = true}) {
122+
///
123+
/// Calculates the indefinite length of the ASN1 object.
124+
/// Throws an [ArgumentError] if the end of content octets is not found.
125+
///
126+
static int calculateIndefiniteLength(Uint8List bytes, int startPosition) {
127+
var currentPosition = startPosition;
128+
var indefiniteLengthObjects = 0;
129+
while (currentPosition < bytes.length - 1) {
130+
if (bytes[currentPosition] == 0x00 && bytes[currentPosition + 1] == 0x00) {
131+
indefiniteLengthObjects--;
132+
if (indefiniteLengthObjects == 0) {
133+
return currentPosition - startPosition;
134+
}
135+
currentPosition += 2;
136+
} else {
137+
final nextLength = ASN1Utils.decodeLength(bytes.sublist(currentPosition));
138+
final valueStartPosition =
139+
ASN1Utils.calculateValueStartPosition(bytes.sublist(currentPosition));
140+
if (nextLength == 0) {
141+
throw ArgumentError('Invalid length of zero.');
142+
}
143+
if (valueStartPosition <= 0) {
144+
throw ArgumentError('Invalid value start position: $valueStartPosition');
145+
}
146+
147+
if (nextLength == -1) {
148+
indefiniteLengthObjects++;
149+
currentPosition += valueStartPosition;
150+
} else {
151+
currentPosition += valueStartPosition + nextLength;
152+
}
153+
}
154+
}
155+
156+
throw ArgumentError('End of content octets not found');
157+
}
158+
159+
static Uint8List getBytesFromPEMString(String pem, {bool checkHeader = true}) {
124160
var lines = LineSplitter.split(pem)
125161
.map((line) => line.trim())
126162
.where((line) => line.isNotEmpty)
@@ -141,8 +177,7 @@ class ASN1Utils {
141177
return Uint8List.fromList(base64Decode(base64));
142178
}
143179

144-
static ECPrivateKey ecPrivateKeyFromDerBytes(Uint8List bytes,
145-
{bool pkcs8 = false}) {
180+
static ECPrivateKey ecPrivateKeyFromDerBytes(Uint8List bytes, {bool pkcs8 = false}) {
146181
var asn1Parser = ASN1Parser(bytes);
147182
var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
148183
var curveName;
@@ -160,23 +195,20 @@ class ASN1Utils {
160195
var octetString = topLevelSeq.elements!.elementAt(2) as ASN1OctetString;
161196
asn1Parser = ASN1Parser(octetString.valueBytes);
162197
var octetStringSeq = asn1Parser.nextObject() as ASN1Sequence;
163-
var octetStringKeyData =
164-
octetStringSeq.elements!.elementAt(1) as ASN1OctetString;
198+
var octetStringKeyData = octetStringSeq.elements!.elementAt(1) as ASN1OctetString;
165199

166200
x = octetStringKeyData.valueBytes!;
167201
} else {
168202
// Parse the SEC1 format
169-
var privateKeyAsOctetString =
170-
topLevelSeq.elements!.elementAt(1) as ASN1OctetString;
203+
var privateKeyAsOctetString = topLevelSeq.elements!.elementAt(1) as ASN1OctetString;
171204
var choice = topLevelSeq.elements!.elementAt(2);
172205
var s = ASN1Sequence();
173206
var parser = ASN1Parser(choice.valueBytes);
174207
while (parser.hasNext()) {
175208
s.add(parser.nextObject());
176209
}
177210
var curveNameOi = s.elements!.elementAt(0) as ASN1ObjectIdentifier;
178-
var data = ObjectIdentifiers.getIdentifierByIdentifier(
179-
curveNameOi.objectIdentifierAsString);
211+
var data = ObjectIdentifiers.getIdentifierByIdentifier(curveNameOi.objectIdentifierAsString);
180212
if (data != null) {
181213
curveName = data['readableName'];
182214
}

test/asn1/primitives/asn1_sequence_test.dart

+103
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:typed_data';
22

33
import 'package:pointycastle/asn1/primitives/asn1_ia5_string.dart';
4+
import 'package:pointycastle/asn1/primitives/asn1_integer.dart';
45
import 'package:pointycastle/asn1/primitives/asn1_null.dart';
56
import 'package:pointycastle/asn1/primitives/asn1_object_identifier.dart';
67
import 'package:pointycastle/asn1/primitives/asn1_sequence.dart';
@@ -59,6 +60,108 @@ void main() {
5960
expect(asn1Object.elements!.elementAt(1) is ASN1Null, true);
6061
});
6162

63+
test('Test named constructor fromBytes with nested indefinite length', () {
64+
/*
65+
SEQUENCE (3 elem, indefinite length)
66+
INTEGER 1
67+
SEQUENCE (1 elem, indefinite length)
68+
OBJECT IDENTIFIER 1.2.840.113549.1.7.1 data (PKCS #7)
69+
INTEGER 1
70+
*/
71+
var bytes = Uint8List.fromList([
72+
0x30,
73+
0x80,
74+
0x02,
75+
0x01,
76+
0x01,
77+
0x30,
78+
0x80,
79+
0x06,
80+
0x09,
81+
0x2A,
82+
0x86,
83+
0x48,
84+
0x86,
85+
0xF7,
86+
0x0D,
87+
0x01,
88+
0x07,
89+
0x01,
90+
0x00,
91+
0x00,
92+
0x02,
93+
0x01,
94+
0x01,
95+
0x00,
96+
0x00
97+
]);
98+
99+
var valueBytes = Uint8List.fromList([
100+
0x02,
101+
0x01,
102+
0x01,
103+
0x30,
104+
0x80,
105+
0x06,
106+
0x09,
107+
0x2A,
108+
0x86,
109+
0x48,
110+
0x86,
111+
0xF7,
112+
0x0D,
113+
0x01,
114+
0x07,
115+
0x01,
116+
0x00,
117+
0x00,
118+
0x02,
119+
0x01,
120+
0x01,
121+
]);
122+
123+
var innerSequenceBytes = Uint8List.fromList([
124+
0x30,
125+
0x80,
126+
0x06,
127+
0x09,
128+
0x2A,
129+
0x86,
130+
0x48,
131+
0x86,
132+
0xF7,
133+
0x0D,
134+
0x01,
135+
0x07,
136+
0x01,
137+
0x00,
138+
0x00
139+
]);
140+
141+
var innerSequenceValueBytes = Uint8List.fromList(
142+
[0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01]);
143+
144+
var asn1Object = ASN1Sequence.fromBytes(bytes);
145+
expect(asn1Object.tag, 48);
146+
expect(asn1Object.isConstructed, true);
147+
expect(asn1Object.encodedBytes, bytes);
148+
expect(asn1Object.valueByteLength, 21);
149+
expect(asn1Object.valueStartPosition, 2);
150+
expect(asn1Object.valueBytes, valueBytes);
151+
expect(asn1Object.elements!.length, 3);
152+
expect(asn1Object.elements!.elementAt(0) is ASN1Integer, true);
153+
expect(asn1Object.elements!.elementAt(1) is ASN1Sequence, true);
154+
expect(asn1Object.elements!.elementAt(2) is ASN1Integer, true);
155+
156+
final innerSequence = asn1Object.elements!.elementAt(1) as ASN1Sequence;
157+
expect(innerSequence.tag, 48);
158+
expect(innerSequence.isConstructed, true);
159+
expect(innerSequence.encodedBytes, innerSequenceBytes);
160+
expect(innerSequence.valueByteLength, 11);
161+
expect(innerSequence.valueStartPosition, 2);
162+
expect(innerSequence.valueBytes, innerSequenceValueBytes);
163+
});
164+
62165
test('Test encode', () {
63166
// Test encoding with zero elements given
64167
var asn1Object = ASN1Sequence(elements: []);

0 commit comments

Comments
 (0)