ssh_core 0.1.0-dev.2 copy "ssh_core: ^0.1.0-dev.2" to clipboard
ssh_core: ^0.1.0-dev.2 copied to clipboard

Core building blocks for an SSH client library in Dart.

example/ssh_core_example.dart

import 'dart:convert';

import 'package:ssh_core/ssh_core.dart';

Future<void> main() async {
  final SshHostKey trustedHostKey = _demoHostKey();
  final client = SshClient(
    config: SshClientConfig(
      host: 'example.com',
      username: 'demo',
      hostKeyVerifier: SshStaticHostKeyVerifier(
        trustedKeys: <SshTrustedHostKey>[
          SshTrustedHostKey(host: 'example.com', hostKey: trustedHostKey),
        ],
      ),
    ),
    authMethods: const <SshAuthMethod>[
      SshPasswordAuthMethod(password: 'secret'),
    ],
    transport: DemoTransport(hostKey: trustedHostKey),
    authenticator: DemoAuthenticator(),
    channelFactory: DemoChannelFactory(),
    sessionManager: DemoSessionManager(),
    execService: DemoExecService(),
    sftpSubsystem: DemoSftpSubsystem(),
    portForwardingService: DemoPortForwardingService(),
  );

  await client.connect();
  final result = await client.exec('uname -a');
  print(result.stdoutText.trim());
  final forward = await client.forwardLocal(
    bindHost: '127.0.0.1',
    bindPort: 8022,
    targetHost: '127.0.0.1',
    targetPort: 22,
  );
  await forward.close();
  await client.close();
}

class DemoTransport implements SshTransport {
  DemoTransport({required SshHostKey hostKey}) : _hostKey = hostKey;

  final SshBannerExchange _bannerExchange = const SshBannerExchange();
  final SshAlgorithmNegotiator _algorithmNegotiator =
      const SshAlgorithmNegotiator();
  final SshHostKey _hostKey;

  @override
  SshTransportState get state => SshTransportState.connected;

  @override
  Future<SshHandshakeInfo> connect({
    required SshEndpoint endpoint,
    required SshTransportSettings settings,
  }) async {
    final SshKexInitMessage clientProposal = SshKexInitMessage(
      cookie: List<int>.filled(16, 1),
      kexAlgorithms: const <String>[
        sshCurve25519Sha256,
        'diffie-hellman-group14-sha256',
      ],
      serverHostKeyAlgorithms: const <String>[
        sshEd25519HostKeyAlgorithm,
        sshRsaSha256HostKeyAlgorithm,
      ],
      encryptionAlgorithmsClientToServer: const <String>[
        sshChaCha20Poly1305OpenSshCipher,
      ],
      encryptionAlgorithmsServerToClient: const <String>[
        sshChaCha20Poly1305OpenSshCipher,
      ],
      macAlgorithmsClientToServer: const <String>[sshHmacSha256Mac],
      macAlgorithmsServerToClient: const <String>[sshHmacSha256Mac],
      compressionAlgorithmsClientToServer: const <String>[sshNoCompression],
      compressionAlgorithmsServerToClient: const <String>[sshNoCompression],
    );
    final SshKexInitMessage serverProposal = SshKexInitMessage(
      cookie: List<int>.filled(16, 2),
      kexAlgorithms: const <String>[sshCurve25519Sha256],
      serverHostKeyAlgorithms: const <String>[sshEd25519HostKeyAlgorithm],
      encryptionAlgorithmsClientToServer: const <String>[
        sshChaCha20Poly1305OpenSshCipher,
      ],
      encryptionAlgorithmsServerToClient: const <String>[
        sshChaCha20Poly1305OpenSshCipher,
      ],
      macAlgorithmsClientToServer: const <String>[sshHmacSha256Mac],
      macAlgorithmsServerToClient: const <String>[sshHmacSha256Mac],
      compressionAlgorithmsClientToServer: const <String>[sshNoCompression],
      compressionAlgorithmsServerToClient: const <String>[sshNoCompression],
    );
    final SshTransportStream transportStream = SshTransportStream(
      incoming: Stream<List<int>>.fromIterable(<List<int>>[
        utf8.encode('demo prelude line\r\nSSH-2.0-demo-server example\r\n'),
      ]),
      onWrite: (List<int> bytes) {},
      bannerExchange: _bannerExchange,
    );

    final SshBannerExchangeResult exchange =
        await transportStream.exchangeBanners(
      localIdentification: settings.clientIdentification,
    );
    final SshNegotiatedAlgorithms algorithms = _algorithmNegotiator.negotiate(
      clientProposal: clientProposal,
      serverProposal: serverProposal,
    );
    final SshKexEcdhInitMessage clientKeyExchange = SshKexEcdhInitMessage(
      clientEphemeralPublicKey: const <int>[3, 1, 4, 1, 5],
    );
    final SshSignature exchangeHashSignature = SshSignature(
      algorithm: sshEd25519HostKeyAlgorithm,
      blob: const <int>[3, 5, 8, 9],
    );
    final SshKexEcdhReplyMessage serverKeyExchange = SshKexEcdhReplyMessage(
      hostKey: _hostKey,
      serverEphemeralPublicKey: const <int>[9, 2, 6, 5],
      exchangeHashSignature: exchangeHashSignature.encode(),
    );
    assert(
      SshKexEcdhExchangeHashInput(
        clientIdentification: exchange.localBanner.value,
        serverIdentification: exchange.remoteBanner.value,
        clientKexInitPayload: clientProposal.encodePayload(),
        serverKexInitPayload: serverProposal.encodePayload(),
        hostKey: serverKeyExchange.hostKey,
        clientEphemeralPublicKey: clientKeyExchange.clientEphemeralPublicKey,
        serverEphemeralPublicKey: serverKeyExchange.serverEphemeralPublicKey,
        sharedSecret: BigInt.from(42),
      ).encode().isNotEmpty,
    );

    return SshHandshakeInfo.fromBannerExchange(
      exchange,
      negotiatedAlgorithms: algorithms.asHandshakeMap(),
      hostKey: _hostKey,
    );
  }

