Skip to content

Commit

Permalink
deprecate CryptoBinaryStream
Browse files Browse the repository at this point in the history
  • Loading branch information
trudyhood committed Jan 14, 2024
1 parent 1c3c360 commit ef38e76
Show file tree
Hide file tree
Showing 10 changed files with 416 additions and 78 deletions.
8 changes: 4 additions & 4 deletions Tests/VpnHood.Test/Tests/TunnelTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ private static async Task SimpleLoopback(TcpListener tcpListener, CancellationTo
using var client = await tcpListener.AcceptTcpClientAsync(cancellationToken);

// Create a memory stream to store the incoming data
ChunkStream binaryStream = new BinaryStream(client.GetStream(), Guid.NewGuid().ToString(), new byte[16]);
ChunkStream binaryStream = new BinaryStream(client.GetStream(), Guid.NewGuid().ToString());
while (true)
{
using var memoryStream = new MemoryStream();
Expand Down Expand Up @@ -267,7 +267,7 @@ public async Task ChunkStream()
};

// first stream
ChunkStream binaryStream = new BinaryStream(stream, Guid.NewGuid().ToString(), new byte[16]);
ChunkStream binaryStream = new BinaryStream(stream, Guid.NewGuid().ToString());
await binaryStream.WriteAsync(BitConverter.GetBytes(chunks.Sum(x => x.Length)), cts.Token);
foreach (var chunk in chunks)
await binaryStream.WriteAsync(Encoding.UTF8.GetBytes(chunk).ToArray(), cts.Token);
Expand All @@ -279,7 +279,7 @@ public async Task ChunkStream()
Assert.AreEqual(string.Join("", chunks), res);

// write second stream
binaryStream = (BinaryStream)await binaryStream.CreateReuse();
binaryStream = await binaryStream.CreateReuse();
await binaryStream.WriteAsync(BitConverter.GetBytes(chunks.Sum(x => x.Length)).ToArray(), cts.Token);
foreach (var chunk in chunks)
await binaryStream.WriteAsync(Encoding.UTF8.GetBytes(chunk).ToArray(), cts.Token);
Expand Down Expand Up @@ -321,7 +321,7 @@ public async Task ChunkStream_Binary()
random.NextBytes(writeBuffer);

// write stream
ChunkStream binaryStream = new BinaryStream(stream, Guid.NewGuid().ToString(), new byte[16]);
ChunkStream binaryStream = new BinaryStream(stream, Guid.NewGuid().ToString());
await binaryStream.WriteAsync(BitConverter.GetBytes(writeBuffer.Length), cts.Token);
await binaryStream.WriteAsync((byte[])writeBuffer.Clone(), cts.Token);

Expand Down
3 changes: 1 addition & 2 deletions VpnHood.Client.App/ClientProfileStoreLegacy.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
using System.Text.Json;
using Microsoft.Extensions.Logging;
using VpnHood.Common;
using VpnHood.Common.Logging;
using VpnHood.Common.TokenLegacy;
using VpnHood.Common.Utils;

namespace VpnHood.Client.App;

// deprecated for version 3.3.450 or upper
// deprecated by version 3.2.440 or upper
internal static class ClientProfileStoreLegacy
{
// ReSharper disable once ClassNeverInstantiated.Local
Expand Down
4 changes: 3 additions & 1 deletion VpnHood.Client.App/VpnHoodApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ private VpnHoodApp(IAppProvider appProvider, AppOptions? options = default)
{
if (IsInit) throw new InvalidOperationException("VpnHoodApp is already initialized.");
options ??= new AppOptions();
#pragma warning disable CS0618 // Type or member is obsolete
MigrateUserDataFromXamarin(options.AppDataFolderPath); // Deprecated >= 400
#pragma warning restore CS0618 // Type or member is obsolete
Directory.CreateDirectory(options.AppDataFolderPath); //make sure directory exists
Resources = options.Resources;

Expand Down Expand Up @@ -668,7 +670,7 @@ public Task RunJob()
: ClientProfileService.FindById(LastActiveClientProfileId);
}

// Deprecated >= 400
[Obsolete("Deprecated >= 400", false)]
private static void MigrateUserDataFromXamarin(string folderPath)
{
try
Expand Down
4 changes: 3 additions & 1 deletion VpnHood.Client/ClientHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,10 @@ await Client.AddPassthruTcpStream(
var orgTcpClientStream = new TcpClientStream(orgTcpClient, orgTcpClient.GetStream(), request.RequestId + ":host");

// MaxEncryptChunk
if (proxyClientStream.Stream is BinaryStream binaryStream)
#pragma warning disable CS0618 // Type or member is obsolete
if (proxyClientStream.Stream is CryptoBinaryStream binaryStream)
binaryStream.MaxEncryptChunk = TunnelDefaults.TcpProxyEncryptChunkCount;
#pragma warning restore CS0618 // Type or member is obsolete

channel = new StreamProxyChannel(request.RequestId, orgTcpClientStream, proxyClientStream);
Client.Tunnel.AddChannel(channel);
Expand Down
35 changes: 24 additions & 11 deletions VpnHood.Client/ConnectorServices/ConnectorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Collections.Concurrent;
using System.Net.Sockets;
using System.Text;
using VpnHood.Common.Exceptions;
using VpnHood.Common.Logging;
using VpnHood.Common.Utils;
using VpnHood.Tunneling;
using VpnHood.Tunneling.Factory;
using VpnHood.Client.Exceptions;
using VpnHood.Tunneling.ClientStreams;
using System.Collections.Concurrent;
using VpnHood.Common.JobController;
using VpnHood.Common.Messaging;
using VpnHood.Tunneling.Messaging;
using VpnHood.Common.Collections;
using VpnHood.Tunneling.Channels.Streams;
using System.Text;
using VpnHood.Tunneling.Utils;
using System.Net.Sockets;

namespace VpnHood.Client.ConnectorServices;

Expand Down Expand Up @@ -52,31 +52,44 @@ public void Init(int serverProtocolVersion, TimeSpan tcpRequestTimeout, TimeSpan
ServerProtocolVersion = serverProtocolVersion;
RequestTimeout = tcpRequestTimeout;
TcpReuseTimeout = tcpReuseTimeout;
_apiKey = serverSecret != null ? HttpUtil.GetApiKey(serverSecret, TunnelDefaults.HttpPassCheck) : "";
_apiKey = serverSecret != null ? HttpUtil.GetApiKey(serverSecret, TunnelDefaults.HttpPassCheck) : string.Empty;
}

private async Task<IClientStream> CreateClientStream(TcpClient tcpClient, Stream sslStream, string streamId, CancellationToken cancellationToken)
{
var streamSecret = VhUtil.GenerateKey(128);
var streamSecret = VhUtil.GenerateKey(128); // deprecated by 450 and upper
var version = ServerProtocolVersion >= 5 ? 3 : 2;
var useBinaryStream = version >= 3 && !string.IsNullOrEmpty(_apiKey);

// write HTTP request
var header =
$"POST /{Guid.NewGuid()} HTTP/1.1\r\n" +
$"Authorization: ApiKey {_apiKey}\r\n" +
$"X-Version: 2\r\n" +
$"X-Version: {version}\r\n" +
$"X-Secret: {Convert.ToBase64String(streamSecret)}\r\n" +
$"X-BinaryStream: {useBinaryStream}\r\n" +
"\r\n";

// Send header and wait for its response
await sslStream.WriteAsync(Encoding.UTF8.GetBytes(header), cancellationToken); // secret
await HttpUtil.ReadHeadersAsync(sslStream, cancellationToken);

if (string.IsNullOrEmpty(_apiKey))
return new TcpClientStream(tcpClient, sslStream, streamId);
// deprecated legacy by version >= 450
#pragma warning disable CS0618 // Type or member is obsolete
if (version == 2)
{
if (string.IsNullOrEmpty(_apiKey))
return new TcpClientStream(tcpClient, sslStream, streamId);

// dispose Ssl
await sslStream.DisposeAsync();
return new TcpClientStream(tcpClient, new CryptoBinaryStream(tcpClient.GetStream(), streamId, streamSecret), streamId, ReuseStreamClient);
}
#pragma warning restore CS0618 // Type or member is obsolete

// dispose Ssl
await sslStream.DisposeAsync();
return new TcpClientStream(tcpClient, new BinaryStream(tcpClient.GetStream(), streamId, streamSecret), streamId, ReuseStreamClient);
return useBinaryStream
? new TcpClientStream(tcpClient, new BinaryStream(tcpClient.GetStream(), streamId), streamId, ReuseStreamClient)
: new TcpClientStream(tcpClient, sslStream, streamId);
}

private async Task<IClientStream> GetTlsConnectionToServer(string streamId, CancellationToken cancellationToken)
Expand Down
11 changes: 5 additions & 6 deletions VpnHood.Client/VpnHoodClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,9 @@ public class VpnHoodClient : IDisposable, IAsyncDisposable
private ClientUsageTracker? _clientUsageTracker;
private DateTime? _initConnectedTime;

private bool IsTcpDatagramLifespanSupported => ServerVersion?.Build >= 345; //will be deprecated
private DateTime? _lastConnectionErrorTime;
private byte[]? _sessionKey;
private byte[]? _serverKey;
private byte[]? _serverSecret;
private bool _useUdpChannel;
private ClientState _state = ClientState.None;
private int ProtocolVersion { get; }
Expand Down Expand Up @@ -547,7 +546,7 @@ private async Task ManageDatagramChannels(CancellationToken cancellationToken)
private async Task AddUdpChannel()
{
if (HostTcpEndPoint == null) throw new InvalidOperationException($"{nameof(HostTcpEndPoint)} is not initialized!");
if (VhUtil.IsNullOrEmpty(_serverKey)) throw new Exception("ServerSecret has not been set.");
if (VhUtil.IsNullOrEmpty(_serverSecret)) throw new Exception("ServerSecret has not been set.");
if (VhUtil.IsNullOrEmpty(_sessionKey)) throw new Exception("Server UdpKey has not been set.");
if (HostUdpEndPoint == null)
{
Expand All @@ -559,7 +558,7 @@ private async Task AddUdpChannel()
var udpChannel = new UdpChannel(SessionId, _sessionKey, false, _connectorService.ServerProtocolVersion);
try
{
var udpChannelTransmitter = new ClientUdpChannelTransmitter(udpChannel, udpClient, _serverKey);
var udpChannelTransmitter = new ClientUdpChannelTransmitter(udpChannel, udpClient, _serverSecret);
udpChannel.SetRemote(udpChannelTransmitter, HostUdpEndPoint);
Tunnel.AddChannel(udpChannel);
}
Expand Down Expand Up @@ -645,7 +644,7 @@ private async Task ConnectInternal(CancellationToken cancellationToken, bool red
// get session id
SessionId = sessionResponse.SessionId != 0 ? sessionResponse.SessionId : throw new Exception("Invalid SessionId!");
_sessionKey = sessionResponse.SessionKey;
_serverKey = sessionResponse.ServerSecret;
_serverSecret = sessionResponse.ServerSecret;
_helloTraffic = sessionResponse.AccessUsage?.Traffic ?? new Traffic();
SessionStatus.SuppressedTo = sessionResponse.SuppressedTo;
PublicAddress = sessionResponse.ClientPublicAddress;
Expand Down Expand Up @@ -715,7 +714,7 @@ private async Task AddTcpDatagramChannel(CancellationToken cancellationToken)
try
{
// find timespan
var lifespan = !VhUtil.IsInfinite(_maxTcpDatagramLifespan) && IsTcpDatagramLifespanSupported
var lifespan = !VhUtil.IsInfinite(_maxTcpDatagramLifespan)
? TimeSpan.FromSeconds(new Random().Next((int)_minTcpDatagramLifespan.TotalSeconds, (int)_maxTcpDatagramLifespan.TotalSeconds))
: Timeout.InfiniteTimeSpan;

Expand Down
55 changes: 33 additions & 22 deletions VpnHood.Server/ServerHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace VpnHood.Server;
internal class ServerHost : IAsyncDisposable, IJob
{
private readonly HashSet<IClientStream> _clientStreams = [];
private const int ServerProtocolVersion = 4;
private const int ServerProtocolVersion = 5;
private CancellationTokenSource _cancellationTokenSource = new();
private readonly SessionManager _sessionManager;
private readonly SslCertificateManager _sslCertificateManager;
Expand Down Expand Up @@ -267,6 +267,16 @@ await sslStream.AuthenticateAsServerAsync(
}
}

private bool CheckApiKeyAuthorization(string authorization)
{
var parts = authorization.Split(' ');
return
parts.Length >= 2 &&
parts[0].Equals("ApiKey", StringComparison.OrdinalIgnoreCase) &&
parts[1].Equals(_sessionManager.ApiKey, StringComparison.OrdinalIgnoreCase) &&
parts[1] == _sessionManager.ApiKey;
}

private async Task<IClientStream> CreateClientStream(TcpClient tcpClient, Stream sslStream, CancellationToken cancellationToken)
{
// check request version
Expand All @@ -279,40 +289,41 @@ private async Task<IClientStream> CreateClientStream(TcpClient tcpClient, Stream
await HttpUtil.ParseHeadersAsync(sslStream, cancellationToken)
?? throw new Exception("Connection has been closed before receiving any request.");

int.TryParse(headers.GetValueOrDefault("X-Version", "0"), out var xVersion);
bool.TryParse(headers.GetValueOrDefault("X-BinaryStream", "false"), out var useBinaryStream);
var authorization = headers.GetValueOrDefault("Authorization", string.Empty);

// read api key
var hasPassChecked = false;
if (headers.TryGetValue("Authorization", out var authorization))
if (!CheckApiKeyAuthorization(authorization))
{
var parts = authorization.Split(' ');
if (parts.Length >= 2 &&
parts[0].Equals("ApiKey", StringComparison.OrdinalIgnoreCase) &&
parts[1].Equals(_sessionManager.ApiKey, StringComparison.OrdinalIgnoreCase))
{
var apiKey = parts[1];
hasPassChecked = apiKey == _sessionManager.ApiKey;
}
// process hello without api key
if (authorization != "ApiKey")
throw new UnauthorizedAccessException();

await sslStream.WriteAsync(HttpResponses.GetUnauthorized(), cancellationToken);
return new TcpClientStream(tcpClient, sslStream, streamId);
}

if (hasPassChecked)
#pragma warning disable CS0618 // Type or member is obsolete
if (xVersion == 2)
{
if (headers.TryGetValue("X-Version", out var xVersion) && int.Parse(xVersion) == 2 &&
headers.TryGetValue("X-Secret", out var xSecret))
if (headers.TryGetValue("X-Secret", out var xSecret) && !string.IsNullOrEmpty(xSecret))
{
await sslStream.WriteAsync(HttpResponses.GetOk(), cancellationToken);
var secret = Convert.FromBase64String(xSecret);
await sslStream.DisposeAsync(); // dispose Ssl
return new TcpClientStream(tcpClient, new BinaryStream(tcpClient.GetStream(), streamId, secret), streamId, ReuseClientStream);
return new TcpClientStream(tcpClient, new CryptoBinaryStream(tcpClient.GetStream(), streamId, secret), streamId, ReuseClientStream);
}
}

// process hello without api key
if (authorization == "ApiKey")
{
await sslStream.WriteAsync(HttpResponses.GetUnauthorized(), cancellationToken);
return new TcpClientStream(tcpClient, sslStream, streamId);
return new TcpClientStream(tcpClient, new CryptoBinaryStream(tcpClient.GetStream(), streamId, Convert.FromBase64String(xSecret)), streamId, ReuseClientStream);
}
#pragma warning restore CS0618 // Type or member is obsolete

throw new UnauthorizedAccessException();
// use binary stream only for authenticated clients
await sslStream.WriteAsync(HttpResponses.GetOk(), cancellationToken);
return useBinaryStream
? new TcpClientStream(tcpClient, new BinaryStream(tcpClient.GetStream(), streamId), streamId, ReuseClientStream)
: new TcpClientStream(tcpClient, sslStream, streamId);
}
catch (Exception ex)
{
Expand Down
4 changes: 3 additions & 1 deletion VpnHood.Server/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,10 @@ await VhUtil.RunTask(
await StreamUtil.WriteJsonAsync(clientStream.Stream, SessionResponse, cancellationToken);

// MaxEncryptChunk
if (clientStream.Stream is BinaryStream binaryStream)
#pragma warning disable CS0618 // Type or member is obsolete
if (clientStream.Stream is CryptoBinaryStream binaryStream)
binaryStream.MaxEncryptChunk = TunnelDefaults.TcpProxyEncryptChunkCount;
#pragma warning restore CS0618 // Type or member is obsolete

// add the connection
VhLogger.Instance.LogTrace(GeneralEventId.StreamProxyChannel,
Expand Down
Loading

0 comments on commit ef38e76

Please sign in to comment.