Skip to content

Commit

Permalink
chore: use new guarantees
Browse files Browse the repository at this point in the history
  • Loading branch information
XavierChanth committed Oct 4, 2023
1 parent 4025f49 commit 6163de5
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 51 deletions.
44 changes: 35 additions & 9 deletions packages/noports_core/lib/src/common/file_system_utils.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'package:at_utils/at_utils.dart';
import 'package:path/path.dart' as path;
Expand Down Expand Up @@ -60,15 +61,28 @@ String getDefaultSshnpConfigDirectory(String homeDirectory) {
return path.normalize('$homeDirectory/.sshnp/config');
}

Future<(String, String)> generateSshKeys(
{required bool rsa,
required String sessionId,
String? sshHomeDirectory}) async {
(String, String, String) _getEphemeralKeysPath(
String? sshHomeDirectory, String sessionId) {
sshHomeDirectory ??= getDefaultSshDirectory(getHomeDirectory()!);
if (!Directory(sshHomeDirectory).existsSync()) {
Directory(sshHomeDirectory).createSync();
}

return (
sshHomeDirectory,
'$sshHomeDirectory/${sessionId}_sshnp.pub',
'$sshHomeDirectory/${sessionId}_sshnp'
);
}

Future<(String, String)> generateEphemeralSshKeys(
{required bool rsa,
required String sessionId,
String? sshHomeDirectory}) async {
var (normalizedSshHomeDirectory, sshPublicKeyPath, sshPrivateKeyPath) =
_getEphemeralKeysPath(sshHomeDirectory, sessionId);
sshHomeDirectory = normalizedSshHomeDirectory;

if (rsa) {
await Process.run('ssh-keygen',
['-t', 'rsa', '-b', '4096', '-f', '${sessionId}_sshnp', '-q', '-N', ''],
Expand All @@ -90,12 +104,24 @@ Future<(String, String)> generateSshKeys(
workingDirectory: sshHomeDirectory);
}

String sshPublicKey =
await File('$sshHomeDirectory/${sessionId}_sshnp.pub').readAsString();
String sshPrivateKey =
await File('$sshHomeDirectory/${sessionId}_sshnp').readAsString();
var keys = await Future.wait([
File(sshPublicKeyPath).readAsString(),
File(sshPrivateKeyPath).readAsString()
]);

return (keys[0], keys[1]);
}

return (sshPublicKey, sshPrivateKey);
Future<void> cleanUpEphemeralSshKeys({
required String sessionId,
String? sshHomeDirectory,
}) async {
var (_, sshPublicKeyPath, sshPrivateKeyPath) =
_getEphemeralKeysPath(sshHomeDirectory, sessionId);
await Future.wait([
File(sshPublicKeyPath).delete(),
File(sshPrivateKeyPath).delete(),
]);
}

Future<void> addEphemeralKeyToAuthorizedKeys(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:path/path.dart' as path;

class SSHNPForwardExecImpl extends SSHNPImpl
with SSHNPForwardDirection, SSHNPLocalFileMixin {
late String ephemeralPrivateKeyPath;
SSHNPForwardExecImpl({
required AtClient atClient,
required SSHNPParams params,
Expand Down Expand Up @@ -45,21 +46,17 @@ class SSHNPForwardExecImpl extends SSHNPImpl
String? errorMessage;
Process? process;

// If using exec then we can assume we're on something unix-y
// So we can write the ephemeralPrivateKey to a tmp file,
// set its permissions appropriately, and remove it after we've
// executed the command
var tmpFileName =
path.normalize('$sshHomeDirectory/tmp/ephemeral_$sessionId');
File tmpFile = File(tmpFileName);
var ephemeralPrivateKeyPath = path.normalize(
'$sshnpHomeDirectory/sessions/$sessionId/ephemeral_private_key');
File tmpFile = File(ephemeralPrivateKeyPath);
await tmpFile.create(recursive: true);
await tmpFile.writeAsString(ephemeralPrivateKey,
mode: FileMode.write, flush: true);
await Process.run('chmod', ['go-rwx', tmpFileName]);
await Process.run('chmod', ['go-rwx', ephemeralPrivateKeyPath]);

String argsString = '$remoteUsername@$host'
' -p $sshrvdPort'
' -i $tmpFileName'
' -i $ephemeralPrivateKeyPath'
' -L $localPort:localhost:${params.remoteSshdPort}'
' -o LogLevel=VERBOSE'
' -t -t'
Expand Down Expand Up @@ -138,4 +135,10 @@ class SSHNPForwardExecImpl extends SSHNPImpl
);
}
}

@override
Future<void> cleanUp() async {
await deleteFile(ephemeralPrivateKeyPath);
super.cleanUp();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ abstract class SSHNPImpl implements SSHNP {
_initializeStarted = true;
}

// Schedule a cleanup on exit
unawaited(doneCompleter.future.then((_) async {
logger.info('SSHNPImpl done');
await cleanUp();
}));

try {
if (!(await atSignIsActivated(atClient, sshnpdAtSign))) {
logger.severe('Device address $sshnpdAtSign is not activated.');
Expand Down Expand Up @@ -297,7 +303,6 @@ abstract class SSHNPImpl implements SSHNP {
try {
return (await atClient.get(userNameRecordID)).value as String;
} catch (e, s) {
await cleanUp();
throw SSHNPError(
"Device unknown, or username not shared\n"
"hint: make sure the device shares username or set remote username manually",
Expand Down Expand Up @@ -344,7 +349,6 @@ abstract class SSHNPImpl implements SSHNP {
counter++;
if (counter == 100) {
logger.warning('Timed out waiting for sshrvd response');
await cleanUp();
throw ('Connection timeout to sshrvd $host service\nhint: make sure host is valid and online');
}
}
Expand Down Expand Up @@ -379,7 +383,6 @@ abstract class SSHNPImpl implements SSHNP {
..ttl = 10000);
await notify(sendOurPublicKeyToSshnpd, toSshPublicKey);
} catch (e, s) {
await cleanUp();
throw SSHNPError(
'Error opening or validating public key file or sending to remote atSign',
error: e,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ class SSHNPLegacyImpl extends SSHNPImpl
);

bool acked = await waitForDaemonResponse();
await cleanUp();
if (!acked) {
var error = SSHNPError(
'sshnp timed out: waiting for daemon response\nhint: make sure the device is online',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import 'dart:io';

import 'package:meta/meta.dart';
import 'package:noports_core/src/common/file_system_utils.dart';
import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl.dart';
import 'package:noports_core/src/sshnp/sshnp_result.dart';

mixin SSHNPLocalFileMixin on SSHNPImpl {
late final String homeDirectory;
late final String sshHomeDirectory;
late final String sshnpHomeDirectory;

final bool _isValidPlatform =
Platform.isLinux || Platform.isMacOS || Platform.isWindows;
Expand All @@ -19,8 +23,28 @@ mixin SSHNPLocalFileMixin on SSHNPImpl {
throw SSHNPError(
'The current platform is not supported: ${Platform.operatingSystem}');
}
try {
homeDirectory = getHomeDirectory(throwIfNull: true)!;
} catch (e, s) {
throw SSHNPError('Unable to determine the home directory',
error: e, stackTrace: s);
}
sshHomeDirectory = getDefaultSshDirectory(homeDirectory);
sshnpHomeDirectory = getDefaultSshnpDirectory(homeDirectory);

await super.init();

if (initializedCompleter.isCompleted) return;
}

@protected
Future<bool> deleteFile(String fileName) async {
try {
final file = File(fileName);
await file.delete();
return true;
} catch (e) {
logger.severe("Error deleting file : $fileName");
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ class SSHNPReverseImpl extends SSHNPImpl
sessionId: sessionId);

bool acked = await waitForDaemonResponse();
await cleanUp();
if (!acked) {
var error =
SSHNPError('sshnp connection timeout: waiting for daemon response');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:io';

import 'package:noports_core/src/common/file_system_utils.dart';
import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_local_file_mixin.dart';
Expand All @@ -10,13 +9,13 @@ import 'package:noports_core/utils.dart';
/// e.g. class [SSHNPReverseImpl] extends [SSHNPImpl] with [SSHNPLocalFileMixin], [SSHNPReverseMixin]
/// Note that the order of mixins is important here.
mixin SSHNPReverseMixin on SSHNPLocalFileMixin {
/// Set by [generateSshKeys] during [init], if we're not doing direct ssh.
/// Set by [generateEphemeralSshKeys] during [init], if we're not doing direct ssh.
/// sshnp generates a new keypair for each ssh session, using ed25519 by
/// default but rsa if the [rsa] flag is set to true. sshnp will write
/// [sshPublicKey] to ~/.ssh/authorized_keys
late final String sshPublicKey;

/// Set by [generateSshKeys] during [init].
/// Set by [generateEphemeralSshKeys] during [init].
/// sshnp generates a new keypair for each ssh session, using ed25519 by
/// default but rsa if the [rsa] flag is set to true. sshnp will send the
/// [sshPrivateKey] to sshnpd
Expand All @@ -35,7 +34,7 @@ mixin SSHNPReverseMixin on SSHNPLocalFileMixin {
logger.info('Generating ephemeral keypair');
try {
var (String ephemeralPublicKey, String ephemeralPrivateKey) =
await generateSshKeys(
await generateEphemeralSshKeys(
rsa: params.rsa,
sessionId: sessionId,
sshHomeDirectory: sshHomeDirectory,
Expand Down Expand Up @@ -72,23 +71,12 @@ mixin SSHNPReverseMixin on SSHNPLocalFileMixin {
var sshHomeDirectory = getDefaultSshDirectory(homeDirectory);
logger.info('Tidying up files');
// Delete the generated RSA keys and remove the entry from ~/.ssh/authorized_keys
await _deleteFile('$sshHomeDirectory/${sessionId}_sshnp');
await _deleteFile('$sshHomeDirectory/${sessionId}_sshnp.pub');
await cleanUpEphemeralSshKeys(
sessionId: sessionId, sshHomeDirectory: sshHomeDirectory);
await removeEphemeralKeyFromAuthorizedKeys(sessionId, logger,
sshHomeDirectory: sshHomeDirectory);
super.cleanUp();
}

Future<bool> _deleteFile(String fileName) async {
try {
final file = File(fileName);
await file.delete();
return true;
} catch (e) {
logger.severe("Error deleting file : $fileName");
return false;
}
}

bool get usingSshrv => sshrvdPort != null;
}
2 changes: 1 addition & 1 deletion packages/noports_core/lib/src/sshnpd/sshnpd_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ class SSHNPDImpl implements SSHNPD {
/// `authorized_keys` file, limiting permissions (e.g. hosts and ports
/// which can be forwarded to) as per the `--ephemeral-permissions` option
var (String ephemeralPublicKey, String ephemeralPrivateKey) =
await generateSshKeys(rsa: rsa, sessionId: sessionId);
await generateEphemeralSshKeys(rsa: rsa, sessionId: sessionId);

await addEphemeralKeyToAuthorizedKeys(
sshPublicKey: ephemeralPublicKey,
Expand Down
9 changes: 0 additions & 9 deletions packages/sshnoports/bin/sshnp.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,6 @@ void main(List<String> args) async {
exit(0);
}

ProcessSignal.sigint.watch().listen((signal) async {
await sshnp?.cleanUp();
exit(1);
});

await runZonedGuarded(() async {
try {
params = SSHNPParams.fromPartial(SSHNPPartialParams.fromArgList(args));
Expand Down Expand Up @@ -98,8 +93,6 @@ void main(List<String> args) async {
if (verbose) {
stderr.writeln('\nStack Trace: ${stackTrace.toString()}');
}

await sshnp?.cleanUp();
exit(1);
}
}, (Object error, StackTrace stackTrace) async {
Expand All @@ -109,8 +102,6 @@ void main(List<String> args) async {
if (verbose) {
stderr.writeln('\nStack Trace: ${stackTrace.toString()}');
}

await sshnp?.cleanUp();
exit(1);
});
}
Expand Down

0 comments on commit 6163de5

Please sign in to comment.