OpenSshPublicKey.decode constructor Null safety

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

This decoder is less strict and will accept multiple whitespaces where a single space is expected. It will also ignore any white space and blank lines before the key-type (i.e. the key-type does not have to be at the beginning of the line).

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? algorithmNameEnd;

  while (p < str.length) {
    final ch = str[p];
    if (ch == ' ') {
      if (p != keyTypeStart + 1) {
        algorithmNameEnd = p;
        p++;
        break;
      } else {
        break;
      }
    } else {
      p++;
    }
  }

  if (algorithmNameEnd == null) {
    throw KeyBad('OpenSSH Public Key: key-type missing');
  }

  final keyType = str.substring(keyTypeStart, algorithmNameEnd);

  // 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 && (str[p] == ' ')) {
    // There is space, 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 if (p < str.length && str[p] != '\r' && str[p] != '\n' ||
      p == str.length) {
    // End of the line or end of string
    comment = null; // no comment
  } 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}');
    }
  }
}