OpenSshPublicKey.decode constructor
Decode from text
The str
must consist of a line containing:
- key-type
- single space
- base-64 encoded OpenSSH format key
- optional: single space followed by a comment
Whitespace
This decoder is less strict and will accept multiple whitespaces between the key-type and the base-64 encoded data. It will also ignore any whitespace and blank lines before the key-type (i.e. the key-type does not have to be at the beginning of the line).
But it strictly follows the specification for space for the comment. Between the end of the base-64 encoded data and the end of the line:
- No characters: no comment (the comment will be null).
- Single space: comment exists and is the empty string.
- Two spaces: comment is a string containing one space.
- Multiple spaces followed by other characters: the comment includes all the spaces except for the first one (e.g if the line ends with "== xyz", the comment is " xyz").
- Any spaces after the comment is included in the comment.
https://tools.ietf.org/html/rfc4253#section-6.6
Throws a FormatException if the string does not contain correctly encoded value. Any whitespace at the start of the string is skipped.
Implementation
OpenSshPublicKey.decode(String str, {int offset = 0}) {
// Skip the key type
if (str.isEmpty) {
throw KeyMissing('OpenSSH Public Key: string is empty');
}
var p = offset;
// Skip leading whitespace and blank lines
while (p < str.length) {
final ch = str[p];
if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') {
p++;
} else {
break;
}
}
final keyTypeStart = p;
int? keyTypeEnd;
while (p < str.length) {
final ch = str[p];
if (ch == ' ') {
keyTypeEnd = p;
break;
} else {
p++; // character is a part of the key-type
}
}
if (keyTypeEnd == null) {
// Loop ended because end of str was reached, not because space found
throw KeyBad('OpenSSH Public Key: key-type missing');
}
final keyType = str.substring(keyTypeStart, keyTypeEnd);
// Find start of PEM data (by skipping all whitespace)
while (p < str.length && (str[p] == ' ' || str[p] == '\t')) {
p++;
}
final pemStart = p;
// Find end of PEM encoded data
while (p < str.length &&
(str[p] != ' ' && str[p] != '\t' && str[p] != '\r' && str[p] != '\n')) {
p++;
}
if (pemStart == p) {
throw KeyBad('OpenSSH Public Key: base64 missing');
}
final pemEnd = p;
// Parse optional comment
if (p == str.length) {
// End of string reached, so there is no comment
comment = null;
} else if (str[p] == '\r' || str[p] == '\n') {
// End of the line reached, so there is no comment
comment = null;
} else if (str[p] == ' ') {
// There is a space after the base64, so the rest of the line is a comment
p++; // skip over the space
final commentStart = p;
// Find end of comment (which is terminated by the end-of-line or string)
while (p < str.length && (str[p] != '\r' && str[p] != '\n')) {
p++;
}
comment = str.substring(commentStart, p);
} else {
throw KeyBad('OpenSSH Public Key: base64 terminated incorrectly');
}
// Skip over any CR, LF or CR-LF
if (p < str.length && str[p] == '\r') {
p++;
}
if (p < str.length && str[p] == '\n') {
p++;
}
// Source
source = PubTextSource(str, keyTypeStart, p, PubKeyEncoding.openSsh);
// Decode the base-64 text
try {
data = base64.decode(str.substring(pemStart, pemEnd));
final chunks = BinaryRange(data);
// The first chunk of data is the key-type and should be the same as the
// text key-type at the beginning of the line
if (BinaryRange.copy(chunks).nextString() != keyType) {
throw KeyBad('OpenSSH Public Key: algorithm name mismatch');
}
} on FormatException catch (e) {
if (e.message == 'Invalid length, must be multiple of four') {
throw KeyBad('OpenSSH Public Key: base64 invalid');
} else {
throw KeyBad('OpenSSH Public Key: ${e.message}');
}
}
}