Skip to content

Commit

Permalink
Merge pull request #215 from hig-dev/master
Browse files Browse the repository at this point in the history
Support parsing of nested objects with indefinite lengths
  • Loading branch information
Ephenodrom authored Jan 11, 2024
2 parents 59e0602 + ef4818e commit 4ff2c40
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 2 deletions.
11 changes: 9 additions & 2 deletions lib/asn1/asn1_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,13 @@ class ASN1Parser {

var valueStartPosition =
ASN1Utils.calculateValueStartPosition(bytes!.sublist(_position));
if (_position < length + valueStartPosition) {

var isIndefiniteLength = false;

if (length == -1) {
length = ASN1Utils.calculateIndefiniteLength(bytes!, _position) + 2;
isIndefiniteLength = true;
} else if (_position < length + valueStartPosition) {
length = length + valueStartPosition;
} else {
length = bytes!.length - _position;
Expand All @@ -83,7 +89,8 @@ class ASN1Parser {
}

// Update the position
_position = _position + obj.totalEncodedByteLength;
_position =
_position + obj.totalEncodedByteLength + (isIndefiniteLength ? 2 : 0);
return obj;
}

Expand Down
40 changes: 40 additions & 0 deletions lib/asn1/asn1_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,46 @@ class ASN1Utils {
return false;
}

///
/// Calculates the indefinite length of the ASN1 object.
/// Throws an [ArgumentError] if the end of content octets is not found.
///
static int calculateIndefiniteLength(Uint8List bytes, int startPosition) {
var currentPosition = startPosition;
var indefiniteLengthObjects = 0;
while (currentPosition < bytes.length - 1) {
if (bytes[currentPosition] == 0x00 &&
bytes[currentPosition + 1] == 0x00) {
indefiniteLengthObjects--;
if (indefiniteLengthObjects == 0) {
return currentPosition - startPosition;
}
currentPosition += 2;
} else {
final nextLength =
ASN1Utils.decodeLength(bytes.sublist(currentPosition));
final valueStartPosition = ASN1Utils.calculateValueStartPosition(
bytes.sublist(currentPosition));
if (nextLength == 0) {
throw ArgumentError('Invalid length of zero.');
}
if (valueStartPosition <= 0) {
throw ArgumentError(
'Invalid value start position: $valueStartPosition');
}

if (nextLength == -1) {
indefiniteLengthObjects++;
currentPosition += valueStartPosition;
} else {
currentPosition += valueStartPosition + nextLength;
}
}
}

throw ArgumentError('End of content octets not found');
}

static Uint8List getBytesFromPEMString(String pem,
{bool checkHeader = true}) {
var lines = LineSplitter.split(pem)
Expand Down
103 changes: 103 additions & 0 deletions test/asn1/primitives/asn1_sequence_test.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:typed_data';

import 'package:pointycastle/asn1/primitives/asn1_ia5_string.dart';
import 'package:pointycastle/asn1/primitives/asn1_integer.dart';
import 'package:pointycastle/asn1/primitives/asn1_null.dart';
import 'package:pointycastle/asn1/primitives/asn1_object_identifier.dart';
import 'package:pointycastle/asn1/primitives/asn1_sequence.dart';
Expand Down Expand Up @@ -59,6 +60,108 @@ void main() {
expect(asn1Object.elements!.elementAt(1) is ASN1Null, true);
});

test('Test named constructor fromBytes with nested indefinite length', () {
/*
SEQUENCE (3 elem, indefinite length)
INTEGER 1
SEQUENCE (1 elem, indefinite length)
OBJECT IDENTIFIER 1.2.840.113549.1.7.1 data (PKCS #7)
INTEGER 1
*/
var bytes = Uint8List.fromList([
0x30,
0x80,
0x02,
0x01,
0x01,
0x30,
0x80,
0x06,
0x09,
0x2A,
0x86,
0x48,
0x86,
0xF7,
0x0D,
0x01,
0x07,
0x01,
0x00,
0x00,
0x02,
0x01,
0x01,
0x00,
0x00
]);

var valueBytes = Uint8List.fromList([
0x02,
0x01,
0x01,
0x30,
0x80,
0x06,
0x09,
0x2A,
0x86,
0x48,
0x86,
0xF7,
0x0D,
0x01,
0x07,
0x01,
0x00,
0x00,
0x02,
0x01,
0x01,
]);

var innerSequenceBytes = Uint8List.fromList([
0x30,
0x80,
0x06,
0x09,
0x2A,
0x86,
0x48,
0x86,
0xF7,
0x0D,
0x01,
0x07,
0x01,
0x00,
0x00
]);

var innerSequenceValueBytes = Uint8List.fromList(
[0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01]);

var asn1Object = ASN1Sequence.fromBytes(bytes);
expect(asn1Object.tag, 48);
expect(asn1Object.isConstructed, true);
expect(asn1Object.encodedBytes, bytes);
expect(asn1Object.valueByteLength, 21);
expect(asn1Object.valueStartPosition, 2);
expect(asn1Object.valueBytes, valueBytes);
expect(asn1Object.elements!.length, 3);
expect(asn1Object.elements!.elementAt(0) is ASN1Integer, true);
expect(asn1Object.elements!.elementAt(1) is ASN1Sequence, true);
expect(asn1Object.elements!.elementAt(2) is ASN1Integer, true);

final innerSequence = asn1Object.elements!.elementAt(1) as ASN1Sequence;
expect(innerSequence.tag, 48);
expect(innerSequence.isConstructed, true);
expect(innerSequence.encodedBytes, innerSequenceBytes);
expect(innerSequence.valueByteLength, 11);
expect(innerSequence.valueStartPosition, 2);
expect(innerSequence.valueBytes, innerSequenceValueBytes);
});

test('Test encode', () {
// Test encoding with zero elements given
var asn1Object = ASN1Sequence(elements: []);
Expand Down

0 comments on commit 4ff2c40

Please sign in to comment.