decodeOpenSshPrivate static method

({String comment, Uint8List publicKey, Uint8List seed}) decodeOpenSshPrivate(
  1. Uint8List bytes
)

Implementation

static ({Uint8List seed, Uint8List publicKey, String comment})
    decodeOpenSshPrivate(Uint8List bytes) {
  final magic = utf8.encode('openssh-key-v1\u0000');
  if (bytes.length < magic.length ||
      !constantTimeAreEqual(
        Uint8List.fromList(bytes.sublist(0, magic.length)),
        Uint8List.fromList(magic),
      )) {
    throw ArgumentError('Formato OpenSSH private key invalido');
  }
  var off = magic.length;

  final cipher = _readSshString(bytes, off);
  off = cipher.nextOffset;
  final kdf = _readSshString(bytes, off);
  off = kdf.nextOffset;
  final kdfOpts = _readSshString(bytes, off);
  off = kdfOpts.nextOffset;
  final nKeys = _readU32(bytes, off);
  off += 4;

  if (utf8.decode(cipher.value) != 'none' ||
      utf8.decode(kdf.value) != 'none' ||
      kdfOpts.value.isNotEmpty ||
      nKeys != 1) {
    throw UnsupportedError('Somente OpenSSH sem criptografia (none/none)');
  }

  final publicBlob = _readSshString(bytes, off);
  off = publicBlob.nextOffset;
  final privateBlob = _readSshString(bytes, off);
  off = privateBlob.nextOffset;
  if (off != bytes.length) {
    throw ArgumentError('Bytes extras em OpenSSH private key');
  }

  var p = 0;
  final check1 = _readU32(privateBlob.value, p);
  p += 4;
  final check2 = _readU32(privateBlob.value, p);
  p += 4;
  if (check1 != check2) {
    throw ArgumentError('Checkints OpenSSH invalidos');
  }

  final type = _readSshString(privateBlob.value, p);
  p = type.nextOffset;
  if (utf8.decode(type.value) != 'ssh-ed25519') {
    throw UnsupportedError('Tipo OpenSSH nao suportado');
  }
  final pub = _readSshString(privateBlob.value, p);
  p = pub.nextOffset;
  final priv = _readSshString(privateBlob.value, p);
  p = priv.nextOffset;
  final comment = _readSshString(privateBlob.value, p);
  p = comment.nextOffset;

  if (pub.value.length != 32 || priv.value.length != 64) {
    throw ArgumentError('Material Ed25519 invalido em OpenSSH');
  }
  final seed = Uint8List.fromList(priv.value.sublist(0, 32));
  final pubFromPriv = Uint8List.fromList(priv.value.sublist(32, 64));
  if (!constantTimeAreEqual(pub.value, pubFromPriv)) {
    throw ArgumentError('Chave publica inconsistente no OpenSSH private key');
  }

  final pad = privateBlob.value.sublist(p);
  for (var i = 0; i < pad.length; i++) {
    if (pad[i] != (i + 1)) {
      throw ArgumentError('Padding OpenSSH invalido');
    }
  }

  final pubFromHeader = _parsePublicBlob(publicBlob.value);
  if (!constantTimeAreEqual(pub.value, pubFromHeader)) {
    throw ArgumentError('Chave publica header/private mismatch');
  }

  final cmt = utf8.decode(comment.value, allowMalformed: true);
  return (seed: seed, publicKey: Uint8List.fromList(pub.value), comment: cmt);
}