parsePem function
Privacy-Enhanced Mail (PEM) is a de facto file format for storing and sending cryptographic keys, certificates, and other data.
Implementation
Identity parsePem(String text,
{StringFunction getPassword, Identity identity}) {
identity ??= Identity();
const String beginText = '-----BEGIN ',
endText = '-----END ',
termText = '-----';
int beginBegin, beginEnd, endBegin, endEnd;
if ((beginBegin = text.indexOf(beginText)) == -1) {
throw FormatException('missing $beginText');
}
if ((beginEnd = text.indexOf(termText, beginBegin + beginText.length)) ==
-1) {
throw FormatException('missing $termText');
}
if ((endBegin = text.indexOf(endText, beginEnd + termText.length)) == -1) {
throw FormatException('missing $endText');
}
if ((endEnd = text.indexOf(termText, endBegin + endText.length)) == -1) {
throw FormatException('missing $termText');
}
String type = text.substring(beginBegin + beginText.length, beginEnd);
if (type != text.substring(endBegin + endText.length, endEnd)) {
throw FormatException('type disagreement: $type');
}
int start = beginEnd + termText.length, end = endBegin;
if (start < text.length && text[start] == '\r') start++;
if (start < text.length && text[start] == '\n') start++;
String headersEndText = '\n\n', procType;
int headersStart = -1, headersEnd = text.indexOf(headersEndText, start);
if (headersEnd == -1 || headersEnd >= end) {
headersEndText = '\r\n\r\n';
headersEnd = text.indexOf(headersEndText, start);
}
if (headersEnd != -1 && headersEnd < end) {
headersStart = start;
start = headersEnd + headersEndText.length;
for (String header
in LineSplitter().convert(text.substring(headersStart, headersEnd))) {
if (header.startsWith('Proc-Type: ')) {
procType = header.substring(11);
} else if (header.startsWith('DEK-Info: ')) {
throw FormatException('not supported');
}
}
}
String base64text = '';
for (String line in LineSplitter().convert(text.substring(start, end))) {
base64text += line.trim();
}
Uint8List payload = base64.decode(base64text);
switch (type) {
case 'OPENSSH PRIVATE KEY':
OpenSSHKey openssh = OpenSSHKey()
..deserialize(SerializableInput(payload));
Uint8List privateKey;
switch (openssh.kdfname) {
case 'bcrypt':
OpenSSHBCryptKDFOptions kdfoptions = OpenSSHBCryptKDFOptions()
..deserialize(SerializableInput(openssh.kdfoptions));
int cipherAlgo;
if (openssh.ciphername == 'aes256-cbc') {
cipherAlgo = Cipher.AES256_CBC;
} else {
throw FormatException('cipher ${openssh.ciphername}');
}
privateKey = opensshKeyCrypt(
false,
(getPassword != null ? getPassword() : '').codeUnits,
kdfoptions.salt,
kdfoptions.rounds,
openssh.privatekey,
cipherAlgo);
break;
case 'none':
privateKey = openssh.privatekey;
break;
default:
throw FormatException('kdf ${openssh.kdfname}');
}
SerializableInput input = SerializableInput(privateKey);
OpenSSHPrivateKeyHeader().deserialize(input);
String type = deserializeString(SerializableInput(input.viewRemaining()));
switch (type) {
case 'ssh-ed25519':
OpenSSHEd25519PrivateKey ed25519 = OpenSSHEd25519PrivateKey()
..deserialize(input);
if (identity.ed25519 != null) throw FormatException();
identity.ed25519 =
tweetnacl.Signature.keyPair_fromSecretKey(ed25519.privkey);
if (!equalUint8List(identity.ed25519.publicKey, ed25519.pubkey)) {
throw FormatException();
}
return identity;
case 'ssh-rsa':
OpenSSHRSAPrivateKey rsaPrivateKey = OpenSSHRSAPrivateKey()
..deserialize(input);
if (identity.rsaPublic != null || identity.rsaPrivate != null) {
throw FormatException();
}
return identity
..rsaPublic =
asymmetric.RSAPublicKey(rsaPrivateKey.n, rsaPrivateKey.e)
..rsaPrivate = asymmetric.RSAPrivateKey(rsaPrivateKey.n,
rsaPrivateKey.d, rsaPrivateKey.p, rsaPrivateKey.q);
default:
if (type.startsWith('ecdsa-')) {
OpenSSHECDSAPrivateKey ecdsaPrivateKey = OpenSSHECDSAPrivateKey()
..deserialize(input);
ECDomainParameters curve =
Key.ellipticCurve(ecdsaPrivateKey.keyTypeId);
if (identity.ecdsaPublic != null || identity.ecdsaPrivate != null) {
throw FormatException();
}
identity
..ecdsaKeyType = ecdsaPrivateKey.keyTypeId
..ecdsaPublic =
ECPublicKey(curve.curve.decodePoint(ecdsaPrivateKey.q), curve)
..ecdsaPrivate = ECPrivateKey(ecdsaPrivateKey.d, curve);
if (curve.G * identity.ecdsaPrivate.d != identity.ecdsaPublic.Q) {
throw FormatException();
}
return identity;
} else {
throw FormatException('type $type');
}
}
break;
case 'RSA PRIVATE KEY':
RSAPrivateKey rsaPrivateKey = RSAPrivateKey()
..deserialize(SerializableInput(payload));
if (identity.rsaPublic != null || identity.rsaPrivate != null) {
throw FormatException();
}
return identity
..rsaPublic = asymmetric.RSAPublicKey(rsaPrivateKey.n, rsaPrivateKey.e)
..rsaPrivate = asymmetric.RSAPrivateKey(
rsaPrivateKey.n, rsaPrivateKey.d, rsaPrivateKey.p, rsaPrivateKey.q);
default:
throw FormatException('type not supported: $type');
}
}