diff --git a/lib/asn1/asn1_parser.dart b/lib/asn1/asn1_parser.dart index ff860fcd..55884043 100644 --- a/lib/asn1/asn1_parser.dart +++ b/lib/asn1/asn1_parser.dart @@ -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; @@ -83,7 +89,8 @@ class ASN1Parser { } // Update the position - _position = _position + obj.totalEncodedByteLength; + _position = + _position + obj.totalEncodedByteLength + (isIndefiniteLength ? 2 : 0); return obj; } diff --git a/lib/asn1/asn1_utils.dart b/lib/asn1/asn1_utils.dart index 13c15604..289f6b71 100644 --- a/lib/asn1/asn1_utils.dart +++ b/lib/asn1/asn1_utils.dart @@ -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) diff --git a/test/asn1/primitives/asn1_sequence_test.dart b/test/asn1/primitives/asn1_sequence_test.dart index 47d89d0f..8ae2eeda 100644 --- a/test/asn1/primitives/asn1_sequence_test.dart +++ b/test/asn1/primitives/asn1_sequence_test.dart @@ -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'; @@ -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: []);