blob: 13c15604f5991f81a1759b414ee20f4d4c928c83 [file] [log] [blame]
import 'dart:convert';
import 'dart:typed_data';
import 'package:pointycastle/asn1.dart';
import 'package:pointycastle/asn1/object_identifiers.dart';
import 'package:pointycastle/ecc/api.dart';
///
/// Utils class holding different methods to ease the handling of ANS1Objects and their byte representation.
///
class ASN1Utils {
///
/// Calculates the start position of the value bytes for the given [encodedBytes].
///
/// It will return 2 if the **length byte** is less than or equal 127 or the length calculate on the **length byte** value.
/// This will throw a [RangeError] if the given [encodedBytes] has length < 2.
///
static int calculateValueStartPosition(Uint8List encodedBytes) {
// TODO tag length can be >1
var length = encodedBytes[1];
if (length <= 0x7F) {
return 2;
} else {
return 2 + (length & 0x7F);
}
}
///
/// Calculates the length of the **value bytes** for the given [encodedBytes].
///
/// Will return **-1** if the length byte equals **0x80**. Throws an [ArgumentError] if the length could not be calculated for the given [encodedBytes].
///
static int decodeLength(Uint8List encodedBytes) {
var valueStartPosition = 2;
var length = encodedBytes[1];
if (length <= 0x7F) {
return length;
}
if (length == 0x80) {
return -1;
}
if (length > 127) {
var length = encodedBytes[1] & 0x7F;
var numLengthBytes = length;
length = 0;
for (var i = 0; i < numLengthBytes; i++) {
length <<= 8;
length |= (encodedBytes[valueStartPosition++] & 0xFF);
}
return length;
}
throw ArgumentError('Could not calculate the length from the given bytes.');
}
///
/// Encode the given [length] to byte representation.
///
static Uint8List encodeLength(int length, {bool longform = false}) {
Uint8List e;
if (length <= 127 && longform == false) {
e = Uint8List(1);
e[0] = length;
} else {
var x = Uint32List(1);
x[0] = length;
var y = Uint8List.view(x.buffer);
// Skip null bytes
var num = 3;
while (y[num] == 0) {
--num;
}
e = Uint8List(num + 2);
e[0] = 0x80 + num + 1;
for (var i = 1; i < e.length; ++i) {
e[i] = y[num--];
}
}
return e;
}
///
/// Checks if the given int [i] is constructed according to <https://www.bouncycastle.org/asn1_layman_93.txt> section 3.2.
///
/// The Identifier octets (represented by the given [i]) is marked as constructed if bit 6 has the value **1**.
///
/// Example with the IA5 String tag:
///
/// 0x36 = 0 0 1 1 0 1 1 0
///
/// 0x16 = 0 0 0 1 0 1 1 0
/// ```
/// ASN1Utils.isConstructed(0x36); // true
/// ASN1Utils.isConstructed(0x16); // false
/// ```
///
///
static bool isConstructed(int i) {
// Shift bits
var newNum = i >> (6 - 1);
// Check if bit is set to 1
return (newNum & 1) == 1;
}
static bool isASN1Tag(int i) {
return ASN1Tags.TAGS.contains(i);
}
///
/// Checks if the given [bytes] ends with 0x00, 0x00
///
static bool hasIndefiniteLengthEnding(Uint8List bytes) {
var last = bytes.elementAt(bytes.length - 1);
var lastMinus1 = bytes.elementAt(bytes.length - 2);
if (last == 0 && lastMinus1 == 0) {
return true;
}
return false;
}
static Uint8List getBytesFromPEMString(String pem,
{bool checkHeader = true}) {
var lines = LineSplitter.split(pem)
.map((line) => line.trim())
.where((line) => line.isNotEmpty)
.toList();
var base64;
if (checkHeader) {
if (lines.length < 2 ||
!lines.first.startsWith('-----BEGIN') ||
!lines.last.startsWith('-----END')) {
throw ArgumentError('The given string does not have the correct '
'begin/end markers expected in a PEM file.');
}
base64 = lines.sublist(1, lines.length - 1).join('');
} else {
base64 = lines.join('');
}
return Uint8List.fromList(base64Decode(base64));
}
static ECPrivateKey ecPrivateKeyFromDerBytes(Uint8List bytes,
{bool pkcs8 = false}) {
var asn1Parser = ASN1Parser(bytes);
var topLevelSeq = asn1Parser.nextObject() as ASN1Sequence;
var curveName;
var x;
if (pkcs8) {
// Parse the PKCS8 format
var innerSeq = topLevelSeq.elements!.elementAt(1) as ASN1Sequence;
var b2 = innerSeq.elements!.elementAt(1) as ASN1ObjectIdentifier;
var b2Data = b2.objectIdentifierAsString;
var b2Curvedata = ObjectIdentifiers.getIdentifierByIdentifier(b2Data);
if (b2Curvedata != null) {
curveName = b2Curvedata['readableName'];
}
var octetString = topLevelSeq.elements!.elementAt(2) as ASN1OctetString;
asn1Parser = ASN1Parser(octetString.valueBytes);
var octetStringSeq = asn1Parser.nextObject() as ASN1Sequence;
var octetStringKeyData =
octetStringSeq.elements!.elementAt(1) as ASN1OctetString;
x = octetStringKeyData.valueBytes!;
} else {
// Parse the SEC1 format
var privateKeyAsOctetString =
topLevelSeq.elements!.elementAt(1) as ASN1OctetString;
var choice = topLevelSeq.elements!.elementAt(2);
var s = ASN1Sequence();
var parser = ASN1Parser(choice.valueBytes);
while (parser.hasNext()) {
s.add(parser.nextObject());
}
var curveNameOi = s.elements!.elementAt(0) as ASN1ObjectIdentifier;
var data = ObjectIdentifiers.getIdentifierByIdentifier(
curveNameOi.objectIdentifierAsString);
if (data != null) {
curveName = data['readableName'];
}
x = privateKeyAsOctetString.valueBytes!;
}
return ECPrivateKey(osp2i(x), ECDomainParameters(curveName));
}
static BigInt osp2i(Iterable<int> bytes, {Endian endian = Endian.big}) {
var result = BigInt.from(0);
if (endian == Endian.little) {
bytes = bytes.toList().reversed;
}
for (var byte in bytes) {
result = result << 8;
result |= BigInt.from(byte);
}
return result;
}
}