blob: 6169a3e6bda3492fb8db7b6dcb8f8685ed9db787 [file] [edit]
// Copyright 2021 Google LLC
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'dart:typed_data';
import 'package:meta/meta.dart';
import 'rsa.dart' as rsa;
// ignore: avoid_classes_with_only_static_members
abstract final class ASN1Parser {
static const integerTag = 0x02;
static const octetStringTag = 0x04;
static const nullTag = 0x05;
static const objectIdTag = 0x06;
static const sequenceTag = 0x30;
static ASN1Sequence parseSequence(Uint8List bytes) {
final obj = parseObject(bytes);
return obj as ASN1Sequence;
}
@visibleForTesting
static ASN1Object parseObject(Uint8List bytes) {
Never invalidFormat(String msg) {
throw FormatException('Invalid DER encoding: $msg');
}
final data = ByteData.view(bytes.buffer);
var offset = 0;
final end = bytes.length;
void checkNBytesAvailable(int n) {
if ((offset + n) > end) {
invalidFormat('Tried to read more bytes than available.');
}
}
List<int> readBytes(int n) {
checkNBytesAvailable(n);
final integerBytes = bytes.sublist(offset, offset + n);
offset += n;
return integerBytes;
}
int readEncodedLength() {
checkNBytesAvailable(1);
final lengthByte = data.getUint8(offset++);
// Short length encoding form: This byte is the length itself.
if (lengthByte < 0x80) {
return lengthByte;
}
// Long length encoding form:
// This byte has in bits 0..6 the number of bytes following which encode
// the length.
var countLengthBytes = lengthByte & 0x7f;
checkNBytesAvailable(countLengthBytes);
var length = 0;
while (countLengthBytes > 0) {
length = (length << 8) | data.getUint8(offset++);
countLengthBytes--;
}
return length;
}
void readNullBytes() {
checkNBytesAvailable(1);
final nullByte = data.getUint8(offset++);
if (nullByte != 0x00) {
invalidFormat('Null byte expect, but was: $nullByte.');
}
}
ASN1Object decodeObject(int recursion) {
if (recursion > 128) {
throw const FormatException(
'Recursion limit for ASN1 messages exceeded.',
);
}
checkNBytesAvailable(1);
final tag = bytes[offset++];
switch (tag) {
case integerTag:
final size = readEncodedLength();
return ASN1Integer._(rsa.bytes2BigInt(readBytes(size)));
case octetStringTag:
final size = readEncodedLength();
return ASN1OctetString._(readBytes(size));
case nullTag:
readNullBytes();
return const ASN1Null._();
case objectIdTag:
final size = readEncodedLength();
return ASN1ObjectIdentifier._(readBytes(size));
case sequenceTag:
final lengthInBytes = readEncodedLength();
if ((offset + lengthInBytes) > end) {
invalidFormat('Tried to read more bytes than available.');
}
final endOfSequence = offset + lengthInBytes;
final objects = <ASN1Object>[];
while (offset < endOfSequence) {
objects.add(decodeObject(recursion + 1));
}
return ASN1Sequence._(objects);
default:
invalidFormat(
'Unexpected tag $tag at offset ${offset - 1} (end: $end).',
);
}
}
final obj = decodeObject(0);
if (offset != bytes.length) {
throw const FormatException('More bytes than expected in ASN1 encoding.');
}
return obj;
}
}
final class ASN1Object {
const ASN1Object._();
}
final class ASN1Sequence extends ASN1Object {
final List<ASN1Object> objects;
ASN1Sequence._(this.objects) : super._();
}
final class ASN1Integer extends ASN1Object {
final BigInt integer;
ASN1Integer._(this.integer) : super._();
}
final class ASN1OctetString extends ASN1Object {
final List<int> bytes;
ASN1OctetString._(this.bytes) : super._();
}
final class ASN1ObjectIdentifier extends ASN1Object {
final List<int> bytes;
ASN1ObjectIdentifier._(this.bytes) : super._();
}
final class ASN1Null extends ASN1Object {
const ASN1Null._() : super._();
}