blob: c6152c4cccecac857aa30494824e6e0b9dffd858 [file] [log] [blame] [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:convert';
import 'dart:typed_data';
import 'asn1.dart';
import 'rsa.dart';
/// Decode a [RSAPrivateKey] from the string content of a PEM file.
///
/// A PEM file can be extracted from a .p12 cryptostore with
/// $ openssl pkcs12 -nocerts -nodes -passin pass:notasecret \
/// -in *-privatekey.p12 -out *-privatekey.pem
RSAPrivateKey keyFromString(String pemFileString) {
final bytes = _getBytesFromPEMString(pemFileString);
return _extractRSAKeyFromDERBytes(bytes);
}
/// Helper function for decoding the base64 in [pemString].
Uint8List _getBytesFromPEMString(String pemString) {
final lines = LineSplitter.split(
pemString,
).map((line) => line.trim()).where((line) => line.isNotEmpty).toList();
if (lines.length < 2 ||
!lines.first.startsWith('-----BEGIN') ||
!lines.last.startsWith('-----END')) {
throw const FormatException(
'The given string does not have the correct '
'begin/end markers expected in a PEM file.',
);
}
final base64 = lines.sublist(1, lines.length - 1).join();
return Uint8List.fromList(base64Decode(base64));
}
/// Helper to decode the ASN.1/DER bytes in [bytes] into an [RSAPrivateKey].
RSAPrivateKey _extractRSAKeyFromDERBytes(Uint8List bytes) {
// We recognize two formats:
// Real format:
//
// PrivateKey := seq[int/version=0, int/n, int/e, int/d, int/p,
// int/q, int/dmp1, int/dmq1, int/coeff]
//
// Or the above `PrivateKey` embeddded inside another ASN object:
// Encapsulated := seq[int/version=0,
// seq[obj-id/rsa-id, null-obj],
// octet-string/PrivateKey]
//
RSAPrivateKey privateKeyFromSequence(ASN1Sequence asnSequence) {
final objects = asnSequence.objects;
final asnIntegers = objects.take(9).map((o) => o as ASN1Integer).toList();
final version = asnIntegers.first;
if (version.integer != BigInt.zero) {
throw FormatException('Expected version 0, got: ${version.integer}.');
}
final key = RSAPrivateKey(
asnIntegers[1].integer,
asnIntegers[2].integer,
asnIntegers[3].integer,
asnIntegers[4].integer,
asnIntegers[5].integer,
asnIntegers[6].integer,
asnIntegers[7].integer,
asnIntegers[8].integer,
);
final bitLength = key.bitLength;
if (bitLength < 1024) {
throw FormatException(
'The RSA modulus has a bit length of $bitLength. '
'Only 1024 or more bits are supported.',
);
}
return key;
}
try {
final asn = ASN1Parser.parseSequence(bytes);
final objects = asn.objects;
if (objects.length == 3 && objects[2] is ASN1OctetString) {
final string = objects[2] as ASN1OctetString;
final algId = objects[1];
if (algId is ASN1Sequence && algId.objects.isNotEmpty) {
final oid = algId.objects[0];
if (oid is ASN1ObjectIdentifier) {
final validOid = [
0x2a,
0x86,
0x48,
0x86,
0xf7,
0x0d,
0x01,
0x01,
0x01,
];
if (oid.bytes.length != validOid.length) {
throw const FormatException('Unexpected Algorithm Identifier OID.');
}
for (var i = 0; i < validOid.length; i++) {
if (oid.bytes[i] != validOid[i]) {
throw const FormatException(
'Unexpected Algorithm Identifier OID.',
);
}
}
}
}
return privateKeyFromSequence(
ASN1Parser.parseSequence(string.bytes as Uint8List),
);
}
return privateKeyFromSequence(asn);
} on FormatException {
rethrow;
} catch (error) {
throw FormatException(
'Error while extracting private key from DER bytes: $error',
);
}
}