  @override
  Future<void> disconnect() async {}

  @override
  Future<void> sendGlobalRequest(SshGlobalRequest request) async {}
}

class DemoAuthenticator implements SshAuthenticator {
  @override
  Future<SshAuthResult> authenticate({
    required SshAuthContext context,
    required List<SshAuthMethod> methods,
  }) async {
    if (methods.isEmpty) {
      return const SshAuthResult.failure(
        message: 'At least one auth method is required.',
      );
    }
    return const SshAuthResult.success(message: 'Authenticated.');
  }
}

class DemoChannel implements SshChannel {
  @override
  int get id => 1;

  @override
  SshChannelType get type => SshChannelType.session;

  @override
  Future<void> close() async {}

  @override
  Future<void> sendRequest(SshChannelRequest request) async {}
}

class DemoChannelFactory implements SshChannelFactory {
  @override
  Future<SshChannel> openChannel(SshChannelOpenRequest request) async {
    return DemoChannel();
  }
}

class DemoShellSession implements SshShellSession {
  DemoShellSession(this.channel);

  @override
  final SshChannel channel;

  @override
  SshSessionState get state => SshSessionState.active;

  @override
  Stream<List<int>> get stderr => const Stream<List<int>>.empty();

  @override
  Stream<List<int>> get stdout => const Stream<List<int>>.empty();

  @override
  Future<void> close() async {}

  @override
  Future<void> resizePty(SshPtyConfig nextPty) async {}

  @override
  Future<void> writeStdin(List<int> data) async {}
}

class DemoSessionManager implements SshSessionManager {
  @override
  Future<SshShellSession> openShellSession(SshShellRequest request) async {
    return DemoShellSession(DemoChannel());
  }
}

class DemoExecService implements SshExecService {
  final SshPacketCodec _packetCodec = const SshPacketCodec();

  @override
  Future<SshExecResult> exec(SshExecRequest request) async {
    final SshBinaryPacket packet = _packetCodec.decode(
      _packetCodec.encode(utf8.encode(request.command)),
    );

    return SshExecResult(
      exitCode: 0,
      stdout: utf8.encode('demo:${utf8.decode(packet.payload)}\n'),
    );
  }
}

class DemoSftpClient implements SftpClient {
  @override
  Future<void> close() async {}

  @override
  Future<void> createDirectory(String path, {bool recursive = false}) async {}

  @override
  Future<void> delete(String path, {bool recursive = false}) async {}

  @override
  Future<List<SftpFileEntry>> listDirectory(String path) async {
    return const <SftpFileEntry>[
      SftpFileEntry(path: '/tmp/demo.txt', type: SftpFileType.file, size: 4),
    ];
  }

  @override
  Future<List<int>> readFile(String path) async {
    return utf8.encode('demo');
  }

  @override
  Future<void> writeFile(String path, List<int> bytes) async {}
}

class DemoSftpSubsystem implements SftpSubsystem {
  @override
  Future<SftpClient> open() async {
    return DemoSftpClient();
  }
}

class DemoPortForward implements SshPortForward {
  const DemoPortForward({
    required this.mode,
    required this.bindHost,
    required this.bindPort,
  });

  @override
  final SshForwardingMode mode;

  @override
  final String bindHost;

  @override
  final int bindPort;

  @override
  Future<void> close() async {}
}

class DemoPortForwardingService implements SshPortForwardingService {
  @override
  Future<SshPortForward> openForward(SshForwardRequest request) async {
    return DemoPortForward(
      mode: request.mode,
      bindHost: request.bindHost,
      bindPort: request.bindPort,
    );
  }
}

SshHostKey _demoHostKey() {
  final SshPayloadWriter writer = SshPayloadWriter()
    ..writeString('ssh-ed25519')
    ..writeStringBytes(const <int>[1, 2, 3, 4, 5, 6]);
  return SshHostKey.decode(writer.toBytes());
}
0
likes
50
points
12
downloads

Documentation

API reference

Publisher

verified publisheratrac613.com

Weekly Downloads

Core building blocks for an SSH client library in Dart.

Repository (GitHub)
View/report issues
Contributing

Topics

#ssh #sftp #networking

License

MIT (license)

Dependencies

crypto, pinenacl, pointycastle

More

Packages that depend on ssh_core