OpenSshPrivateKey.decode constructor

OpenSshPrivateKey.decode(
  1. Uint8List bytes, {
  2. PvtTextSource? source,
})

Decode from a sequence of bytes.

Implementation

factory OpenSshPrivateKey.decode(Uint8List bytes, {PvtTextSource? source}) {
  var p = 0;

  // Extract null-terminated magic string

  const maxMagicLength = 32; // size limit on the null-terminated magic string
  while (bytes[p] != 0 && p < maxMagicLength && p < bytes.length) {
    p++;
  }
  if (p == 0 || p == bytes.length || bytes[p] != 0) {
    throw KeyBad('magic string not found');
  }

  final magicString = latin1.decode(bytes.sublist(0, p), allowInvalid: false);

  if (magicString != magicVersionId) {
    throw KeyUnsupported('unsupported type: $magicString');
  }
  p++; // skip over the null '\0'

  // Use a binary range to extract out length-value chunks from the rest
  //
  // From [The OpenSSH Private Key Format](https://coolaj86.com/articles/the-openssh-private-key-format/):
  //
  // "openssh-key-v1"0x00    # NULL-terminated "Auth Magic" string
  // 32-bit length, "none"   # ciphername length and string
  // 32-bit length, "none"   # kdfname length and string
  // 32-bit length, nil      # kdf (0 length, no kdf)
  // 32-bit 0x01             # number of keys, hard-coded to 1 (no length)
  // 32-bit length, sshpub   # public key in ssh format
  //    32-bit length, keytype
  //    32-bit length, pub0
  //    32-bit length, pub1
  // 32-bit length for rnd+prv+comment+pad
  //    64-bit dummy checksum?  # a random 32-bit int, repeated
  //    32-bit length, keytype  # the private key (including public)
  //    32-bit length, pub0     # Public Key parts
  //    32-bit length, pub1
  //    32-bit length, prv0     # Private Key parts
  //    ...                     # (number varies by type)
  //    32-bit length, comment  # comment string
  //    padding bytes 0x010203  # pad to block size (see notes below)
  //
  // Source code: https://github.com/openssh/openssh-portable/blob/master/sshkey.c

  final br = BinaryRange(bytes, begin: p);

  final cipherName = br.nextString();
  final kdfName = br.nextString();

  final kdfRange = br.nextBinary();
  final kdf = kdfRange.allRawBytes();
  assert(kdfRange.isEmpty);

  final numberOfKeys = br.nextUint32();
  if (numberOfKeys != 1) {
    throw KeyUnsupported('multiple keys not supported');
  }

  final publicKeyRange = br.nextBinary();

  final privateKeyRange = br.nextBinary();

  if (br.isNotEmpty) {
    throw KeyBad('unexpected extra data in OpenSSH private key');
  }

  // Save the bytes making up the public and private keys

  final publicKeyBytes = publicKeyRange.allRawBytes();
  final privateKeyBytes = privateKeyRange.allRawBytes();

  if (privateKeyBytes.length % 8 != 0) {
    throw KeyBad('private key part is not padded correctly');
  }

  return OpenSshPrivateKey(
      cipherName, kdfName, kdf, publicKeyBytes, privateKeyBytes, source);
}