OpenSshPublicKey.decode constructor

OpenSshPublicKey.decode(
  1. String str, {
  2. int offset = 0,
})

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}');
    }
  }
}