blob: b000796a0a6450463fe533d36fe343b9b2c0d99c [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
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 ArgumentError(
'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 ArgumentError('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 && bitLength != 2048 && bitLength != 4096) {
throw ArgumentError(
'The RSA modulus has a bit length of $bitLength. '
'Only 1024, 2048 and 4096 are supported.',
);
}
return key;
}
try {
final asn = ASN1Parser.parse(bytes);
if (asn is ASN1Sequence) {
final objects = asn.objects;
if (objects.length == 3 && objects[2] is ASN1OctetString) {
final string = objects[2] as ASN1OctetString;
// Seems like the embedded form.
// TODO: Validate that rsa identifier matches!
return privateKeyFromSequence(
ASN1Parser.parse(string.bytes as Uint8List) as ASN1Sequence,
);
}
}
return privateKeyFromSequence(asn as ASN1Sequence);
} catch (error) {
throw ArgumentError(
'Error while extracting private key from DER bytes: $error',
);
}
}