/*
* Copyright (c) 2023 Proton AG
*
* This file is part of ProtonVPN.
*
* ProtonVPN is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonVPN is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonVPN. If not, see .
*/
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NSubstitute;
using ProtonVPN.Common.Core.Networking;
using ProtonVPN.Common.Legacy.Vpn;
using ProtonVPN.Configurations.Contracts;
using ProtonVPN.Crypto.Contracts;
using ProtonVPN.Logging.Contracts;
using ProtonVPN.Vpn.ServerValidation;
namespace ProtonVPN.Vpn.Tests.ServerValidation;
[TestClass]
public class ServerValidatorTest
{
private const string CONFIG_SERVER_VALIDATION_PUBLIC_KEY = "ServerValidationPublicKey";
private const string SERVER_NAME = "protonvpn.com";
private const string SERVER_IP = "192.168.1.1";
private const string SERVER_LABEL = "99";
private const string SERVER_PUBLIC_KEY_BASE_64 = "U2VydmVyUHVibGljS2V5QmFzZTY0IFByb3RvblZQTg==";
private const string SERVER_SIGNATURE = "TestSignature";
private ILogger _logger;
private IConfiguration _config;
private IEd25519SignatureValidator _ed25519SignatureValidator;
private ServerValidator _serverValidator;
[TestInitialize]
public void Initialize()
{
_logger = Substitute.For();
_config = Substitute.For();
_ed25519SignatureValidator = Substitute.For();
_serverValidator = new(_logger, _config, _ed25519SignatureValidator);
}
[TestCleanup]
public void Cleanup()
{
_logger = null;
_config = null;
_ed25519SignatureValidator = null;
_serverValidator = null;
}
[TestMethod]
public void TestValidate()
{
SetServerValidationPublicKeyConfig();
SetValidSignature($"{{\"Server\":{{\"EntryIP\":\"{SERVER_IP}\",\"Label\":\"{SERVER_LABEL}\"}}}}");
VpnHost server = CreateVpnHost();
VpnError error = _serverValidator.Validate(server);
error.Should().Be(VpnError.None);
}
private void SetValidSignature(string expectedData)
{
_ed25519SignatureValidator.IsValid(
Arg.Is(expectedData),
Arg.Is(SERVER_SIGNATURE),
Arg.Is(CONFIG_SERVER_VALIDATION_PUBLIC_KEY)
).Returns(true);
}
private void SetServerValidationPublicKeyConfig()
{
_config.ServerValidationPublicKey.Returns(CONFIG_SERVER_VALIDATION_PUBLIC_KEY);
}
private VpnHost CreateVpnHost()
{
return new(
name: SERVER_NAME,
ip: SERVER_IP,
label: SERVER_LABEL,
x25519PublicKey: CreatePublicKey(),
signature: SERVER_SIGNATURE,
isIpv6Supported: false,
relayIpByProtocol: null);
}
private PublicKey CreatePublicKey()
{
return new PublicKey(SERVER_PUBLIC_KEY_BASE_64, KeyAlgorithm.X25519);
}
[TestMethod]
public void TestValidate_WithNullLabel()
{
SetServerValidationPublicKeyConfig();
SetValidSignature($"{{\"Server\":{{\"EntryIP\":\"{SERVER_IP}\",\"Label\":null}}}}");
VpnHost server = new(
name: SERVER_NAME,
ip: SERVER_IP,
label: null,
x25519PublicKey: CreatePublicKey(),
signature: SERVER_SIGNATURE,
isIpv6Supported: false,
relayIpByProtocol: null);
VpnError error = _serverValidator.Validate(server);
error.Should().Be(VpnError.None);
}
[TestMethod]
public void TestValidate_WithoutPublicKey()
{
SetServerValidationPublicKeyConfig();
SetValidSignature($"{{\"Server\":{{\"EntryIP\":\"{SERVER_IP}\",\"Label\":\"{SERVER_LABEL}\"}}}}");
VpnHost server = new(
name: SERVER_NAME,
ip: SERVER_IP,
label: SERVER_LABEL,
x25519PublicKey: null,
signature: SERVER_SIGNATURE,
isIpv6Supported: false,
relayIpByProtocol: null);
VpnError error = _serverValidator.Validate(server);
error.Should().Be(VpnError.None);
}
[TestMethod]
public void TestValidate_X25519PublicKeyIsTooShort()
{
VpnHost server = new(
name: SERVER_NAME,
ip: SERVER_IP,
label: SERVER_LABEL,
x25519PublicKey: new PublicKey(new byte[1] { 1 }, KeyAlgorithm.X25519),
signature: SERVER_SIGNATURE,
isIpv6Supported: false,
relayIpByProtocol: null);
VpnError error = _serverValidator.Validate(server);
error.Should().Be(VpnError.ServerValidationError);
}
[TestMethod]
public void TestValidate_SignatureIsNull()
{
VpnHost server = CreateVpnHostBySignature(null);
VpnError error = _serverValidator.Validate(server);
error.Should().Be(VpnError.ServerValidationError);
}
private VpnHost CreateVpnHostBySignature(string signature)
{
return new(
name: SERVER_NAME,
ip: SERVER_IP,
label: SERVER_LABEL,
x25519PublicKey: CreatePublicKey(),
signature: signature,
isIpv6Supported: false,
relayIpByProtocol: null);
}
[TestMethod]
public void TestValidate_SignatureIsEmpty()
{
VpnHost server = CreateVpnHostBySignature(string.Empty);
VpnError error = _serverValidator.Validate(server);
error.Should().Be(VpnError.ServerValidationError);
}
[TestMethod]
public void TestValidate_SignatureIsWhitespace()
{
VpnHost server = CreateVpnHostBySignature(" ");
VpnError error = _serverValidator.Validate(server);
error.Should().Be(VpnError.ServerValidationError);
}
[TestMethod]
public void TestValidate_ConfigIsNull()
{
_config.ServerValidationPublicKey.Returns((string)null);
VpnHost server = CreateVpnHost();
VpnError error = _serverValidator.Validate(server);
error.Should().Be(VpnError.NoServerValidationPublicKey);
}
[TestMethod]
public void TestValidate_ConfigIsEmpty()
{
_config.ServerValidationPublicKey.Returns(string.Empty);
VpnHost server = CreateVpnHost();
VpnError error = _serverValidator.Validate(server);
error.Should().Be(VpnError.NoServerValidationPublicKey);
}
[TestMethod]
public void TestValidate_ConfigIsWhitespace()
{
_config.ServerValidationPublicKey.Returns(" ");
VpnHost server = CreateVpnHost();
VpnError error = _serverValidator.Validate(server);
error.Should().Be(VpnError.NoServerValidationPublicKey);
}
[TestMethod]
public void TestValidate_SignatureIsInvalid()
{
SetServerValidationPublicKeyConfig();
VpnHost server = CreateVpnHost();
VpnError error = _serverValidator.Validate(server);
error.Should().Be(VpnError.ServerValidationError);
}
}