import 'dart:typed_data'; import 'package:pointycastle/asn1/asn1_parser.dart'; import 'package:pointycastle/asn1/asn1_encoding_rule.dart'; import 'package:pointycastle/asn1/asn1_utils.dart'; /// /// Base model for all ASN1Objects /// class ASN1Object { /// /// The BER tag representing this object. /// /// For a list of all supported BER tags take a look in the **Asn1Tags** class. /// int? tag; /// /// The encoded bytes. /// Uint8List? encodedBytes; /// /// The value bytes. /// Uint8List? valueBytes; /// /// The index where the value bytes start. This is the position after the tag + length bytes. /// /// The default value for this field is 2. If the length byte is larger than **127**, the value of this field will increase depending on the length bytes. /// int valueStartPosition = 2; /// /// Length of the encoded value bytes. /// int? valueByteLength; /// /// Describes if this ASN1 Object is constructed. /// /// The object is marked as constructed if bit 6 of the [tag] field has value **1** /// bool? isConstructed; int dumpIndent = 2; ASN1Object({this.tag}) { if (tag != null) { isConstructed = ASN1Utils.isConstructed(tag!); } } /// /// Creates a new ASN1Object from the given [encodedBytes]. /// /// The first byte will be used as the [tag].The field [valueStartPosition] and [valueByteLength] will be calculated on the given [encodedBytes]. /// ASN1Object.fromBytes(this.encodedBytes) { tag = encodedBytes![0]; isConstructed = ASN1Utils.isConstructed(tag!); valueByteLength = ASN1Utils.decodeLength(encodedBytes!); valueStartPosition = ASN1Utils.calculateValueStartPosition(encodedBytes!); if (valueByteLength == -1) { // Indefinite length, check the last to bytes if (ASN1Utils.hasIndefiniteLengthEnding(encodedBytes!)) { valueByteLength = encodedBytes!.length - 4; } } valueBytes = Uint8List.view(encodedBytes!.buffer, valueStartPosition + encodedBytes!.offsetInBytes, valueByteLength); } /// /// Encode the object to their byte representation. /// /// [encodingRule] defines if the [valueByteLength] should be encoded as indefinite length (0x80) or fixed length with short/long form. /// The default is [ASN1EncodingRule.ENCODING_DER] which will automatically decode in definite length with short form. /// /// **Important note**: Subclasses need to override this method and may call this method. If this method is called by a subclass, the subclass has to set the [valueBytes] before calling super.encode(). /// Uint8List encode( {ASN1EncodingRule encodingRule = ASN1EncodingRule.ENCODING_DER}) { if (encodedBytes == null) { // Encode the length Uint8List lengthAsBytes; valueByteLength ??= valueBytes!.length; // Check if we have indefinite length or fixed length (short or longform) if (encodingRule == ASN1EncodingRule.ENCODING_BER_CONSTRUCTED_INDEFINITE_LENGTH) { // Set length to 0x80 lengthAsBytes = Uint8List.fromList([0x80]); // Add 2 to the valueByteLength to handle the 0x00, 0x00 at the end //valueByteLength = valueByteLength + 2; } else { lengthAsBytes = ASN1Utils.encodeLength(valueByteLength!, longform: encodingRule == ASN1EncodingRule.ENCODING_BER_LONG_LENGTH_FORM); } // Create the Uint8List with the calculated length encodedBytes = Uint8List(1 + lengthAsBytes.length + valueByteLength!); // Set the tag encodedBytes![0] = tag!; // Set the length bytes encodedBytes!.setRange(1, 1 + lengthAsBytes.length, lengthAsBytes, 0); // Set the value bytes encodedBytes!.setRange( 1 + lengthAsBytes.length, encodedBytes!.length, valueBytes!, 0); } return encodedBytes!; } /// /// The total length of this object, including its value bytes, the encoded tag and length bytes. /// int get totalEncodedByteLength => valueStartPosition + valueByteLength!; /// /// Creates a readable dump from the current ASN1Object. /// /// **Important note**: Subclasses need to override this method. If the ASN1Object is constructed and has child elements, dump() has to be called for each child element. /// String dump({int spaces = 0}) { var sb = StringBuffer(); for (var i = 0; i < spaces; i++) { sb.write(' '); } if (tag == 0xA0 || tag == 0xA3) { sb.write('[$tag]'); var parser = ASN1Parser(valueBytes); if (parser.hasNext()) { var next = parser.nextObject(); var dump = next.dump(spaces: spaces + dumpIndent); sb.write('\n$dump'); } else { sb.write(' (0 elem)'); } } return sb.toString(); } }