Skip to content

Commit

Permalink
Merge pull request #895 from atsign-foundation/gkc/npt-new-aes-keys-f…
Browse files Browse the repository at this point in the history
…or-each-socket-pair
  • Loading branch information
gkc authored Mar 21, 2024
2 parents 261caf3 + 184aab6 commit dd72ba0
Show file tree
Hide file tree
Showing 12 changed files with 228 additions and 114 deletions.
201 changes: 119 additions & 82 deletions packages/dart/noports_core/lib/src/srv/srv_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:at_chops/at_chops.dart';
import 'package:at_utils/at_utils.dart';
import 'package:cryptography/cryptography.dart';
import 'package:cryptography/dart.dart';
Expand Down Expand Up @@ -348,69 +349,64 @@ class SrvImplDart implements Srv<SocketConnector> {
}
}

@override
Future<SocketConnector> run() async {
DataTransformer? encrypter;
DataTransformer? decrypter;

if (sessionAESKeyString != null && sessionIVString != null) {
final DartAesCtr algorithm = DartAesCtr.with256bits(
macAlgorithm: Hmac.sha256(),
DataTransformer createEncrypter(String aesKeyBase64, String ivBase64) {
final DartAesCtr algorithm = DartAesCtr.with256bits(
macAlgorithm: Hmac.sha256(),
);
final SecretKey sessionAESKey = SecretKey(base64Decode(aesKeyBase64));
final List<int> sessionIV = base64Decode(ivBase64);

return (Stream<List<int>> stream) {
return algorithm.encryptStream(
stream,
secretKey: sessionAESKey,
nonce: sessionIV,
onMac: (mac) {},
);
final SecretKey sessionAESKey =
SecretKey(base64Decode(sessionAESKeyString!));
final List<int> sessionIV = base64Decode(sessionIVString!);
};
}

encrypter = (Stream<List<int>> stream) {
return algorithm.encryptStream(
stream,
secretKey: sessionAESKey,
nonce: sessionIV,
onMac: (mac) {},
);
};
decrypter = (Stream<List<int>> stream) {
return algorithm.decryptStream(
stream,
secretKey: sessionAESKey,
nonce: sessionIV,
mac: Mac.empty,
);
};
}
DataTransformer createDecrypter(String aesKeyBase64, String ivBase64) {
final DartAesCtr algorithm = DartAesCtr.with256bits(
macAlgorithm: Hmac.sha256(),
);
final SecretKey sessionAESKey = SecretKey(base64Decode(aesKeyBase64));
final List<int> sessionIV = base64Decode(ivBase64);

return (Stream<List<int>> stream) {
return algorithm.decryptStream(
stream,
secretKey: sessionAESKey,
nonce: sessionIV,
mac: Mac.empty,
);
};
}

@override
Future<SocketConnector> run() async {
try {
var hosts = await InternetAddress.lookup(streamingHost);

late SocketConnector sc;
if (bindLocalPort) {
if (multi) {
sc = await _runClientSideMulti(
hosts: hosts,
encrypter: encrypter,
decrypter: decrypter,
);
if (sessionAESKeyString == null || sessionIVString == null) {
throw ArgumentError('Symmetric session encryption key required');
}
sc = await _runClientSideMulti(hosts: hosts);
} else {
sc = await _runClientSideSingle(
hosts: hosts,
encrypter: encrypter,
decrypter: decrypter,
);
sc = await _runClientSideSingle(hosts: hosts);
}
} else {
// daemon side
if (multi) {
sc = await _runDaemonSideMulti(
hosts: hosts,
encrypter: encrypter,
decrypter: decrypter,
);
if (sessionAESKeyString == null || sessionIVString == null) {
throw ArgumentError('Symmetric session encryption key required');
}
sc = await _runDaemonSideMulti(hosts: hosts);
} else {
sc = await _runDaemonSideSingle(
hosts: hosts,
encrypter: encrypter,
decrypter: decrypter,
);
sc = await _runDaemonSideSingle(hosts: hosts);
}
}

Expand All @@ -433,9 +429,13 @@ class SrvImplDart implements Srv<SocketConnector> {

Future<SocketConnector> _runClientSideSingle({
required List<InternetAddress> hosts,
required DataTransformer? encrypter,
required DataTransformer? decrypter,
}) async {
DataTransformer? encrypter;
DataTransformer? decrypter;
if (sessionAESKeyString != null && sessionIVString != null) {
encrypter = createEncrypter(sessionAESKeyString!, sessionIVString!);
decrypter = createDecrypter(sessionAESKeyString!, sessionIVString!);
}
// client side
SocketConnector sc = await SocketConnector.serverToSocket(
portA: localPort,
Expand All @@ -445,12 +445,12 @@ class SrvImplDart implements Srv<SocketConnector> {
transformAtoB: encrypter,
transformBtoA: decrypter,
multi: multi,
onConnect: (Socket sideA, Socket sideB) async {
beforeJoining: (Side sideA, Side sideB) async {
// Authenticate the sideB socket (to the rvd)
if (rvdAuthString != null) {
logger.info(
'_runClientSideSingle authenticating new connection to rvd');
sideB.writeln(rvdAuthString);
sideB.socket.writeln(rvdAuthString);
}
},
);
Expand All @@ -460,81 +460,109 @@ class SrvImplDart implements Srv<SocketConnector> {

Future<SocketConnector> _runClientSideMulti({
required List<InternetAddress> hosts,
required DataTransformer? encrypter,
required DataTransformer? decrypter,
}) async {
// client side
SocketConnector? sc;
SocketConnector? socketConnector;

Socket controlSocket = await Socket.connect(streamingHost, streamingPort,
Socket sessionControlSocket = await Socket.connect(
streamingHost, streamingPort,
timeout: Duration(seconds: 1));
// Authenticate the control socket
if (rvdAuthString != null) {
logger.info(
'_runClientSideMulti authenticating control socket connection to rvd');
controlSocket.writeln(rvdAuthString);
sessionControlSocket.writeln(rvdAuthString);
}
controlSocket.listen((event) {
DataTransformer controlEncrypter =
createEncrypter(sessionAESKeyString!, sessionIVString!);
DataTransformer controlDecrypter =
createDecrypter(sessionAESKeyString!, sessionIVString!);

// Listen to stream which is decrypting the socket stream
// Write to a stream controller which encrypts and writes to the socket
Stream<List<int>> controlStream = controlDecrypter(sessionControlSocket);
StreamController<Uint8List> controlSink = StreamController<Uint8List>();
controlEncrypter(controlSink.stream).listen(sessionControlSocket.add);

controlStream.listen((event) {
String response = String.fromCharCodes(event).trim();
logger.info(
'_runClientSideMulti Received control socket response: [$response]');
}, onError: (e) {
logger.severe('_runClientSideMulti controlSocket error: $e');
sc?.close();
socketConnector?.close();
}, onDone: () {
logger.info('_runClientSideMulti controlSocket done');
sc?.close();
socketConnector?.close();
});

sc = await SocketConnector.serverToSocket(
socketConnector = await SocketConnector.serverToSocket(
portA: localPort,
addressB: hosts[0],
portB: streamingPort,
verbose: false,
transformAtoB: encrypter,
transformBtoA: decrypter,
multi: multi,
onConnect: (Socket sideA, Socket sideB) {
beforeJoining: (Side sideA, Side sideB) {
// For some bizarro reason, we can't write to stderr in this callback
// when the Srv has been started via SrvImplExec. Thus it is very
// important that the srv binary never have a logger root level of
// anything below 'warning'
logger.info('_runClientSideMulti Sending connect request');
controlSocket.writeln('connect');

String socketAESKey =
AtChopsUtil.generateSymmetricKey(EncryptionKeyType.aes256).key;
String socketIV =
base64Encode(AtChopsUtil.generateRandomIV(16).ivBytes);
controlSink.add(
Uint8List.fromList('connect:$socketAESKey:$socketIV'.codeUnits));
// Authenticate the sideB socket (to the rvd)
if (rvdAuthString != null) {
logger
.info('_runClientSideMulti authenticating new connection to rvd');
sideB.writeln(rvdAuthString);
sideB.socket.writeln(rvdAuthString);
}
sideA.transformer = createEncrypter(socketAESKey, socketIV);
sideB.transformer = createDecrypter(socketAESKey, socketIV);
},
);

// upon socketConnector.done, destroy the control socket, and complete
unawaited(sc.done.whenComplete(() {
unawaited(socketConnector.done.whenComplete(() {
logger.info('_runClientSideMulti sc.done');
controlSocket.destroy();
sessionControlSocket.destroy();
}));

return sc;
return socketConnector;
}

Future<SocketConnector> _runDaemonSideMulti({
required List<InternetAddress> hosts,
required DataTransformer? encrypter,
required DataTransformer? decrypter,
}) async {
SocketConnector sc = SocketConnector();

// - create control socket and listen for requests
// - for each request, create a socketToSocket connection
Socket controlSocket = await Socket.connect(streamingHost, streamingPort,
Socket sessionControlSocket = await Socket.connect(
streamingHost, streamingPort,
timeout: Duration(seconds: 1));
// Authenticate the control socket
if (rvdAuthString != null) {
logger.info('authenticating control socket connection to rvd');
controlSocket.writeln(rvdAuthString);
logger.info(
'_runDaemonSideMulti authenticating control socket connection to rvd');
sessionControlSocket.writeln(rvdAuthString);
}
controlSocket.listen((event) async {
DataTransformer controlEncrypter =
createEncrypter(sessionAESKeyString!, sessionIVString!);
DataTransformer controlDecrypter =
createDecrypter(sessionAESKeyString!, sessionIVString!);

// Listen to stream which is decrypting the socket stream
// Write to a stream controller which encrypts and writes to the socket
Stream<List<int>> controlStream = controlDecrypter(sessionControlSocket);
StreamController<Uint8List> controlSink = StreamController<Uint8List>();
controlEncrypter(controlSink.stream).listen(sessionControlSocket.add);

controlStream.listen((event) async {
if (event.isEmpty) {
logger.info('Empty control message (Uint8List) received');
return;
Expand All @@ -544,9 +572,14 @@ class SrvImplDart implements Srv<SocketConnector> {
logger.info('Empty control message (String) received');
return;
}
switch (request) {
List<String> args = request.split(":");
switch (args.first) {
case 'connect':
logger.info('Control socket received request: [$request];'
if (args.length != 3) {
logger.severe('Unknown request to control socket: [$request]');
return;
}
logger.info('Control socket received ${args.first} request - '
' creating new socketToSocket connection');
await SocketConnector.socketToSocket(
connector: sc,
Expand All @@ -556,8 +589,8 @@ class SrvImplDart implements Srv<SocketConnector> {
addressB: hosts[0],
portB: streamingPort,
verbose: false,
transformAtoB: encrypter,
transformBtoA: decrypter);
transformAtoB: createEncrypter(args[1], args[2]),
transformBtoA: createDecrypter(args[1], args[2]));
if (rvdAuthString != null) {
stderr.writeln('authenticating new socket connection to rvd');
sc.connections.last.sideB.socket.writeln(rvdAuthString);
Expand All @@ -577,17 +610,21 @@ class SrvImplDart implements Srv<SocketConnector> {

// upon socketConnector.done, destroy the control socket, and complete
unawaited(sc.done.whenComplete(() {
controlSocket.destroy();
sessionControlSocket.destroy();
}));

return sc;
}

Future<SocketConnector> _runDaemonSideSingle({
required List<InternetAddress> hosts,
required DataTransformer? encrypter,
required DataTransformer? decrypter,
}) async {
DataTransformer? encrypter;
DataTransformer? decrypter;
if (sessionAESKeyString != null && sessionIVString != null) {
encrypter = createEncrypter(sessionAESKeyString!, sessionIVString!);
decrypter = createDecrypter(sessionAESKeyString!, sessionIVString!);
}
SocketConnector socketConnector = await SocketConnector.socketToSocket(
addressA: (await InternetAddress.lookup(localHost ?? 'localhost'))[0],
portA: localPort,
Expand Down
2 changes: 1 addition & 1 deletion packages/dart/noports_core/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ dependencies:
openssh_ed25519: ^1.1.0
path: ^1.9.0
posix: ^6.0.1
socket_connector: ^2.1.0
socket_connector: ^2.2.0
uuid: ^3.0.7

dependency_overrides:
Expand Down
7 changes: 3 additions & 4 deletions packages/dart/sshnoports/bin/npt.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import 'package:at_utils/at_logger.dart';
import 'package:at_cli_commons/at_cli_commons.dart' as cli;
import 'package:noports_core/npt.dart';
import 'package:noports_core/sshnp_foundation.dart';
import 'package:sshnoports/src/extended_arg_parser.dart';

// local packages
import 'package:sshnoports/src/print_version.dart';
Expand Down Expand Up @@ -144,13 +143,13 @@ void main(List<String> args) async {
defaultsTo: false, negatable: false, help: 'Print usage');

parser.addFlag(
outputExecutionCommandFlag,
'exit-when-connected',
abbr: 'x',
help: 'Instead of running the srv in the same process,'
' fork the srv,'
' print the local port to stdout,'
' and exit this program.',
defaultsTo: DefaultExtendedArgs.outputExecutionCommand,
defaultsTo: false,
negatable: false,
);

Expand All @@ -171,7 +170,7 @@ void main(List<String> args) async {
String rootDomain = parsedArgs['root-domain'];
perSessionStorage = parsedArgs['per-session-storage'];
int localPort = int.parse(parsedArgs['local-port']);
bool inline = !parsedArgs[outputExecutionCommandFlag];
bool inline = !parsedArgs['exit-when-connected'];

// Windows will not let us delete files in use so
// We will point storage to temp directory and let OS clean up
Expand Down
2 changes: 1 addition & 1 deletion packages/dart/sshnoports/lib/src/version.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit dd72ba0

Please sign in to comment.