diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj
index ef9406d0d..99c4d0705 100644
--- a/BTCPayServer/BTCPayServer.csproj
+++ b/BTCPayServer/BTCPayServer.csproj
@@ -2,7 +2,7 @@
Exe
netcoreapp2.1
- 1.0.2.88
+ 1.0.2.89
NU1701,CA1816,CA1308,CA1810,CA2208
diff --git a/BTCPayServer/Configuration/BTCPayServerOptions.cs b/BTCPayServer/Configuration/BTCPayServerOptions.cs
index 0b45c937a..2587e5fae 100644
--- a/BTCPayServer/Configuration/BTCPayServerOptions.cs
+++ b/BTCPayServer/Configuration/BTCPayServerOptions.cs
@@ -12,6 +12,7 @@ using Microsoft.Extensions.Configuration;
using NBXplorer;
using BTCPayServer.Payments.Lightning;
using Renci.SshNet;
+using NBitcoin.DataEncoders;
namespace BTCPayServer.Configuration
{
@@ -72,7 +73,7 @@ namespace BTCPayServer.Configuration
settings.Username = "root";
}
}
- else if(externalUrl != null)
+ else if (externalUrl != null)
{
settings.Port = 22;
settings.Username = "root";
@@ -182,7 +183,7 @@ namespace BTCPayServer.Configuration
ExternalUrl = conf.GetOrDefault("externalurl", null);
var sshSettings = SSHSettings.ParseConfiguration(conf);
- if (!string.IsNullOrEmpty(sshSettings.Password) || !string.IsNullOrEmpty(sshSettings.KeyFile))
+ if ((!string.IsNullOrEmpty(sshSettings.Password) || !string.IsNullOrEmpty(sshSettings.KeyFile)) && !string.IsNullOrEmpty(sshSettings.Server))
{
if (!string.IsNullOrEmpty(sshSettings.KeyFile) && !File.Exists(sshSettings.KeyFile))
throw new ConfigException($"sshkeyfile does not exist");
@@ -199,10 +200,19 @@ namespace BTCPayServer.Configuration
{
throw new ConfigException($"sshkeyfilepassword is invalid");
}
-
SSHSettings = sshSettings;
}
+ var fingerPrints = conf.GetOrDefault("sshtrustedfingerprints", "");
+ if (!string.IsNullOrEmpty(fingerPrints))
+ {
+ foreach (var fingerprint in fingerPrints.Split(';', StringSplitOptions.RemoveEmptyEntries)
+ .Select(str => str.Replace(":", "", StringComparison.OrdinalIgnoreCase)))
+ {
+ TrustedFingerprints.Add(DecodeFingerprint(fingerprint));
+ }
+ }
+
RootPath = conf.GetOrDefault("rootpath", "/");
if (!RootPath.StartsWith("/", StringComparison.InvariantCultureIgnoreCase))
RootPath = "/" + RootPath;
@@ -210,6 +220,45 @@ namespace BTCPayServer.Configuration
if (old != null)
throw new ConfigException($"internallightningnode should not be used anymore, use btclightning instead");
}
+
+ private static byte[] DecodeFingerprint(string fingerprint)
+ {
+ try
+ {
+ return Encoders.Hex.DecodeData(fingerprint.Trim());
+ }
+ catch
+ {
+ }
+
+ var localFingerprint = fingerprint;
+ if (localFingerprint.StartsWith("SHA256", StringComparison.OrdinalIgnoreCase))
+ localFingerprint = localFingerprint.Substring("SHA256".Length).Trim();
+ try
+ {
+ return Encoders.Base64.DecodeData(localFingerprint);
+ }
+ catch
+ {
+ }
+
+ if (!localFingerprint.EndsWith('='))
+ localFingerprint = localFingerprint + "=";
+ try
+ {
+ return Encoders.Base64.DecodeData(localFingerprint);
+ }
+ catch
+ {
+ throw new ConfigException($"sshtrustedfingerprints is invalid");
+ }
+ }
+
+ internal bool IsTrustedFingerprint(byte[] fingerPrint)
+ {
+ return TrustedFingerprints.Any(f => Utils.ArrayEqual(f, fingerPrint));
+ }
+
public string RootPath { get; set; }
public Dictionary InternalLightningByCryptoCode { get; set; } = new Dictionary();
public ExternalServices ExternalServicesByCryptoCode { get; set; } = new ExternalServices();
@@ -230,6 +279,7 @@ namespace BTCPayServer.Configuration
get;
set;
}
+ public List TrustedFingerprints { get; set; } = new List();
public SSHSettings SSHSettings
{
get;
diff --git a/BTCPayServer/Configuration/DefaultConfiguration.cs b/BTCPayServer/Configuration/DefaultConfiguration.cs
index dd68a924d..62b544f9f 100644
--- a/BTCPayServer/Configuration/DefaultConfiguration.cs
+++ b/BTCPayServer/Configuration/DefaultConfiguration.cs
@@ -38,6 +38,7 @@ namespace BTCPayServer.Configuration
app.Option("--sshpassword", "SSH password to manage BTCPay (default: empty)", CommandOptionType.SingleValue);
app.Option("--sshkeyfile", "SSH private key file to manage BTCPay (default: empty)", CommandOptionType.SingleValue);
app.Option("--sshkeyfilepassword", "Password of the SSH keyfile (default: empty)", CommandOptionType.SingleValue);
+ app.Option("--sshtrustedfingerprints", "SSH Host SHA256 rsa fingerprint in base64 or hex (default: empty, it will allow untrusted connections)", CommandOptionType.SingleValue);
foreach (var network in provider.GetAll())
{
var crypto = network.CryptoCode.ToLowerInvariant();
diff --git a/BTCPayServer/Controllers/ServerController.cs b/BTCPayServer/Controllers/ServerController.cs
index 240f1e36b..baae1d691 100644
--- a/BTCPayServer/Controllers/ServerController.cs
+++ b/BTCPayServer/Controllers/ServerController.cs
@@ -260,6 +260,29 @@ namespace BTCPayServer.Controllers
ssh = $"sudo bash -c '. /etc/profile.d/btcpay-env.sh && nohup {ssh} > /dev/null 2>&1 & disown'";
var sshClient = _Options.SSHSettings == null ? vm.CreateSSHClient(this.Request.Host.Host)
: new SshClient(_Options.SSHSettings.CreateConnectionInfo());
+
+ if (_Options.TrustedFingerprints.Count != 0)
+ {
+ sshClient.HostKeyReceived += (object sender, Renci.SshNet.Common.HostKeyEventArgs e) =>
+ {
+ if (_Options.TrustedFingerprints.Count == 0)
+ {
+ Logs.Configuration.LogWarning($"SSH host fingerprint for {e.HostKey} is untrusted, start BTCPay with -sshtrustedfingerprints \"{Encoders.Hex.EncodeData(e.FingerPrint)}\"");
+ e.CanTrust = true; // Not a typo, we want the connection to succeed with a warning
+ }
+ else
+ {
+ e.CanTrust = _Options.IsTrustedFingerprint(e.FingerPrint);
+ if(!e.CanTrust)
+ Logs.Configuration.LogError($"SSH host fingerprint for {e.HostKey} is untrusted, start BTCPay with -sshtrustedfingerprints \"{Encoders.Hex.EncodeData(e.FingerPrint)}\"");
+ }
+ };
+ }
+ else
+ {
+
+ }
+
try
{
sshClient.Connect();
diff --git a/BTCPayServer/HostedServices/CheckConfigurationHostedService.cs b/BTCPayServer/HostedServices/CheckConfigurationHostedService.cs
index ad6d38e18..958705958 100644
--- a/BTCPayServer/HostedServices/CheckConfigurationHostedService.cs
+++ b/BTCPayServer/HostedServices/CheckConfigurationHostedService.cs
@@ -10,6 +10,7 @@ using Microsoft.Extensions.Hosting;
using System.Threading;
using BTCPayServer.Configuration;
using BTCPayServer.Logging;
+using NBitcoin.DataEncoders;
namespace BTCPayServer.HostedServices
{
@@ -30,6 +31,14 @@ namespace BTCPayServer.HostedServices
{
Logs.Configuration.LogInformation($"SSH settings detected, testing connection to {_options.SSHSettings.Username}@{_options.SSHSettings.Server} on port {_options.SSHSettings.Port} ...");
var connection = new Renci.SshNet.SshClient(_options.SSHSettings.CreateConnectionInfo());
+ connection.HostKeyReceived += (object sender, Renci.SshNet.Common.HostKeyEventArgs e) =>
+ {
+ e.CanTrust = true;
+ if (!_options.IsTrustedFingerprint(e.FingerPrint))
+ {
+ Logs.Configuration.LogWarning($"SSH host fingerprint for {e.HostKey} is untrusted, start BTCPay with -sshtrustedfingerprints \"{Encoders.Hex.EncodeData(e.FingerPrint)}\"");
+ }
+ };
try
{
connection.Connect();