mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-09-10 01:05:42 +00:00
Add support for OpenSSH certificates (#1498)
Co-authored-by: cedricMicrovision <cedric.legoff@microvision.fr>
This commit is contained in:
+28
-1
@@ -59,12 +59,39 @@ using (var client = new SshClient("sftp.foo.com", "guest", "pwd"))
|
||||
{
|
||||
client.HostKeyReceived += (sender, e) =>
|
||||
{
|
||||
e.CanTrust = expectedFingerPrint.Equals(e.FingerPrintSHA256);
|
||||
e.CanTrust = e.FingerPrintSHA256 == expectedFingerPrint;
|
||||
};
|
||||
client.Connect();
|
||||
}
|
||||
```
|
||||
|
||||
When expecting the server to present a certificate signed by a trusted certificate authority:
|
||||
|
||||
```cs
|
||||
string expectedCAFingerPrint = "tF3DRTUXtYFZ5Yz0SBOrEbixHaCifHmNVK6FtptXZVM";
|
||||
|
||||
using (var client = new SshClient("sftp.foo.com", "guest", "pwd"))
|
||||
{
|
||||
client.HostKeyReceived += (sender, e) =>
|
||||
{
|
||||
e.CanTrust = e.Certificate?.CertificateAuthorityKeyFingerPrint == expectedCAFingerPrint;
|
||||
};
|
||||
client.Connect();
|
||||
}
|
||||
```
|
||||
|
||||
### Authenticating with a user certificate
|
||||
|
||||
When you have a certificate for your key which is signed by a certificate authority that the server trusts:
|
||||
|
||||
```cs
|
||||
using (var privateKeyFile = new PrivateKeyFile("path/to/my/key", passPhrase: null, "path/to/my/certificate.pub"))
|
||||
using (var client = new SshClient("sftp.foo.com", "guest", privateKeyFile))
|
||||
{
|
||||
client.Connect();
|
||||
}
|
||||
```
|
||||
|
||||
### Open a Shell
|
||||
|
||||
```cs
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
using System;
|
||||
|
||||
using Renci.SshNet.Abstractions;
|
||||
using Renci.SshNet.Security;
|
||||
@@ -83,6 +84,12 @@ namespace Renci.SshNet.Common
|
||||
/// </value>
|
||||
public int KeyLength { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the certificate presented by the host, or <see langword="null"/> if the host
|
||||
/// did not present a certificate.
|
||||
/// </summary>
|
||||
public Certificate? Certificate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HostKeyEventArgs"/> class.
|
||||
/// </summary>
|
||||
@@ -93,7 +100,7 @@ namespace Renci.SshNet.Common
|
||||
ThrowHelper.ThrowIfNull(host);
|
||||
|
||||
CanTrust = true;
|
||||
HostKey = host.Data;
|
||||
HostKey = host.KeyData.GetBytes();
|
||||
HostKeyName = host.Name;
|
||||
KeyLength = host.Key.KeyLength;
|
||||
|
||||
@@ -107,6 +114,11 @@ namespace Renci.SshNet.Common
|
||||
return BitConverter.ToString(FingerPrint).Replace('-', ':').ToLowerInvariant();
|
||||
#pragma warning restore CA1308 // Normalize strings to uppercase
|
||||
});
|
||||
|
||||
if (host is CertificateHostAlgorithm certificateAlg)
|
||||
{
|
||||
Certificate = certificateAlg.Certificate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,19 +387,26 @@ namespace Renci.SshNet
|
||||
{ "hmac-sha1-etm@openssh.com", new HashInfo(20*8, key => new HMACSHA1(key), isEncryptThenMAC: true) },
|
||||
};
|
||||
|
||||
HostKeyAlgorithms = new Dictionary<string, Func<byte[], KeyHostAlgorithm>>
|
||||
{
|
||||
{ "ssh-ed25519", data => new KeyHostAlgorithm("ssh-ed25519", new ED25519Key(new SshKeyData(data))) },
|
||||
{ "ecdsa-sha2-nistp256", data => new KeyHostAlgorithm("ecdsa-sha2-nistp256", new EcdsaKey(new SshKeyData(data))) },
|
||||
{ "ecdsa-sha2-nistp384", data => new KeyHostAlgorithm("ecdsa-sha2-nistp384", new EcdsaKey(new SshKeyData(data))) },
|
||||
{ "ecdsa-sha2-nistp521", data => new KeyHostAlgorithm("ecdsa-sha2-nistp521", new EcdsaKey(new SshKeyData(data))) },
|
||||
#pragma warning disable SA1107 // Code should not contain multiple statements on one line
|
||||
{ "rsa-sha2-512", data => { var key = new RsaKey(new SshKeyData(data)); return new KeyHostAlgorithm("rsa-sha2-512", key, new RsaDigitalSignature(key, HashAlgorithmName.SHA512)); } },
|
||||
{ "rsa-sha2-256", data => { var key = new RsaKey(new SshKeyData(data)); return new KeyHostAlgorithm("rsa-sha2-256", key, new RsaDigitalSignature(key, HashAlgorithmName.SHA256)); } },
|
||||
var hostAlgs = new Dictionary<string, Func<byte[], KeyHostAlgorithm>>();
|
||||
hostAlgs.Add("ssh-ed25519-cert-v01@openssh.com", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("ssh-ed25519-cert-v01@openssh.com", cert, hostAlgs); });
|
||||
hostAlgs.Add("ecdsa-sha2-nistp256-cert-v01@openssh.com", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("ecdsa-sha2-nistp256-cert-v01@openssh.com", cert, hostAlgs); });
|
||||
hostAlgs.Add("ecdsa-sha2-nistp384-cert-v01@openssh.com", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("ecdsa-sha2-nistp384-cert-v01@openssh.com", cert, hostAlgs); });
|
||||
hostAlgs.Add("ecdsa-sha2-nistp521-cert-v01@openssh.com", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("ecdsa-sha2-nistp521-cert-v01@openssh.com", cert, hostAlgs); });
|
||||
hostAlgs.Add("rsa-sha2-512-cert-v01@openssh.com", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("rsa-sha2-512-cert-v01@openssh.com", cert, new RsaDigitalSignature((RsaKey)cert.Key, HashAlgorithmName.SHA512), hostAlgs); });
|
||||
hostAlgs.Add("rsa-sha2-256-cert-v01@openssh.com", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("rsa-sha2-256-cert-v01@openssh.com", cert, new RsaDigitalSignature((RsaKey)cert.Key, HashAlgorithmName.SHA256), hostAlgs); });
|
||||
hostAlgs.Add("ssh-rsa-cert-v01@openssh.com", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("ssh-rsa-cert-v01@openssh.com", cert, hostAlgs); });
|
||||
hostAlgs.Add("ssh-dss-cert-v01@openssh.com", data => { var cert = new Certificate(data); return new CertificateHostAlgorithm("ssh-dss-cert-v01@openssh.com", cert, hostAlgs); });
|
||||
hostAlgs.Add("ssh-ed25519", data => new KeyHostAlgorithm("ssh-ed25519", new ED25519Key(new SshKeyData(data))));
|
||||
hostAlgs.Add("ecdsa-sha2-nistp256", data => new KeyHostAlgorithm("ecdsa-sha2-nistp256", new EcdsaKey(new SshKeyData(data))));
|
||||
hostAlgs.Add("ecdsa-sha2-nistp384", data => new KeyHostAlgorithm("ecdsa-sha2-nistp384", new EcdsaKey(new SshKeyData(data))));
|
||||
hostAlgs.Add("ecdsa-sha2-nistp521", data => new KeyHostAlgorithm("ecdsa-sha2-nistp521", new EcdsaKey(new SshKeyData(data))));
|
||||
hostAlgs.Add("rsa-sha2-512", data => { var key = new RsaKey(new SshKeyData(data)); return new KeyHostAlgorithm("rsa-sha2-512", key, new RsaDigitalSignature(key, HashAlgorithmName.SHA512)); });
|
||||
hostAlgs.Add("rsa-sha2-256", data => { var key = new RsaKey(new SshKeyData(data)); return new KeyHostAlgorithm("rsa-sha2-256", key, new RsaDigitalSignature(key, HashAlgorithmName.SHA256)); });
|
||||
hostAlgs.Add("ssh-rsa", data => new KeyHostAlgorithm("ssh-rsa", new RsaKey(new SshKeyData(data))));
|
||||
hostAlgs.Add("ssh-dss", data => new KeyHostAlgorithm("ssh-dss", new DsaKey(new SshKeyData(data))));
|
||||
#pragma warning restore SA1107 // Code should not contain multiple statements on one line
|
||||
{ "ssh-rsa", data => new KeyHostAlgorithm("ssh-rsa", new RsaKey(new SshKeyData(data))) },
|
||||
{ "ssh-dss", data => new KeyHostAlgorithm("ssh-dss", new DsaKey(new SshKeyData(data))) },
|
||||
};
|
||||
HostKeyAlgorithms = hostAlgs;
|
||||
|
||||
CompressionAlgorithms = new Dictionary<string, Func<Compressor>>
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Formats.Asn1;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
@@ -119,15 +120,20 @@ namespace Renci.SshNet
|
||||
public partial class PrivateKeyFile : IPrivateKeySource, IDisposable
|
||||
{
|
||||
private const string PrivateKeyPattern = @"^-+ *BEGIN (?<keyName>\w+( \w+)*) *-+\r?\n((Proc-Type: 4,ENCRYPTED\r?\nDEK-Info: (?<cipherName>[A-Z0-9-]+),(?<salt>[A-F0-9]+)\r?\n\r?\n)|(Comment: ""?[^\r\n]*""?\r?\n))?(?<data>([a-zA-Z0-9/+=]{1,80}\r?\n)+)(\r?\n)?-+ *END \k<keyName> *-+";
|
||||
private const string CertificatePattern = @"(?<type>[-\w]+@openssh\.com)\s(?<data>[a-zA-Z0-9\/+=]*)(\s+(?<comment>.*))?";
|
||||
|
||||
#if NET7_0_OR_GREATER
|
||||
private static readonly Regex PrivateKeyRegex = GetPrivateKeyRegex();
|
||||
private static readonly Regex CertificateRegex = GetCertificateRegex();
|
||||
|
||||
[GeneratedRegex(PrivateKeyPattern, RegexOptions.Multiline | RegexOptions.ExplicitCapture)]
|
||||
private static partial Regex GetPrivateKeyRegex();
|
||||
|
||||
[GeneratedRegex(CertificatePattern, RegexOptions.ExplicitCapture)]
|
||||
private static partial Regex GetCertificateRegex();
|
||||
#else
|
||||
private static readonly Regex PrivateKeyRegex = new Regex(PrivateKeyPattern,
|
||||
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.ExplicitCapture);
|
||||
private static readonly Regex PrivateKeyRegex = new Regex(PrivateKeyPattern, RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.ExplicitCapture);
|
||||
private static readonly Regex CertificateRegex = new Regex(CertificatePattern, RegexOptions.Compiled | RegexOptions.ExplicitCapture);
|
||||
#endif
|
||||
|
||||
private readonly List<HostAlgorithm> _hostAlgorithms = new List<HostAlgorithm>();
|
||||
@@ -156,6 +162,13 @@ namespace Renci.SshNet
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the public key certificate associated with this key,
|
||||
/// or <see langword="null"/> if no certificate data
|
||||
/// has been passed to the constructor.
|
||||
/// </summary>
|
||||
public Certificate? Certificate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.
|
||||
/// </summary>
|
||||
@@ -173,7 +186,7 @@ namespace Renci.SshNet
|
||||
/// </summary>
|
||||
/// <param name="privateKey">The private key.</param>
|
||||
public PrivateKeyFile(Stream privateKey)
|
||||
: this(privateKey, passPhrase: null)
|
||||
: this(privateKey, passPhrase: null, certificate: null)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -186,7 +199,7 @@ namespace Renci.SshNet
|
||||
/// This method calls <see cref="File.Open(string, FileMode)"/> internally, this method does not catch exceptions from <see cref="File.Open(string, FileMode)"/>.
|
||||
/// </remarks>
|
||||
public PrivateKeyFile(string fileName)
|
||||
: this(fileName, passPhrase: null)
|
||||
: this(fileName, passPhrase: null, certificateFileName: null)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -200,6 +213,18 @@ namespace Renci.SshNet
|
||||
/// This method calls <see cref="File.Open(string, FileMode)"/> internally, this method does not catch exceptions from <see cref="File.Open(string, FileMode)"/>.
|
||||
/// </remarks>
|
||||
public PrivateKeyFile(string fileName, string? passPhrase)
|
||||
: this(fileName, passPhrase, certificateFileName: null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.
|
||||
/// </summary>
|
||||
/// <param name="fileName">The path of the private key file.</param>
|
||||
/// <param name="passPhrase">The pass phrase for the private key.</param>
|
||||
/// <param name="certificateFileName">The path of a certificate file which certifies the private key.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="fileName"/> is <see langword="null"/>.</exception>
|
||||
public PrivateKeyFile(string fileName, string? passPhrase, string? certificateFileName)
|
||||
{
|
||||
ThrowHelper.ThrowIfNull(fileName);
|
||||
|
||||
@@ -208,6 +233,16 @@ namespace Renci.SshNet
|
||||
Open(keyFile, passPhrase);
|
||||
}
|
||||
|
||||
if (certificateFileName is not null)
|
||||
{
|
||||
using (var certificateFile = File.OpenRead(certificateFileName))
|
||||
{
|
||||
OpenCertificate(certificateFile);
|
||||
}
|
||||
|
||||
Debug.Assert(Certificate is not null, $"{nameof(Certificate)} is null.");
|
||||
}
|
||||
|
||||
Debug.Assert(Key is not null, $"{nameof(Key)} is null.");
|
||||
Debug.Assert(HostKeyAlgorithms.Count > 0, $"{nameof(HostKeyAlgorithms)} is not set.");
|
||||
}
|
||||
@@ -219,11 +254,29 @@ namespace Renci.SshNet
|
||||
/// <param name="passPhrase">The pass phrase.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="privateKey"/> is <see langword="null"/>.</exception>
|
||||
public PrivateKeyFile(Stream privateKey, string? passPhrase)
|
||||
: this(privateKey, passPhrase, certificate: null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.
|
||||
/// </summary>
|
||||
/// <param name="privateKey">The private key.</param>
|
||||
/// <param name="passPhrase">The pass phrase for the private key.</param>
|
||||
/// <param name="certificate">A certificate which certifies the private key.</param>
|
||||
public PrivateKeyFile(Stream privateKey, string? passPhrase, Stream? certificate)
|
||||
{
|
||||
ThrowHelper.ThrowIfNull(privateKey);
|
||||
|
||||
Open(privateKey, passPhrase);
|
||||
|
||||
if (certificate is not null)
|
||||
{
|
||||
OpenCertificate(certificate);
|
||||
|
||||
Debug.Assert(Certificate is not null, $"{nameof(Certificate)} is null.");
|
||||
}
|
||||
|
||||
Debug.Assert(Key is not null, $"{nameof(Key)} is null.");
|
||||
Debug.Assert(HostKeyAlgorithms.Count > 0, $"{nameof(HostKeyAlgorithms)} is not set.");
|
||||
}
|
||||
@@ -854,6 +907,65 @@ namespace Renci.SshNet
|
||||
throw new SshException(string.Format(CultureInfo.InvariantCulture, "Private key algorithm \"{0}\" is not supported.", algorithmOid));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the specified certificate.
|
||||
/// </summary>
|
||||
/// <param name="certificate">The certificate.</param>
|
||||
private void OpenCertificate(Stream certificate)
|
||||
{
|
||||
Debug.Assert(certificate is not null, "Should have validated not-null in the constructor.");
|
||||
|
||||
Match certificateMatch;
|
||||
|
||||
using (var sr = new StreamReader(certificate))
|
||||
{
|
||||
var text = sr.ReadToEnd();
|
||||
certificateMatch = CertificateRegex.Match(text);
|
||||
}
|
||||
|
||||
if (!certificateMatch.Success)
|
||||
{
|
||||
throw new SshException("Invalid certificate file.");
|
||||
}
|
||||
|
||||
var data = certificateMatch.Result("${data}");
|
||||
|
||||
Certificate = new Certificate(Convert.FromBase64String(data));
|
||||
|
||||
Debug.Assert(Key is not null, $"{nameof(Key)} should have been initialised already.");
|
||||
|
||||
if (!Certificate.Key.Public.SequenceEqual(Key.Public))
|
||||
{
|
||||
throw new ArgumentException("The supplied certificate does not certify the supplied key.");
|
||||
}
|
||||
|
||||
if (Key is RsaKey rsaKey)
|
||||
{
|
||||
Debug.Assert(Certificate.Key is RsaKey,
|
||||
$"Expected {nameof(Certificate)}.{nameof(Certificate.Key)} to be {nameof(RsaKey)} but was {Certificate.Key?.GetType()}");
|
||||
|
||||
_hostAlgorithms.Insert(0, new CertificateHostAlgorithm("ssh-rsa-cert-v01@openssh.com", Key, Certificate));
|
||||
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
_hostAlgorithms.Insert(0, new CertificateHostAlgorithm(
|
||||
"rsa-sha2-256-cert-v01@openssh.com",
|
||||
Key,
|
||||
Certificate,
|
||||
new RsaDigitalSignature(rsaKey, HashAlgorithmName.SHA256)));
|
||||
|
||||
_hostAlgorithms.Insert(0, new CertificateHostAlgorithm(
|
||||
"rsa-sha2-512-cert-v01@openssh.com",
|
||||
Key,
|
||||
Certificate,
|
||||
new RsaDigitalSignature(rsaKey, HashAlgorithmName.SHA512)));
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
}
|
||||
else
|
||||
{
|
||||
_hostAlgorithms.Insert(0, new CertificateHostAlgorithm(Certificate.Name, Key, Certificate));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,432 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
using Renci.SshNet.Abstractions;
|
||||
using Renci.SshNet.Common;
|
||||
|
||||
namespace Renci.SshNet.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an OpenSSH certificate as described in
|
||||
/// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.certkeys.
|
||||
/// </summary>
|
||||
// The xmldoc comments in the class are mostly lifted from the linked document.
|
||||
#pragma warning disable SA1623 // Property summary documentation should match accessors; for the above reason
|
||||
public class Certificate
|
||||
{
|
||||
/// <summary>
|
||||
/// The type identifier of the certificate.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value is one of the following:
|
||||
/// <list type="bullet">
|
||||
/// <item>ssh-rsa-cert-v01@openssh.com</item>
|
||||
/// <item>ssh-dss-cert-v01@openssh.com</item>
|
||||
/// <item>ecdsa-sha2-nistp256-cert-v01@openssh.com</item>
|
||||
/// <item>ecdsa-sha2-nistp384-cert-v01@openssh.com</item>
|
||||
/// <item>ecdsa-sha2-nistp521-cert-v01@openssh.com</item>
|
||||
/// <item>ssh-ed25519-cert-v01@openssh.com</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.Name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A CA-provided random bitstring of arbitrary length
|
||||
/// (but typically 16 or 32 bytes) included to make attacks that depend on
|
||||
/// inducing collisions in the signature hash infeasible.
|
||||
/// </summary>
|
||||
public byte[] Nonce
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.Nonce;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The public key that has been certified by the certificate authority.
|
||||
/// </summary>
|
||||
public Key Key
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.Key;
|
||||
}
|
||||
}
|
||||
|
||||
internal SshKeyData KeyData
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.KeyData;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An optional certificate serial number set by the CA to
|
||||
/// provide an abbreviated way to refer to certificates from that CA.
|
||||
/// If a CA does not wish to number its certificates, it must set this
|
||||
/// field to zero.
|
||||
/// </summary>
|
||||
public ulong Serial
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.Serial;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether this certificate is for identification of a user
|
||||
/// or a host.
|
||||
/// </summary>
|
||||
public CertificateType Type
|
||||
{
|
||||
get
|
||||
{
|
||||
return (CertificateType)_data.Type;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A free-form text field that is filled in by the CA at the time
|
||||
/// of signing; the intention is that the contents of this field are used to
|
||||
/// identify the identity principal in log messages.
|
||||
/// </summary>
|
||||
public string KeyId
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.KeyId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The names for which this certificate is valid;
|
||||
/// hostnames for SSH_CERT_TYPE_HOST certificates and
|
||||
/// usernames for SSH_CERT_TYPE_USER certificates. As a special case, a
|
||||
/// zero-length "valid principals" field means the certificate is valid for
|
||||
/// any principal of the specified type.
|
||||
/// </summary>
|
||||
public IList<string> ValidPrincipals
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.ValidPrincipals;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The beginning of the validity period of the certificate, as the number
|
||||
/// of seconds elapsed since 1970-01-01T00:00:00Z.
|
||||
/// </summary>
|
||||
/// <seealso cref="ValidAfter"/>
|
||||
public ulong ValidAfterUnixSeconds
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.ValidAfter;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The beginning of the validity period of the certificate.
|
||||
/// </summary>
|
||||
public DateTimeOffset ValidAfter
|
||||
{
|
||||
get
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeSeconds((long)_data.ValidAfter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The end of the validity period of the certificate, as the number
|
||||
/// of seconds elapsed since 1970-01-01T00:00:00Z.
|
||||
/// </summary>
|
||||
public ulong ValidBeforeUnixSeconds
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.ValidBefore;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The end of the validity period of the certificate.
|
||||
/// </summary>
|
||||
public DateTimeOffset ValidBefore
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.ValidBefore == ulong.MaxValue
|
||||
? DateTimeOffset.MaxValue
|
||||
: DateTimeOffset.FromUnixTimeSeconds((long)_data.ValidBefore);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A set of zero or more options on the certificate's validity.
|
||||
/// The key identifies the option and the value encodes
|
||||
/// option-specific information.
|
||||
/// All such options are "critical" in the sense that an implementation
|
||||
/// must refuse to authorise a key that has an unrecognised option.
|
||||
/// </summary>
|
||||
public IDictionary<string, string> CriticalOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.CriticalOptions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A set of zero or more optional extensions. These extensions
|
||||
/// are not critical, and an implementation that encounters one that it does
|
||||
/// not recognise may safely ignore it.
|
||||
/// </summary>
|
||||
public IDictionary<string, string> Extensions
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.Extensions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The CA key used to sign the certificate.
|
||||
/// The valid key types for CA keys are ssh-rsa,
|
||||
/// ssh-dss, ssh-ed25519 and the ECDSA types ecdsa-sha2-nistp256,
|
||||
/// ecdsa-sha2-nistp384, ecdsa-sha2-nistp521. "Chained" certificates, where
|
||||
/// the signature key type is a certificate type itself are NOT supported.
|
||||
/// Note that it is possible for a RSA certificate key to be signed by a
|
||||
/// Ed25519 or ECDSA CA key and vice-versa.
|
||||
/// </summary>
|
||||
public byte[] CertificateAuthorityKey
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.SignatureKey;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SHA256 fingerprint of the certificate authority key in the same format
|
||||
/// as the ssh command, i.e. non-padded base64, but without the <c>SHA256:</c> prefix.
|
||||
/// </summary>
|
||||
/// <example><c>ohD8VZEXGWo6Ez8GSEJQ9WpafgLFsOfLOtGGQCQo6Og</c>.</example>
|
||||
/// <value>
|
||||
/// Base64 encoded SHA256 fingerprint with padding (equals sign) removed.
|
||||
/// </value>
|
||||
public string CertificateAuthorityKeyFingerPrint
|
||||
{
|
||||
get
|
||||
{
|
||||
return Convert.ToBase64String(CryptoAbstraction.HashSHA256(CertificateAuthorityKey)).TrimEnd('=');
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The signature computed over all preceding fields from the initial string
|
||||
/// up to, and including the signature key. Signatures are computed and
|
||||
/// encoded according to the rules defined for the CA's public key algorithm
|
||||
/// (RFC4253 section 6.6 for ssh-rsa and ssh-dss, RFC5656 for the ECDSA
|
||||
/// types, and RFC8032 for Ed25519).
|
||||
/// </summary>
|
||||
public byte[] Signature
|
||||
{
|
||||
get
|
||||
{
|
||||
return _data.Signature;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The encoded certificate bytes.
|
||||
/// </summary>
|
||||
internal byte[] Bytes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The encoded bytes of the certificate which are used
|
||||
/// to calculate <see cref="Signature"/>.
|
||||
/// This consists of all of the fields before (i.e. except from)
|
||||
/// <see cref="Signature"/>.
|
||||
/// </summary>
|
||||
internal byte[] BytesForSignature
|
||||
{
|
||||
get
|
||||
{
|
||||
return Bytes.Take((int)_data.ByteCountBeforeSignature);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly CertificateData _data;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Certificate"/>
|
||||
/// class based on the data encoded in <paramref name="data"/>.
|
||||
/// </summary>
|
||||
/// <param name="data">The encoded public-key certificate data.</param>
|
||||
public Certificate(byte[] data)
|
||||
{
|
||||
Bytes = data;
|
||||
_data = new CertificateData();
|
||||
_data.Load(Bytes);
|
||||
}
|
||||
|
||||
private sealed class CertificateData : SshData
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
|
||||
public byte[] Nonce { get; private set; }
|
||||
|
||||
public Key Key { get; private set; }
|
||||
|
||||
public SshKeyData KeyData { get; private set; }
|
||||
|
||||
public ulong Serial { get; private set; }
|
||||
|
||||
public uint Type { get; private set; }
|
||||
|
||||
public string KeyId { get; private set; }
|
||||
|
||||
public List<string> ValidPrincipals { get; private set; }
|
||||
|
||||
public ulong ValidAfter { get; private set; }
|
||||
|
||||
public ulong ValidBefore { get; private set; }
|
||||
|
||||
public Dictionary<string, string> CriticalOptions { get; private set; }
|
||||
|
||||
public Dictionary<string, string> Extensions { get; private set; }
|
||||
|
||||
public byte[] SignatureKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of bytes in the encoded certificate data
|
||||
/// up to and including <see cref="SignatureKey"/>.
|
||||
/// Used for verifying <see cref="Signature"/> which is calculated
|
||||
/// from those bytes.
|
||||
/// </summary>
|
||||
public long ByteCountBeforeSignature { get; private set; }
|
||||
|
||||
public byte[] Signature { get; private set; }
|
||||
|
||||
protected override void LoadData()
|
||||
{
|
||||
Name = ReadString();
|
||||
Nonce = ReadBinary();
|
||||
Key = ReadPublicKey(out var keyData);
|
||||
KeyData = keyData;
|
||||
Serial = ReadUInt64();
|
||||
Type = ReadUInt32();
|
||||
KeyId = ReadString();
|
||||
ValidPrincipals = ReadValidPrincipals(ReadBinary());
|
||||
ValidAfter = ReadUInt64();
|
||||
ValidBefore = ReadUInt64();
|
||||
CriticalOptions = ReadExtensionPair(ReadBinary());
|
||||
Extensions = ReadExtensionPair(ReadBinary());
|
||||
_ = ReadBinary(); // Unused reserved field
|
||||
SignatureKey = ReadBinary();
|
||||
|
||||
ByteCountBeforeSignature = DataStream.Position;
|
||||
|
||||
Signature = ReadBinary();
|
||||
}
|
||||
|
||||
private Key ReadPublicKey(out SshKeyData keyData)
|
||||
{
|
||||
switch (Name)
|
||||
{
|
||||
case "ssh-rsa-cert-v01@openssh.com":
|
||||
keyData = new SshKeyData("ssh-rsa", LoadPublicKeys(2));
|
||||
return new RsaKey(keyData);
|
||||
case "ssh-dss-cert-v01@openssh.com":
|
||||
keyData = new SshKeyData("ssh-dss", LoadPublicKeys(4));
|
||||
return new DsaKey(keyData);
|
||||
case "ecdsa-sha2-nistp256-cert-v01@openssh.com":
|
||||
case "ecdsa-sha2-nistp384-cert-v01@openssh.com":
|
||||
case "ecdsa-sha2-nistp521-cert-v01@openssh.com":
|
||||
keyData = new SshKeyData(Name.Substring(0, 19), LoadPublicKeys(2));
|
||||
return new EcdsaKey(keyData);
|
||||
case "ssh-ed25519-cert-v01@openssh.com":
|
||||
keyData = new SshKeyData("ssh-ed25519", LoadPublicKeys(1));
|
||||
return new ED25519Key(keyData);
|
||||
default:
|
||||
throw new NotSupportedException($"Certificate type '{Name}'.");
|
||||
}
|
||||
|
||||
BigInteger[] LoadPublicKeys(int numPublicKeyFields)
|
||||
{
|
||||
var keys = new BigInteger[numPublicKeyFields];
|
||||
|
||||
for (var i = 0; i < numPublicKeyFields; i++)
|
||||
{
|
||||
keys[i] = ReadBinary().ToBigInteger();
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> ReadExtensionPair(byte[] data)
|
||||
{
|
||||
var result = new Dictionary<string, string>();
|
||||
using var reader = new SshDataStream(data);
|
||||
|
||||
while (!reader.IsEndOfData)
|
||||
{
|
||||
var extensionName = reader.ReadString();
|
||||
var extensionData = reader.ReadString();
|
||||
result.Add(extensionName, extensionData);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<string> ReadValidPrincipals(byte[] data)
|
||||
{
|
||||
var result = new List<string>();
|
||||
using var reader = new SshDataStream(data);
|
||||
|
||||
while (!reader.IsEndOfData)
|
||||
{
|
||||
result.Add(reader.ReadString());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override void SaveData()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to specify whether a certificate is for identification of a user
|
||||
/// or a host.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1028 // Enum Storage should be Int32; match the type specified in PROTOCOL.certkeys
|
||||
public enum CertificateType : uint
|
||||
#pragma warning restore CA1028 // Enum Storage should be Int32
|
||||
{
|
||||
/// <summary>
|
||||
/// The certificate is for identification of a user (SSH_CERT_TYPE_USER).
|
||||
/// </summary>
|
||||
User = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The certificate is for identification of a host (SSH_CERT_TYPE_HOST).
|
||||
/// </summary>
|
||||
Host = 2
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,152 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
using Renci.SshNet.Security.Cryptography;
|
||||
|
||||
namespace Renci.SshNet.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements certificate support for host algorithm.
|
||||
/// </summary>
|
||||
public class CertificateHostAlgorithm : HostAlgorithm
|
||||
public class CertificateHostAlgorithm : KeyHostAlgorithm
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the host key data.
|
||||
/// The <see cref="KeyHostAlgorithm"/> factories which may be used in order to verify
|
||||
/// the signature within the certificate.
|
||||
/// </summary>
|
||||
private readonly IReadOnlyDictionary<string, Func<byte[], KeyHostAlgorithm>>? _keyAlgorithms;
|
||||
|
||||
/// <summary>
|
||||
/// Gets certificate used in this host key algorithm.
|
||||
/// </summary>
|
||||
public Certificate Certificate { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override SshKeyData KeyData
|
||||
{
|
||||
get
|
||||
{
|
||||
return Certificate.KeyData;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the encoded bytes of the certificate.
|
||||
/// </summary>
|
||||
public override byte[] Data
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
get { return Certificate.Bytes; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CertificateHostAlgorithm"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The host key name.</param>
|
||||
public CertificateHostAlgorithm(string name)
|
||||
: base(name)
|
||||
/// <param name="name">The algorithm identifier.</param>
|
||||
/// <param name="privateKey">The private key used for this host algorithm.</param>
|
||||
/// <param name="certificate">The certificate which certifies <paramref name="privateKey"/>.</param>
|
||||
public CertificateHostAlgorithm(string name, Key privateKey, Certificate certificate)
|
||||
: base(name, privateKey)
|
||||
{
|
||||
Certificate = certificate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signs the specified data.
|
||||
/// Initializes a new instance of the <see cref="CertificateHostAlgorithm"/> class.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>Signed data.</returns>
|
||||
/// <exception cref="NotImplementedException">Always.</exception>
|
||||
/// <param name="name">The algorithm identifier.</param>
|
||||
/// <param name="privateKey">The private key used for this host algorithm.</param>
|
||||
/// <param name="certificate">The certificate which certifies <paramref name="privateKey"/>.</param>
|
||||
/// <param name="digitalSignature"><inheritdoc cref="KeyHostAlgorithm.DigitalSignature" path="/summary"/></param>
|
||||
public CertificateHostAlgorithm(string name, Key privateKey, Certificate certificate, DigitalSignature digitalSignature)
|
||||
: base(name, privateKey, digitalSignature)
|
||||
{
|
||||
Certificate = certificate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CertificateHostAlgorithm"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The algorithm identifier.</param>
|
||||
/// <param name="certificate">The certificate.</param>
|
||||
/// <param name="keyAlgorithms"><inheritdoc cref="_keyAlgorithms" path="/summary"/></param>
|
||||
public CertificateHostAlgorithm(string name, Certificate certificate, IReadOnlyDictionary<string, Func<byte[], KeyHostAlgorithm>> keyAlgorithms)
|
||||
: base(name, certificate.Key)
|
||||
{
|
||||
Certificate = certificate;
|
||||
_keyAlgorithms = keyAlgorithms;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CertificateHostAlgorithm"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The algorithm identifier.</param>
|
||||
/// <param name="certificate">The certificate.</param>
|
||||
/// <param name="digitalSignature"><inheritdoc cref="KeyHostAlgorithm.DigitalSignature" path="/summary"/></param>
|
||||
/// <param name="keyAlgorithms"><inheritdoc cref="_keyAlgorithms" path="/summary"/></param>
|
||||
public CertificateHostAlgorithm(string name, Certificate certificate, DigitalSignature digitalSignature, IReadOnlyDictionary<string, Func<byte[], KeyHostAlgorithm>> keyAlgorithms)
|
||||
: base(name, certificate.Key, digitalSignature)
|
||||
{
|
||||
Certificate = certificate;
|
||||
_keyAlgorithms = keyAlgorithms;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override byte[] Sign(byte[] data)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
Debug.Assert("-cert-v01@openssh.com".Length == 21);
|
||||
|
||||
var signatureFormatIdentifier = Name.EndsWith("-cert-v01@openssh.com", StringComparison.Ordinal)
|
||||
? Name.Substring(0, Name.Length - 21)
|
||||
: Name;
|
||||
|
||||
return new SignatureKeyData(signatureFormatIdentifier, DigitalSignature.Sign(data)).GetBytes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the signature.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <param name="signature">The signature.</param>
|
||||
/// <returns><see langword="true"/> if signature was successfully verified; otherwise <see langword="false"/>.</returns>
|
||||
/// <exception cref="NotImplementedException">Always.</exception>
|
||||
public override bool VerifySignature(byte[] data, byte[] signature)
|
||||
/// <param name="data">The data to verify the signature against.</param>
|
||||
/// <param name="signatureBlob">The signature blob in format specific encoding.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if <paramref name="signatureBlob"/> is the result of signing
|
||||
/// <paramref name="data"/> with the corresponding private key to <see cref="Certificate"/>,
|
||||
/// and <see cref="Certificate"/> is valid with respect to its validity period and to its
|
||||
/// signature therein as signed by the certificate authority.
|
||||
/// </returns>
|
||||
internal override bool VerifySignatureBlob(byte[] data, byte[] signatureBlob)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Validate the session signature against the public key as normal.
|
||||
|
||||
if (!base.VerifySignatureBlob(data, signatureBlob))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate the validity period of the certificate.
|
||||
|
||||
var unixNow = (ulong)DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
|
||||
if (unixNow < Certificate.ValidAfterUnixSeconds || unixNow > Certificate.ValidBeforeUnixSeconds)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate the certificate (i.e. the signature contained within) against
|
||||
// the CA public key (also contained in the certificate).
|
||||
|
||||
var certSignatureData = new SignatureKeyData();
|
||||
certSignatureData.Load(Certificate.Signature);
|
||||
|
||||
if (_keyAlgorithms is null)
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid usage of {nameof(CertificateHostAlgorithm)}.{nameof(VerifySignature)}. " +
|
||||
$"Use a constructor which passes key algorithms.");
|
||||
}
|
||||
|
||||
return _keyAlgorithms.TryGetValue(certSignatureData.AlgorithmName, out var keyAlgFactory) &&
|
||||
keyAlgFactory(Certificate.CertificateAuthorityKey).VerifySignatureBlob(Certificate.BytesForSignature, certSignatureData.Signature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,22 +368,40 @@ namespace Renci.SshNet.Security
|
||||
{
|
||||
var exchangeHash = CalculateHash();
|
||||
|
||||
// We need to inspect both the key and signature format identifers to find the correct
|
||||
// HostAlgorithm instance. Example cases:
|
||||
|
||||
// Key identifier Signature identifier | Algorithm name
|
||||
// ssh-rsa ssh-rsa | ssh-rsa
|
||||
// ssh-rsa rsa-sha2-256 | rsa-sha2-256
|
||||
// ssh-rsa-cert-v01@openssh.com ssh-rsa | ssh-rsa-cert-v01@openssh.com
|
||||
// ssh-rsa-cert-v01@openssh.com rsa-sha2-256 | rsa-sha2-256-cert-v01@openssh.com
|
||||
|
||||
var signatureData = new KeyHostAlgorithm.SignatureKeyData();
|
||||
signatureData.Load(encodedSignature);
|
||||
|
||||
var keyAlgorithm = Session.ConnectionInfo.HostKeyAlgorithms[signatureData.AlgorithmName](encodedKey);
|
||||
|
||||
Session.ConnectionInfo.CurrentHostKeyAlgorithm = signatureData.AlgorithmName;
|
||||
|
||||
if (CanTrustHostKey(keyAlgorithm))
|
||||
string keyName;
|
||||
using (var keyReader = new SshDataStream(encodedKey))
|
||||
{
|
||||
// keyAlgorithm.VerifySignature decodes the signature data before verifying.
|
||||
// But as we have already decoded the data to find the signature algorithm,
|
||||
// we just verify the decoded data directly through the DigitalSignature.
|
||||
return keyAlgorithm.DigitalSignature.Verify(exchangeHash, signatureData.Signature);
|
||||
keyName = keyReader.ReadString();
|
||||
}
|
||||
|
||||
return false;
|
||||
string algorithmName;
|
||||
|
||||
if (signatureData.AlgorithmName.StartsWith("rsa-sha2", StringComparison.Ordinal))
|
||||
{
|
||||
algorithmName = keyName.Replace("ssh-rsa", signatureData.AlgorithmName);
|
||||
}
|
||||
else
|
||||
{
|
||||
algorithmName = keyName;
|
||||
}
|
||||
|
||||
var keyAlgorithm = Session.ConnectionInfo.HostKeyAlgorithms[algorithmName](encodedKey);
|
||||
|
||||
Session.ConnectionInfo.CurrentHostKeyAlgorithm = algorithmName;
|
||||
|
||||
return keyAlgorithm.VerifySignatureBlob(exchangeHash, signatureData.Signature) && CanTrustHostKey(keyAlgorithm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -20,6 +20,18 @@ namespace Renci.SshNet.Security
|
||||
/// </summary>
|
||||
public DigitalSignature DigitalSignature { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the encoded public key data.
|
||||
/// </summary>
|
||||
internal virtual SshKeyData KeyData
|
||||
{
|
||||
get
|
||||
{
|
||||
var keyFormatIdentifier = Key is RsaKey ? "ssh-rsa" : Name;
|
||||
return new SshKeyData(keyFormatIdentifier, Key.Public);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the encoded public key data.
|
||||
/// </summary>
|
||||
@@ -30,8 +42,7 @@ namespace Renci.SshNet.Security
|
||||
{
|
||||
get
|
||||
{
|
||||
var keyFormatIdentifier = Key is RsaKey ? "ssh-rsa" : Name;
|
||||
return new SshKeyData(keyFormatIdentifier, Key.Public).GetBytes();
|
||||
return KeyData.GetBytes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,20 +88,37 @@ namespace Renci.SshNet.Security
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the signature.
|
||||
/// Verifies the encoded signature.
|
||||
/// </summary>
|
||||
/// <param name="data">The data to verify the signature against.</param>
|
||||
/// <param name="signature">The encoded signature data.</param>
|
||||
/// <param name="signature">
|
||||
/// The encoded signature data, as the signature format identifier followed by the signature blob.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if <paramref name="signature"/> is the result of signing <paramref name="data"/>
|
||||
/// with the corresponding private key to <see cref="Key"/>.
|
||||
/// <see langword="true"/> if <paramref name="signature"/> is the result of signing and encoding
|
||||
/// <paramref name="data"/> with the corresponding private key to <see cref="Key"/>.
|
||||
/// </returns>
|
||||
/// <remarks>See <see href="https://datatracker.ietf.org/doc/html/rfc4253#section-6.6"/>.</remarks>
|
||||
public override bool VerifySignature(byte[] data, byte[] signature)
|
||||
{
|
||||
var signatureData = new SignatureKeyData();
|
||||
signatureData.Load(signature);
|
||||
|
||||
return DigitalSignature.Verify(data, signatureData.Signature);
|
||||
return VerifySignatureBlob(data, signatureData.Signature);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the signature.
|
||||
/// </summary>
|
||||
/// <param name="data">The data to verify the signature against.</param>
|
||||
/// <param name="signatureBlob">The signature blob in format specific encoding.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if <paramref name="signatureBlob"/> is the result of signing <paramref name="data"/>
|
||||
/// with the corresponding private key to <see cref="Key"/>.
|
||||
/// </returns>
|
||||
internal virtual bool VerifySignatureBlob(byte[] data, byte[] signatureBlob)
|
||||
{
|
||||
return DigitalSignature.Verify(data, signatureBlob);
|
||||
}
|
||||
|
||||
internal sealed class SignatureKeyData : SshData
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg7W7ctYMMIVaWbaLUmo28K5Sl4CcOExOY+rILg1Wum60AAAAIbmlzdHAyNTYAAABBBI/dlNvfssW9KYrB67TcDmz9zBzDf7eMvUupAroP3b3FjUnYnpL3Utc4GkF/PiX7w2DuxaG70/+EX/CYHZBHKCsAAAAAAAAAAAAAAAEAAAAVZWNkc2EyNTZjZXJ0UnNhU2hhNTEyAAAACgAAAAZzc2huZXQAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAhcAAAAHc3NoLXJzYQAAAAMBAAEAAAIBAK9YEVSlF1erJEAcsiobagUVqpdZnVg5GIjHSXt+I7XIbQGfjVJpBpafMGsJac8YmRYOcJv13dvSjzWLqCCJvBNg8cXrt6InLJo0ISdgwk5y+i9+YQHyr8DRl836ipI5cFShRmG+p04bgUXNsj99pNWyIzwcfymJkfV4dxEryLZSwrM0DKQrNjGZ1ggWsc/oxyBGu1rRNjqzxdZY2rCiOsfxQ+dRLE5gJfz14OzfA8rPKvKw343+mugsiobTE8uPKUpM7NzUyDZzLzg/x8s2KWJkvWtYbo5To/I8Yl53B/72sWMCLtk5GcASMnnMCfsrnWx5+j3BfXb910hEaKfTb/JKu9zbz4CoEcDaB3e6y+w2+VB0s9Ubi3fr4qq20WLqdoV3gzi/6hjaXCjD6S9xbPE+xkkBR8AMaGs+chJyvj2b0TTh2mtvY0MLDnQxFMQW9gTvUvSM69EH/qzZkE5cDk+J7HEQHlGiRGIeug6bCjCF3BjslDGpEqX4NuUwNzgQ1RlDSXc/mKNdZkYjOnwttQrY3WcNzy6pABozWRJRORQRkjlpRNyp1o6Xn/XhceRayQGnT3Mk2wjpWhXXsLUT9J3DOctsIM9/xaAm1g0qHmrc5HlkUGIGxHY9gpmD2yT/CuuWe7xV+BVhDmjgfOBmDy9jHv1px+P0miwudUZzIDkTAAACFAAAAAxyc2Etc2hhMi01MTIAAAIAH+pygyvTZfq+NamjGaL7fAYBc+a0Yz7anU997Kye0EHiK8HFCq855qN4q5EBqGglmBZ/o+1aBWrK5e6imj7s63cU9G2yMy+Rr0oGCDGNWKHJVa2qeU8beIAeT82TYrusiHR9viEzc4XKgQE4k3lxUZth8Mdm8I6lkkT4fJiqrAEpCbJArCAwohrDaTP1v0DwoAd3Svqz7dLz0/I1Yv8wJqlg4bOiei3EAbRiHGFmZggi4JRV9ZWlOFLdrVbtHbbv3N68QZVFj7n4pmyTEFDxLHIxMMjOOiWn8ZIyQCz5+Ahy4Liq/gYuQAlmm2P775BCLydzcexUKLkeNEhp6VT/QR85S+qOpzvpFVqP5Qu8OL55P9IKFUg+Z0r1G9boq5rkSRc3zMPRCm7SQkk+caJq3pNDDwM8r5uRPNJwEAG/7Od5mcAVgRsXO8ccMlF6QLyGFJu8mL6avGJlEMdgBvdGMvmOQCY+zHPP0PB8dK47KoH4uopi7vXfSGK3JUB+HbBFgOUq+2lD3X2WyZHh5GDHL+c4zwmwCYyom5IoQ+DP5BWIeGKAAT1THZiIGYC/exwvbWBvPBsmOZaFAmIEaqz9oooidE9iq8AqgB6C6W5F/j1Y0QsE2h2N6cvzsgT8o++oARM1w3ZuqD4gvGCF++9I8NdGnmDYo/2eE0HUgSBuVlw= (null)
|
||||
@@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgwHAnH6bfXU1eV2weJ6Xw5wIG0N7dUfF83qQdwzDWdmcAAAAIbmlzdHAyNTYAAABBBP05AsaI5tCqEDeC6upJ6C8mzlEg79coH3HAj+yMkCbSOyarh323fQLri3W7i5wkBzoCehiLQcTqm25dCfkDbz0AAAAAAAAAAAAAAAEAAAASZWNkc2EyNTZDZXJ0UnNhNTEyAAAACgAAAAZzc2huZXQAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAhcAAAAHc3NoLXJzYQAAAAMBAAEAAAIBAK9YEVSlF1erJEAcsiobagUVqpdZnVg5GIjHSXt+I7XIbQGfjVJpBpafMGsJac8YmRYOcJv13dvSjzWLqCCJvBNg8cXrt6InLJo0ISdgwk5y+i9+YQHyr8DRl836ipI5cFShRmG+p04bgUXNsj99pNWyIzwcfymJkfV4dxEryLZSwrM0DKQrNjGZ1ggWsc/oxyBGu1rRNjqzxdZY2rCiOsfxQ+dRLE5gJfz14OzfA8rPKvKw343+mugsiobTE8uPKUpM7NzUyDZzLzg/x8s2KWJkvWtYbo5To/I8Yl53B/72sWMCLtk5GcASMnnMCfsrnWx5+j3BfXb910hEaKfTb/JKu9zbz4CoEcDaB3e6y+w2+VB0s9Ubi3fr4qq20WLqdoV3gzi/6hjaXCjD6S9xbPE+xkkBR8AMaGs+chJyvj2b0TTh2mtvY0MLDnQxFMQW9gTvUvSM69EH/qzZkE5cDk+J7HEQHlGiRGIeug6bCjCF3BjslDGpEqX4NuUwNzgQ1RlDSXc/mKNdZkYjOnwttQrY3WcNzy6pABozWRJRORQRkjlpRNyp1o6Xn/XhceRayQGnT3Mk2wjpWhXXsLUT9J3DOctsIM9/xaAm1g0qHmrc5HlkUGIGxHY9gpmD2yT/CuuWe7xV+BVhDmjgfOBmDy9jHv1px+P0miwudUZzIDkTAAACFAAAAAxyc2Etc2hhMi01MTIAAAIAQCrigzdgDsE1VUj2Ii0xwIM9kjYzIX7irCVQn6pq6vluH9e43/4AfMIE3GmHubfwUGr+z3kg2ykvy7DGDpUr6rBT62zUsr/52djAo6t8iVW7misEBeGe0TAsuCt+D6td81Kzl/MQ/DBW03q9tscGNN5DQmZwQYLjg2qydP4BooUaLlQRyg4W8J0wb2ek9lMHWCqqJZPAZu38nagBoyeyFOggnQ41cU3+vYoei/GRTmKoNk8t5wtEqJmsF7f0zGFULRFPhy0v/pBWjBC+DpwTGtf5+9SMF3qm943iSxwHFr7mPUwVg23CP4DHXVImZdxEh8VC9GcZkq0OR7JfnBvMAfPG20pqMZUIJl5qR88tKibyIp2EY2vy3IQ+iYS6fRpmPCgHck8qlHjZaaqBEeV2E0t2YJMDs5CrgLkCOClsjBeNm1wp7xL7VV07ZLNYIVAW1Bx7YTyPT/18Y/PNrnBd2Z8XCTX0oI2EXh7t8pjHSMk2HVq/i6Pmnmndvm8Wym+IDScBGeJJB4jiiAs6OaDZTOjPy0wudo7NapoeAEfDAWkPMavARdl6zz1q8IGxJ3ldxvcTU4Og83x3NmvZXPtzfoMOJJbA6H66/9DylClEjflJU4Rvf7pG6F4UEUACZu/ocKAasRIfatSo4QGCuV+Fq/geHrC/oiEELo2+Erj7pRM= robert@VMWKS-015
|
||||
@@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp384-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgmWC208kRFGQSG7AG3NFtkSeeGKVuKlby6CpjhD5bZpIAAAAIbmlzdHAzODQAAABhBFM/UMxegeBb5Ff5L83FQQSWi7VyYsPoISJH7OnNoYbqbOXouFRj5nd/Yze7i7u1wzxOAH+OIducj1Np43lArgdfUP0NeQflGF+ct+ubeQJM2gIUp3RZr9AC8quU0qJGLwAAAAAAAAAAAAAAAQAAABJlY2RzYTM4NGNlcnRSc2EyNTYAAAAKAAAABnNzaG5ldAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAACFwAAAAdzc2gtcnNhAAAAAwEAAQAAAgEAr1gRVKUXV6skQByyKhtqBRWql1mdWDkYiMdJe34jtchtAZ+NUmkGlp8wawlpzxiZFg5wm/Xd29KPNYuoIIm8E2Dxxeu3oicsmjQhJ2DCTnL6L35hAfKvwNGXzfqKkjlwVKFGYb6nThuBRc2yP32k1bIjPBx/KYmR9Xh3ESvItlLCszQMpCs2MZnWCBaxz+jHIEa7WtE2OrPF1ljasKI6x/FD51EsTmAl/PXg7N8Dys8q8rDfjf6a6CyKhtMTy48pSkzs3NTINnMvOD/HyzYpYmS9a1hujlOj8jxiXncH/vaxYwIu2TkZwBIyecwJ+yudbHn6PcF9dv3XSERop9Nv8kq73NvPgKgRwNoHd7rL7Db5UHSz1RuLd+viqrbRYup2hXeDOL/qGNpcKMPpL3Fs8T7GSQFHwAxoaz5yEnK+PZvRNOHaa29jQwsOdDEUxBb2BO9S9Izr0Qf+rNmQTlwOT4nscRAeUaJEYh66DpsKMIXcGOyUMakSpfg25TA3OBDVGUNJdz+Yo11mRiM6fC21CtjdZw3PLqkAGjNZElE5FBGSOWlE3KnWjpef9eFx5FrJAadPcyTbCOlaFdewtRP0ncM5y2wgz3/FoCbWDSoeatzkeWRQYgbEdj2CmYPbJP8K65Z7vFX4FWEOaOB84GYPL2Me/WnH4/SaLC51RnMgORMAAAIUAAAADHJzYS1zaGEyLTI1NgAAAgB6DjB06g/oN5cMdyHzSrA3AjZXkfmmrxDLos2OvgkVOwsRdThj5G7r+c3wVH5cvZ0QdSl9S4CqO/aCaPeYcavqSFKYTbmDE0phQRQB8dsH8PG552b6L8ZvxRNGNFAxvBAm1G8VM3qLK04FREkfBhfxArYhtiKIT0BAXZPwkf6NSaEM5oMZ0ypC9FeKbs/05/3P01h9AZPvBIA5mqdIq6iGpzdmm13yjotsaideFnuBePmLaHrKybZb+qtvpT4/RKKp6GiM3Kbz+MpDidwcn+ltcNU4sxzIEUWm51Ig0fgN0QGkgggw66ala4Z7lwGImLHGZ7tPk8XuWANPv92hWGBAV5EXT42AjYEkdEZl7cwRR6qz3bwgBDqRWfrMkB68g2qDhPoZlF5UwNFVljefl3S/AqNjo8RjLWNLwNRkUxiu9u/m5uEZX8TqjAXphYU5JGpIR45hoscxv5glTtidtC6Lzj9hX44eroYs48HZ6yktDjILgcfSHyxTLAjjcWIUtWTmXNXsqJM2Afmt5iND1ZC86gGhCnMhLcQQ8+FC0tOw0WX1ksX/J5sA7qqp3ztxuPQeVY7k7htUVpb78TQZzPxjYLxRV/NYtZKlrvK2u2zM+eExNxCTPOt/ZlS9T2bxolwVZL1PzHCGkhWr76FNhitGVVxc06TcL2G9YPLznYdLVw== (null)
|
||||
@@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp384-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgCqaZ+v4X2FFSge4hPDeBFqdFKDHpvqG7CkrXBmViLiUAAAAIbmlzdHAzODQAAABhBIoFo5PTu8NHANpVbFmZLpH3wvvegkt14r6cJF3WeUYAQLuhfxfujxli6+nHdgkuEZXVt3EVDZaosap9K8ZdiPxdA7fHj41Dc5CyyMIeNCdCTkO0+BBDCygLHCw6k0a7wgAAAAAAAAAAAAAAAQAAABJlY2RzYTM4NENlcnRSc2EyNTYAAAAKAAAABnNzaG5ldAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAACFwAAAAdzc2gtcnNhAAAAAwEAAQAAAgEAr1gRVKUXV6skQByyKhtqBRWql1mdWDkYiMdJe34jtchtAZ+NUmkGlp8wawlpzxiZFg5wm/Xd29KPNYuoIIm8E2Dxxeu3oicsmjQhJ2DCTnL6L35hAfKvwNGXzfqKkjlwVKFGYb6nThuBRc2yP32k1bIjPBx/KYmR9Xh3ESvItlLCszQMpCs2MZnWCBaxz+jHIEa7WtE2OrPF1ljasKI6x/FD51EsTmAl/PXg7N8Dys8q8rDfjf6a6CyKhtMTy48pSkzs3NTINnMvOD/HyzYpYmS9a1hujlOj8jxiXncH/vaxYwIu2TkZwBIyecwJ+yudbHn6PcF9dv3XSERop9Nv8kq73NvPgKgRwNoHd7rL7Db5UHSz1RuLd+viqrbRYup2hXeDOL/qGNpcKMPpL3Fs8T7GSQFHwAxoaz5yEnK+PZvRNOHaa29jQwsOdDEUxBb2BO9S9Izr0Qf+rNmQTlwOT4nscRAeUaJEYh66DpsKMIXcGOyUMakSpfg25TA3OBDVGUNJdz+Yo11mRiM6fC21CtjdZw3PLqkAGjNZElE5FBGSOWlE3KnWjpef9eFx5FrJAadPcyTbCOlaFdewtRP0ncM5y2wgz3/FoCbWDSoeatzkeWRQYgbEdj2CmYPbJP8K65Z7vFX4FWEOaOB84GYPL2Me/WnH4/SaLC51RnMgORMAAAIUAAAADHJzYS1zaGEyLTI1NgAAAgBKYSthoC0jBYvlWqbHiUfk/dEfnIthoJsnvsQwWHCY5YIv6EqwO0n1j+Oc6942NDdeJYG3FoA/8hvDvi/QRMEWN8/Opoj3d0mWJdyCjZAt+1o6gq2oNsuXywJEW9rnC9bmdsMSbQgDSpc+DY7asp/DSLXZDb54JALf6rcZ8kfPXQ17gdNfw2cOg4X1n3cvGvOMDLdRtwSNRYn/RfD9idf9acfycSJIRNvBFwcx0kPWlc5NWsrIbP7pMsh0Rdr7Cc/7cO2TFy0qvrR6jB+WXWhkQFlPbg9EvOrPeuxtppetqRl7e0jc3CsdL8yiVasd57Uyfc7z+HX9dCnk71L+yGmOsJM42M8p808WBV3IXOGne8HScOTb/6rjFDChV3GcsXO/fdZdG1WEdRsYoYqx5iadTKTXt1+RB4Hygm+TTYm3P34Mkq+mAu5xTwsWgV716h3vcyAE2+bA6fo9RDa+Rrizo70bOd8pDDLpbUInpISYRYFuAuvgceHRt19i45McANlTXarOl3Dt1THuOTWcCzlWZbadES+hBGPzDhhrQAEV14bgMvcJbKGeG3UsdZY8Rhy7tERDta/hGffwFAO/jnjEvTO28xqucCBowIutDFlq6fG8JX9urwNUMKarGIFHR7a1EEsK8+cuypxXDm978/EFzp0mVeg7eFBCrK/qBKKFJw== SSH.NET
|
||||
@@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp521-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHA1MjEtY2VydC12MDFAb3BlbnNzaC5jb20AAAAggRfJnc8gqLYnXn7VgT8GoB1ko5INrxXhDD9Ap+MBZqMAAAAIbmlzdHA1MjEAAACFBABrunhZWBr7Tyq7XrQGt3MrJE0kxAJ4aEWW412rvf+5pbeqWqgSJo21zm4HscfKMJZBOZ/OtJEtFntgHBRqdzDKHgCrqAGAaxdXPA29jeTFEOUatJ8yaweVfPjV2DD3CbV8Fx/3ueJ7FFD/EaWGTJ/shiVD+zkGlcXaVL2XQfmEGKmlGAAAAAAAAAAAAAAAAQAAABFlY2RzYTUyMWNlcnRFY2RzYQAAAAoAAAAGc3NobmV0AAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLvxUyJKR9Jxxutu2sMEoZ1MsIxUdMNqYcpmbUX8Yu4kdGx+Wr4ktOXv8CseoXSkY2W2/lh8g/roE6N4H6cQWk8AAABlAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAABKAAAAIQCUzZ6Z537DWC4RrgxWmGSY3/GpY9ZySZx+lSvNEj7NQgAAACEAuI4WpushiyWuFq+qgUbzRzAlURAcVTP0TH6QVFTIKfA= (null)
|
||||
@@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp521-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHA1MjEtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgvnIWATz6TOlL4gCr4t6yk2PIxN2eHym+YSDgJs0wMukAAAAIbmlzdHA1MjEAAACFBAGdkuygP8JJOCTbo3G6pxSBXIODWBNkYaidj4Z47o+r4OiAiAcEgpZSsQn1qhXMOtzeqBMvRe7IphOWFXBae2viVABR5JNwUB08HtxRG6zgr6jv5vrDqggHGNAcShOcluRrWu87nIQgIw2N5unSX6HhdOQ3VYRTUI3kAAfx/8WI6GSfvQAAAAAAAAAAAAAAAQAAABFlY2RzYTUyMUNlcnRFY2RzYQAAAAoAAAAGc3NobmV0AAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLvxUyJKR9Jxxutu2sMEoZ1MsIxUdMNqYcpmbUX8Yu4kdGx+Wr4ktOXv8CseoXSkY2W2/lh8g/roE6N4H6cQWk8AAABjAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAABIAAAAIFnvTwo0GBTn91FYop1TCjS1KSNH2Y9OrKAQJwA8eJI4AAAAIAZ1vRormvCd1lOlBx52MmWZOlitfR6DN/tUe5cyXTMF SSH.NET
|
||||
@@ -0,0 +1 @@
|
||||
ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIDZqX9y0awmstGNnKnsJk+rljDcgUKP+lPg7L3DX1zm3AAAAIA0JZnDQrxQZcNALfZYG7LPAW1MYEGvVW5nje7OlMGMiAAAAAAAAAAAAAAABAAAAEmVkMjU1MTktY2VydC1lY2RzYQAAAAoAAAAGc3NobmV0AAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLvxUyJKR9Jxxutu2sMEoZ1MsIxUdMNqYcpmbUX8Yu4kdGx+Wr4ktOXv8CseoXSkY2W2/lh8g/roE6N4H6cQWk8AAABkAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAABJAAAAIQD0JteEHgKpsM+xWqLsSl5CXj8pC8VlSTBKaIHDbQ0mhwAAACB+RIVgmktyCbm5/D1geigyzLnB2pHPrPIlNtTo+0vtLQ== Key.OPENSSH.ED25519
|
||||
@@ -0,0 +1 @@
|
||||
ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIO17UOT7XYGpB0kekNKXWNafEvDaZzmYz40s5X0h7mT1AAAAIBquS79wnun0ksZv6JJMgHzoZhzqH6Lkft+1sHTHFOjYAAAAAAAAAAAAAAABAAAAEGVkMjU1MTlDZXJ0RWNkc2EAAAAKAAAABnNzaG5ldAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQS78VMiSkfSccbrbtrDBKGdTLCMVHTDamHKZm1F/GLuJHRsflq+JLTl7/ArHqF0pGNltv5YfIP66BOjeB+nEFpPAAAAYwAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAASAAAACBeyEF9YGumQTDU0WIMG44g7p5xmtDQ9iwCgSazPRQ6UQAAACBbuMtgLhGK1uaqNYTrVpFvQr2kKU2jB4F8S/1Oopx5/Q== (null)
|
||||
@@ -0,0 +1 @@
|
||||
ssh-rsa-cert-v01@openssh.com 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 Key.OPENSSH.RSA
|
||||
@@ -0,0 +1 @@
|
||||
ssh-rsa-cert-v01@openssh.com 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 SSH.NET
|
||||
@@ -15,6 +15,7 @@ namespace Renci.SshNet.IntegrationTests.Common
|
||||
.WithLogLevel(LogLevel.Debug3)
|
||||
.ClearHostKeyFiles()
|
||||
.AddHostKeyFile(HostKeyFile.Rsa.FilePath)
|
||||
.WithHostKeyCertificate(null)
|
||||
.ClearSubsystems()
|
||||
.AddSubsystem(new Subsystem("sftp", "/usr/lib/ssh/sftp-server"))
|
||||
.ClearCiphers()
|
||||
|
||||
@@ -17,6 +17,7 @@ RUN apk update && apk upgrade --no-cache && \
|
||||
sed -i 's/#LogLevel\s*INFO/LogLevel DEBUG3/' /etc/ssh/sshd_config && \
|
||||
# Set the default RSA key
|
||||
echo 'HostKey /etc/ssh/ssh_host_rsa_key' >> /etc/ssh/sshd_config && \
|
||||
echo 'TrustedUserCAKeys /etc/ssh/user-ca.pub' >> /etc/ssh/sshd_config && \
|
||||
chmod 646 /etc/ssh/sshd_config && \
|
||||
# install and configure sudo
|
||||
apk add --no-cache sudo && \
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace Renci.SshNet.IntegrationTests
|
||||
{
|
||||
public sealed class HostCertificateFile
|
||||
{
|
||||
public static readonly HostCertificateFile RsaCertRsa = new HostCertificateFile("ssh-rsa-cert-v01@openssh.com", "/etc/ssh/ssh_host_rsa_key-cert_rsa.pub", HostKeyFile.Rsa, "x0vVk+h7SGE7bNN0wAA2vsA9Mg9qLOZPqhq2Dj/rqfM");
|
||||
public static readonly HostCertificateFile Ed25519CertEcdsa = new HostCertificateFile("ssh-ed25519-cert-v01@openssh.com", "/etc/ssh/ssh_host_ed25519_key-cert_ecdsa", HostKeyFile.Ed25519, "Z2diHpknyvJpetRw47iIjqt9OUzm6cAVOe4FM5FbDQw");
|
||||
public static readonly HostCertificateFile Ecdsa256CertRsa = new HostCertificateFile("ecdsa-sha2-nistp256-cert-v01@openssh.com", "/etc/ssh/ssh_host_ecdsa256_key-cert_rsa", HostKeyFile.Ecdsa256, "x0vVk+h7SGE7bNN0wAA2vsA9Mg9qLOZPqhq2Dj/rqfM");
|
||||
public static readonly HostCertificateFile Ecdsa384CertEcdsa = new HostCertificateFile("ecdsa-sha2-nistp384-cert-v01@openssh.com", "/etc/ssh/ssh_host_ecdsa384_key-cert_ecdsa", HostKeyFile.Ecdsa384, "Z2diHpknyvJpetRw47iIjqt9OUzm6cAVOe4FM5FbDQw");
|
||||
public static readonly HostCertificateFile Ecdsa521CertEd25519 = new HostCertificateFile("ecdsa-sha2-nistp521-cert-v01@openssh.com", "/etc/ssh/ssh_host_ecdsa521_key-cert_ed25519", HostKeyFile.Ecdsa521, "tF3DRTUXtYFZ5Yz0SBOrEbixHaCifHmNVK6FtptXZVM");
|
||||
|
||||
private HostCertificateFile(string certificateName, string filePath, HostKeyFile hostKeyFile, string caFingerPrint)
|
||||
{
|
||||
CertificateName = certificateName;
|
||||
FilePath = filePath;
|
||||
HostKeyFile = hostKeyFile;
|
||||
CAFingerPrint = caFingerPrint;
|
||||
}
|
||||
|
||||
public string CertificateName { get; }
|
||||
public string FilePath { get; }
|
||||
public HostKeyFile HostKeyFile { get; }
|
||||
public string CAFingerPrint { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Renci.SshNet.Common;
|
||||
using Renci.SshNet.IntegrationTests.Common;
|
||||
using Renci.SshNet.Security;
|
||||
using Renci.SshNet.TestTools.OpenSSH;
|
||||
|
||||
namespace Renci.SshNet.IntegrationTests
|
||||
@@ -71,12 +72,49 @@ namespace Renci.SshNet.IntegrationTests
|
||||
DoTest(HostKeyAlgorithm.EcdsaSha2Nistp521, HostKeyFile.Ecdsa521);
|
||||
}
|
||||
|
||||
private void DoTest(HostKeyAlgorithm hostKeyAlgorithm, HostKeyFile hostKeyFile)
|
||||
[TestMethod]
|
||||
public void SshRsaCertificate()
|
||||
{
|
||||
DoTest(HostKeyAlgorithm.SshRsaCertV01OpenSSH, HostCertificateFile.RsaCertRsa);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SshRsaSha256Certificate()
|
||||
{
|
||||
DoTest(HostKeyAlgorithm.RsaSha2256CertV01OpenSSH, HostCertificateFile.RsaCertRsa);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Ecdsa256Certificate()
|
||||
{
|
||||
DoTest(HostKeyAlgorithm.EcdsaSha2Nistp256CertV01OpenSSH, HostCertificateFile.Ecdsa256CertRsa);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Ecdsa384Certificate()
|
||||
{
|
||||
DoTest(HostKeyAlgorithm.EcdsaSha2Nistp384CertV01OpenSSH, HostCertificateFile.Ecdsa384CertEcdsa);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Ecdsa521Certificate()
|
||||
{
|
||||
DoTest(HostKeyAlgorithm.EcdsaSha2Nistp521CertV01OpenSSH, HostCertificateFile.Ecdsa521CertEd25519);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Ed25519Certificate()
|
||||
{
|
||||
DoTest(HostKeyAlgorithm.SshEd25519CertV01OpenSSH, HostCertificateFile.Ed25519CertEcdsa);
|
||||
}
|
||||
|
||||
private void DoTest(HostKeyAlgorithm hostKeyAlgorithm, HostKeyFile hostKeyFile, HostCertificateFile hostCertificateFile = null)
|
||||
{
|
||||
_remoteSshdConfig.ClearHostKeyAlgorithms()
|
||||
.AddHostKeyAlgorithm(hostKeyAlgorithm)
|
||||
.ClearHostKeyFiles()
|
||||
.AddHostKeyFile(hostKeyFile.FilePath)
|
||||
.WithHostKeyCertificate(hostCertificateFile?.FilePath)
|
||||
.Update()
|
||||
.Restart();
|
||||
|
||||
@@ -93,6 +131,22 @@ namespace Renci.SshNet.IntegrationTests
|
||||
Assert.AreEqual(hostKeyAlgorithm.Name, hostKeyEventsArgs.HostKeyName);
|
||||
Assert.AreEqual(hostKeyFile.KeyLength, hostKeyEventsArgs.KeyLength);
|
||||
CollectionAssert.AreEqual(hostKeyFile.FingerPrint, hostKeyEventsArgs.FingerPrint);
|
||||
|
||||
if (hostCertificateFile is not null)
|
||||
{
|
||||
Assert.IsNotNull(hostKeyEventsArgs.Certificate);
|
||||
Assert.AreEqual(Certificate.CertificateType.Host, hostKeyEventsArgs.Certificate.Type);
|
||||
Assert.AreEqual(hostCertificateFile.CAFingerPrint, hostKeyEventsArgs.Certificate.CertificateAuthorityKeyFingerPrint);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNull(hostKeyEventsArgs.Certificate);
|
||||
}
|
||||
}
|
||||
|
||||
private void DoTest(HostKeyAlgorithm hostKeyAlgorithm, HostCertificateFile hostCertificateFile)
|
||||
{
|
||||
DoTest(hostKeyAlgorithm, hostCertificateFile.HostKeyFile, hostCertificateFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,14 +70,74 @@ namespace Renci.SshNet.IntegrationTests
|
||||
DoTest(PublicKeyAlgorithm.SshEd25519, "Data.Key.OPENSSH.ED25519.Encrypted.txt", "12345");
|
||||
}
|
||||
|
||||
private void DoTest(PublicKeyAlgorithm publicKeyAlgorithm, string keyResource, string passPhrase = null)
|
||||
// The private keys used for the certificate tests below should stay out of authorized_keys for a proper test.
|
||||
|
||||
[TestMethod]
|
||||
public void SshRsaCertificate()
|
||||
{
|
||||
// ssh-keygen -L -f Key.OPENSSH.RSA.Encrypted.Aes.192.CTR-cert.pub
|
||||
// Type: ssh-rsa-cert-v01@openssh.com user certificate
|
||||
// Public key: RSA-CERT SHA256:MMIzDVhQHqU9SAZ8p3x2wo6JpXixCWO/7qf6h0l8DJA
|
||||
// Signing CA: RSA SHA256:NqLEgdYti0XjUkYjGyQv2Ddy1O5v2NZDZFRtlfESLIA (using rsa-sha2-512)
|
||||
// And we will authenticate (sign) with ssh-rsa (SHA-1)
|
||||
DoTest(PublicKeyAlgorithm.SshRsaCertV01OpenSSH, "Data.Key.OPENSSH.RSA.Encrypted.Aes.192.CTR.txt", "12345", "Data.Key.OPENSSH.RSA.Encrypted.Aes.192.CTR-cert.pub");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SshRsaSha256Certificate()
|
||||
{
|
||||
// As above, but we will authenticate (sign) with rsa-sha2-256
|
||||
DoTest(PublicKeyAlgorithm.SshRsaCertV01OpenSSH, "Data.Key.OPENSSH.RSA.Encrypted.Aes.192.CTR.txt", "12345", "Data.Key.OPENSSH.RSA.Encrypted.Aes.192.CTR-cert.pub");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Ecdsa256Certificate()
|
||||
{
|
||||
// ssh-keygen -L -f Key.OPENSSH.ECDSA.Encrypted.Aes.128.CTR-cert.pub
|
||||
// Type: ecdsa-sha2-nistp256-cert-v01@openssh.com user certificate
|
||||
// Public key: ECDSA-CERT SHA256:ufAaMwjTmKrjvt4CQiLPal1/HrmB2D7oL+H2lh/Om8c
|
||||
// Signing CA: RSA SHA256:NqLEgdYti0XjUkYjGyQv2Ddy1O5v2NZDZFRtlfESLIA (using rsa-sha2-512)
|
||||
DoTest(PublicKeyAlgorithm.EcdsaSha2Nistp256CertV01OpenSSH, "Data.Key.OPENSSH.ECDSA.Encrypted.Aes.128.CTR.txt", "12345", "Data.Key.OPENSSH.ECDSA.Encrypted.Aes.128.CTR-cert.pub");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Ecdsa384Certificate()
|
||||
{
|
||||
// ssh-keygen -L -f Key.OPENSSH.ECDSA384.Encrypted.Aes.256.GCM-cert.pub
|
||||
// Type: ecdsa-sha2-nistp384-cert-v01@openssh.com user certificate
|
||||
// Public key: ECDSA-CERT SHA256:wy4X47uddqD8nggcsGHG7Rcs0qcnh4r6NrdBGdh/8us
|
||||
// Signing CA: RSA SHA256:NqLEgdYti0XjUkYjGyQv2Ddy1O5v2NZDZFRtlfESLIA (using rsa-sha2-256)
|
||||
DoTest(PublicKeyAlgorithm.EcdsaSha2Nistp384CertV01OpenSSH, "Data.Key.OPENSSH.ECDSA384.Encrypted.Aes.256.GCM.txt", "12345", "Data.Key.OPENSSH.ECDSA384.Encrypted.Aes.256.GCM-cert.pub");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Ecdsa521Certificate()
|
||||
{
|
||||
// ssh-keygen -L -f Key.OPENSSH.ECDSA521.Encrypted.Aes.192.CBC-cert.pub
|
||||
// Type: ecdsa-sha2-nistp521-cert-v01@openssh.com user certificate
|
||||
// Public key: ECDSA-CERT SHA256:U3wBX0sSPYxso31gi1QPz7O+1eMOTb0LoOSOjWRwyYE
|
||||
// Signing CA: ECDSA SHA256:r/t6I+bZQzN5BhSuntFSHDHlrnNHVM2lAo6gbvynG/4 (using ecdsa-sha2-nistp256)
|
||||
DoTest(PublicKeyAlgorithm.EcdsaSha2Nistp521CertV01OpenSSH, "Data.Key.OPENSSH.ECDSA521.Encrypted.Aes.192.CBC.txt", "12345", "Data.Key.OPENSSH.ECDSA521.Encrypted.Aes.192.CBC-cert.pub");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Ed25519Certificate()
|
||||
{
|
||||
// ssh-keygen -L -f Key.OPENSSH.ED25519.Encrypted.ChaCha20.Poly1305-cert.pub
|
||||
// Type: ssh-ed25519-cert-v01@openssh.com user certificate
|
||||
// Public key: ED25519-CERT SHA256:gwO3eBcuPqChqg9B/kHsQo1/bYTAjaEZCanA7hqSuEg
|
||||
// Signing CA: ECDSA SHA256:r/t6I+bZQzN5BhSuntFSHDHlrnNHVM2lAo6gbvynG/4 (using ecdsa-sha2-nistp256)
|
||||
DoTest(PublicKeyAlgorithm.SshEd25519CertV01OpenSSH, "Data.Key.OPENSSH.ED25519.Encrypted.ChaCha20.Poly1305.txt", "12345", "Data.Key.OPENSSH.ED25519.Encrypted.ChaCha20.Poly1305-cert.pub");
|
||||
}
|
||||
|
||||
private void DoTest(PublicKeyAlgorithm publicKeyAlgorithm, string keyResource, string passPhrase = null, string certificateResource = null)
|
||||
{
|
||||
_remoteSshdConfig.ClearPublicKeyAcceptedAlgorithms()
|
||||
.AddPublicKeyAcceptedAlgorithm(publicKeyAlgorithm)
|
||||
.Update()
|
||||
.Restart();
|
||||
|
||||
var connectionInfo = _connectionInfoFactory.Create(CreatePrivateKeyAuthenticationMethod(keyResource, passPhrase));
|
||||
var connectionInfo = _connectionInfoFactory.Create(CreatePrivateKeyAuthenticationMethod(keyResource, passPhrase, certificateResource));
|
||||
|
||||
using (var client = new SshClient(connectionInfo))
|
||||
{
|
||||
@@ -85,12 +145,26 @@ namespace Renci.SshNet.IntegrationTests
|
||||
}
|
||||
}
|
||||
|
||||
private static PrivateKeyAuthenticationMethod CreatePrivateKeyAuthenticationMethod(string keyResource, string passPhrase)
|
||||
private static PrivateKeyAuthenticationMethod CreatePrivateKeyAuthenticationMethod(string keyResource, string passPhrase, string certificateResource)
|
||||
{
|
||||
using (var stream = GetData(keyResource))
|
||||
PrivateKeyFile privateKey;
|
||||
|
||||
using (var keyStream = GetData(keyResource))
|
||||
{
|
||||
return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, new PrivateKeyFile(stream, passPhrase));
|
||||
if (certificateResource is not null)
|
||||
{
|
||||
using (var certificateStream = GetData(certificateResource))
|
||||
{
|
||||
privateKey = new PrivateKeyFile(keyStream, passPhrase, certificateStream);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
privateKey = new PrivateKeyFile(keyStream, passPhrase);
|
||||
}
|
||||
}
|
||||
|
||||
return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,12 @@ namespace Renci.SshNet.IntegrationTests
|
||||
return this;
|
||||
}
|
||||
|
||||
public RemoteSshdConfig WithHostKeyCertificate(string hostKeyCertificate)
|
||||
{
|
||||
_config.HostCertificate = hostKeyCertificate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RemoteSshd Update()
|
||||
{
|
||||
using (var client = new ScpClient(_connectionInfoFactory.Create()))
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
using DotNet.Testcontainers.Containers;
|
||||
using DotNet.Testcontainers.Images;
|
||||
|
||||
#if !NET && !NETSTANDARD2_1_OR_GREATER
|
||||
using Renci.SshNet.Abstractions;
|
||||
#endif
|
||||
|
||||
namespace Renci.SshNet.IntegrationTests.TestsFixtures
|
||||
{
|
||||
public sealed class InfrastructureFixture : IDisposable
|
||||
@@ -36,10 +32,6 @@ namespace Renci.SshNet.IntegrationTests.TestsFixtures
|
||||
|
||||
public SshUser User = new SshUser("sshnet", "ssh4ever");
|
||||
|
||||
// To get the sshd logs (also uncomment WithOutputConsumer below)
|
||||
private readonly Stream _fsOut = Stream.Null; // File.Create("fsout.txt");
|
||||
private readonly Stream _fsErr = Stream.Null; // File.Create("fserr.txt");
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
_sshServerImage = new ImageFromDockerfileBuilder()
|
||||
@@ -55,7 +47,6 @@ namespace Renci.SshNet.IntegrationTests.TestsFixtures
|
||||
.WithHostname("renci-ssh-tests-server")
|
||||
.WithImage(_sshServerImage)
|
||||
.WithPortBinding(22, true)
|
||||
//.WithOutputConsumer(Consume.RedirectStdoutAndStderrToStream(_fsOut, _fsErr))
|
||||
.Build();
|
||||
|
||||
await _sshServer.StartAsync();
|
||||
@@ -76,6 +67,15 @@ namespace Renci.SshNet.IntegrationTests.TestsFixtures
|
||||
{
|
||||
if (_sshServer != null)
|
||||
{
|
||||
//try
|
||||
//{
|
||||
// File.WriteAllBytes(@"C:\tmp\auth.log", await _sshServer.ReadFileAsync("/var/log/auth.log"));
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// Console.Error.WriteLine(ex.ToString());
|
||||
//}
|
||||
|
||||
await _sshServer.DisposeAsync();
|
||||
}
|
||||
|
||||
@@ -83,9 +83,6 @@ namespace Renci.SshNet.IntegrationTests.TestsFixtures
|
||||
{
|
||||
await _sshServerImage.DisposeAsync();
|
||||
}
|
||||
|
||||
await _fsOut.DisposeAsync();
|
||||
await _fsErr.DisposeAsync();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS
|
||||
1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBOm4mqGkmRjg+N/BstCoV+3OYDp5Q
|
||||
q/s3Lp/CLCNjAHsyEKQJ7U0gHTybdXskhPZwPEQpTZRyXmNyyZUxvRGu6jIB9njA8bq6m8
|
||||
tOrbJz3SY26gcJPtVQgPzz7RkGHLJIvopkzAdKYLtf/vYpiCAP8SU0aZLL8j/WaQQRoZ36
|
||||
QJr2Rp8AAAEIzunYnM7p2JwAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ
|
||||
AAAIUEATpuJqhpJkY4PjfwbLQqFftzmA6eUKv7Ny6fwiwjYwB7MhCkCe1NIB08m3V7JIT2
|
||||
cDxEKU2Ucl5jcsmVMb0RruoyAfZ4wPG6upvLTq2yc90mNuoHCT7VUID88+0ZBhyySL6KZM
|
||||
wHSmC7X/72KYggD/ElNGmSy/I/1mkEEaGd+kCa9kafAAAAQgDOhCBRxYWebDsbGhHbzEbg
|
||||
BcrczdS+3Jkf2eq1SIhTWYRlJw9lVNPaQxkYnUnLYeWIdG3sAxWdZsepGhm2y+dgTQAAAA
|
||||
hDQS1FQ0RTQQEC
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
@@ -0,0 +1,7 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACCgTBKYawpTnGkumT6IXiHGdiqk0C+4rPVB1Vs7uVFuxAAAAJCdPWEKnT1h
|
||||
CgAAAAtzc2gtZWQyNTUxOQAAACCgTBKYawpTnGkumT6IXiHGdiqk0C+4rPVB1Vs7uVFuxA
|
||||
AAAECxa3d+jGIfVQT5AF0Ssb1pPxUJVH88BPbKy5LxwMGbgaBMEphrClOcaS6ZPoheIcZ2
|
||||
KqTQL7is9UHVWzu5UW7EAAAACkNBLUVEMjU1MTkBAgM=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
@@ -0,0 +1,49 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
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
|
||||
bZjU9P/dMt0AAAAGQ0EtUlNBAQIDBAU=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
@@ -0,0 +1,9 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
|
||||
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQS78VMiSkfSccbrbtrDBKGdTLCMVHTD
|
||||
amHKZm1F/GLuJHRsflq+JLTl7/ArHqF0pGNltv5YfIP66BOjeB+nEFpPAAAAqCFPmN8hT5
|
||||
jfAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLvxUyJKR9Jxxutu
|
||||
2sMEoZ1MsIxUdMNqYcpmbUX8Yu4kdGx+Wr4ktOXv8CseoXSkY2W2/lh8g/roE6N4H6cQWk
|
||||
8AAAAgaEEu4ZRGDRhhSTG9pU2pACyIY4M2mVSIM5/g2RAA9z0AAAAOcm9iZXJ0QFJIRTE0
|
||||
RzMBAg==
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
@@ -0,0 +1,49 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
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
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
@@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgR6jvisYMr7lS9maXh96zzfyYb2rdf9mEmq1RyRN/ZjUAAAAIbmlzdHAyNTYAAABBBO0OhyLQJoSDJH6n56uyJiW0pRKpnH8x99amcQ98aCcUjzWG5DDQP179KoAFt4e2vNxJayRJlzR/aOMXHKpWSWIAAAAAAAAAAAAAAAIAAAAPZWNkc2EyNTZjZXJ0UlNBAAAAAAAAAAAAAAAA//////////8AAAAAAAAAAAAAAAAAAAIXAAAAB3NzaC1yc2EAAAADAQABAAACAQCrcoVYAVaFda7O86SEab33pyP6hS1p7skrD/qRQ0sZEOATdSUvCD0y45zl3jwGcBPOX7mdxeFaFBAO/QRnE++4FkIJmpXRHh/Zy+D+CoOPWudFck8b8Zsx36wfA8DsVuqZdAVRrik5xgPy2yP/g6h27sGFIL0Ced3A9GuUAje22O8IAcg09TqMJzsyaWORo2ez+kXIiJkxeAgiyOHS4kxZG/hAr20xfsHx3pG7UlX+EYuqL7/v2uT3A4ceFAi+7pob/06SxozOm3B1Bl3xGLw0M5o1eQBja4pBVJ8ccUM/BII+vlEgLoMMPPgOkYxMAnAateGqD5HoRWYTkYbTHwMtqkNyMx9Bo6OnIrFQ3jYRwFFyu8B2vm+Tod15YVWWtB4FXQ5FHW5f6v87TT9fB4HE3lhSSKT76WMxrLxUS1I1etNFuCb5Fwfe6JCfqAjy10Q0Ad60M2bgPK69j4+xtEicXZ6iZIO+AqYUSzWF91JUWKC5DjGMqUQxckJe4X37kZb1O1BIgD9e3tivg/mnYY4kDxhtE8CUgPPnBBlCFf+f2JSsALQ7PcNI33BfpXWRpQ7dYrr8lL65YUdrZ/V7WZlinmA2RlZichRnm0aAsgzYIALxLkQDGEIkuf0Ql4rmLxoftmbzb7RWGX56ilsxpie7YrK3xxnHeZAiBKxPL5dmawAAAhQAAAAMcnNhLXNoYTItNTEyAAACADGhUG+gliMrVfKD+WfYkmhL15rmzGJIPdqDwezEqSOcGl3y7QiT3DGIBSSWGvolYF3f2wf7+6COrnOkONIxvpPpH0OSECmDRW5tbcaijyJEEBituqNfTDTJNTDKyYW2jPxWbJCeyn3ESaKJ5q8EhO2I25w63mYxRdMaZUk1WABi2vqo5hPycbGEvwfV30F4+QUpw2RFcUVmbg8gC90OOIjGwonBxG0VPadlMj+ms7YEGm7GiZEdBksrhHdobO7b9T6y3Je9UBkxJMEAIdYSLKkzu4TheGrzIk6/k5Lh8Qq1oVFbe0VpQAE4i6Go39C2gm7noJAO6rPtFMjH789av4sFPssMydwrYaUpfLoDJ8Px/hbD/M61gZSYnccAbadV6fekJH57/MeJFHtaCVMP93i4KbCo/UwQc4lJy3fw2UQOLk/XwuPmkaL0Kb4EI2zjSQEf6AvwuC84D7ObGKcufO6o7BCsUIuQFr2ps0Lscs/jHItXNoMpd/dgBj6RoMNXJujxCkA6bBH33pFGDfyxqO6l37gXCDI7muLSH8oWq9lf+jKHoo9kz46KkbcIG4NAmQHc8PCTh0WqvJA4CQ52GfdtWgqqW27VoYDT5NcU/C28rDGZ8y6uZBA7bFpL+bWkIoqhBOlCwtTVT4NSuLuCWEd7sRGpgshJ+VZ9I3ZkeUjl (null)
|
||||
@@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp384-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgphXMu5lsJKjbFrUqc4ksYylRjgBP3CjCTohjqO8G03EAAAAIbmlzdHAzODQAAABhBOAOQhPx63nmC8GRm1qPNjA5UH/gt36h3eR7PL3D8V0ZBKT+NhT1fB0eArVzdl32NTyBMRaTviL7uFWGCPn+kBq4e89QExo/BttXaBR3FSVTPfT384rTCccOg8ZC19BpQQAAAAAAAAAAAAAAAgAAABFlY2RzYTM4NGNlcnRFY2RzYQAAAAAAAAAAAAAAAP//////////AAAAAAAAAAAAAAAAAAAArAAAABNlY2RzYS1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBOm4mqGkmRjg+N/BstCoV+3OYDp5Qq/s3Lp/CLCNjAHsyEKQJ7U0gHTybdXskhPZwPEQpTZRyXmNyyZUxvRGu6jIB9njA8bq6m8tOrbJz3SY26gcJPtVQgPzz7RkGHLJIvopkzAdKYLtf/vYpiCAP8SU0aZLL8j/WaQQRoZ36QJr2Rp8AAACmAAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAACLAAAAQVOOpC8htogU5LX6pIMaNHZ+6+Q3oRQme1kVhjILPDgMyv5kOzlz1EcI9qDwWjtmoewxYaIVvRHoTCf4WW3QjJ7HAAAAQgG6iDMHm/8oJ55mxWtorVxf/mBqUZDfH2VCx1T34Sw+6GW7Z59hWp6DOanlNDuJEm2eT8sDqF6hHxaYVw4exDYPvg== (null)
|
||||
@@ -0,0 +1 @@
|
||||
ecdsa-sha2-nistp521-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHA1MjEtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgc5mujscEbh/GGGq3kjeEbZEXT0Rt8waJ9hE2Ckz3tfEAAAAIbmlzdHA1MjEAAACFBAErBBojetXoSOJoH71QvOyAUyaEN9kQKUzouR0V2yCkZpBdS+TYLcVduDkXU0dvrj6nqbS32IrG6x3PDk/B4kduJAF0q80dgoCHPWE5t3bwJvGAZh0ABEEETeV/qFtTIZ/HDINcXFfhH6zWVLCw5g+i9YOqx24xwZbQRzVsbuwERCe+7QAAAAAAAAAAAAAAAgAAABNlY2RzYTUyMWNlcnRFZDI1NTE5AAAAAAAAAAAAAAAA//////////8AAAAAAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIKBMEphrClOcaS6ZPoheIcZ2KqTQL7is9UHVWzu5UW7EAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEBgDUlR8fk92XP05DVIy3O1UuJ8ArHamImOS6Vd0ZLsJiFZL+s9VpR7BTgqxXhTS5tgAI2B9a/+gc1BLl8UdbIF (null)
|
||||
@@ -0,0 +1 @@
|
||||
ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIL4D+xZeR0A4m3jiDUGNP42yfOXevsOVY2Tcr1az8su1AAAAIIIxbdWe4p+PoDEFDuAnioB8SDD9gmX2rcBfJ6ULD453AAAAAAAAAAAAAAACAAAADGhvc3RfZWQyNTUxOQAAAAAAAAAAAAAAAP//////////AAAAAAAAAAAAAAAAAAAArAAAABNlY2RzYS1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBOm4mqGkmRjg+N/BstCoV+3OYDp5Qq/s3Lp/CLCNjAHsyEKQJ7U0gHTybdXskhPZwPEQpTZRyXmNyyZUxvRGu6jIB9njA8bq6m8tOrbJz3SY26gcJPtVQgPzz7RkGHLJIvopkzAdKYLtf/vYpiCAP8SU0aZLL8j/WaQQRoZ36QJr2Rp8AAACmAAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAACLAAAAQgFBd6Z6VCjfr/SXZ23IjhEVWM2oHJviwBtrTTB39rr6xONV0GzbyHz9w10B5usM1nS6DsYKF6OgXeOLGNXxxPbxnwAAAEFmMbiXJbQf5D/1WRpwwn3inqoAfP0jx/Wzd/VbpoTHYJYVoti/NXi8f/KNlXWTKJzuM2PFjciFdpkkjlU8oWTNIw== root@Ubuntu1910Desktop
|
||||
@@ -0,0 +1 @@
|
||||
ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg3CCii0zSSIKu+ZGmEN98aghrHzLjsgbgE9zRaIn28v0AAAADAQABAAABgQCwbnZssaQUhTYjuaTvML5W1nETUk4ko0JMXLQONkOxO8dpcVYcDIp/bpgiS5Ch9LbBvdLX8o4i/1Qki90Ar4n5RGhQcu3uoYWZSb9hR//0mMNN0cS98yqRblE+zNKGsmSSjPa16QCMNI0nMOzQ9RLsxo57OH7w52vzxMfuTBU51Ez42soiZWfGo3r90RUmJ/Tw2pGslfpI5qlnI4A37aROZvkCJLt9iDZb2Yje6VbgZQ03fOmeHYV6un2ltDkB+7UtDNBS+xXpXM3ancqoV5nVy7fICww8bGlOhTNMSI+nwZCz9GghFXhG3W7BAzrlWQYkPYUx3aB/F7ltIe8Cr6Fo4ITyjJPRoMyVH0HvYTF+HuJpYiW/jjF5CSXqlKFuareZeQ0wY55bpxUQE7S1gVKs4IY08Ex5U488Tsg8COVpOjJi5XizwUkb44CMPn933fm16yq+JuMXxxOBh2Jyf7ai1w4kbSHnBthrv+FR/TDIMbnW1BnY9Yqk2GhmO/UUKEMAAAAAAAAAAAAAAAIAAAAIaG9zdF9yc2EAAAAAAAAAAAAAAAD//////////wAAAAAAAAAAAAAAAAAAAhcAAAAHc3NoLXJzYQAAAAMBAAEAAAIBAKtyhVgBVoV1rs7zpIRpvfenI/qFLWnuySsP+pFDSxkQ4BN1JS8IPTLjnOXePAZwE85fuZ3F4VoUEA79BGcT77gWQgmaldEeH9nL4P4Kg49a50VyTxvxmzHfrB8DwOxW6pl0BVGuKTnGA/LbI/+DqHbuwYUgvQJ53cD0a5QCN7bY7wgByDT1OownOzJpY5GjZ7P6RciImTF4CCLI4dLiTFkb+ECvbTF+wfHekbtSVf4Ri6ovv+/a5PcDhx4UCL7umhv/TpLGjM6bcHUGXfEYvDQzmjV5AGNrikFUnxxxQz8Egj6+USAugww8+A6RjEwCcBq14aoPkehFZhORhtMfAy2qQ3IzH0Gjo6cisVDeNhHAUXK7wHa+b5Oh3XlhVZa0HgVdDkUdbl/q/ztNP18HgcTeWFJIpPvpYzGsvFRLUjV600W4JvkXB97okJ+oCPLXRDQB3rQzZuA8rr2Pj7G0SJxdnqJkg74CphRLNYX3UlRYoLkOMYypRDFyQl7hffuRlvU7UEiAP17e2K+D+adhjiQPGG0TwJSA8+cEGUIV/5/YlKwAtDs9w0jfcF+ldZGlDt1iuvyUvrlhR2tn9XtZmWKeYDZGVmJyFGebRoCyDNggAvEuRAMYQiS5/RCXiuYvGh+2ZvNvtFYZfnqKWzGmJ7tisrfHGcd5kCIErE8vl2ZrAAACFAAAAAxyc2Etc2hhMi01MTIAAAIAKdc3REv1lclOPSRxP3gtJT0dO6FSr/FdutfUez+byJNV+KqO3CCpqAxvmx82U4E6VqNUn0U2PNzmECBuuf2yKC/LRrl30JW+JSEUyv/jGbZ71bX36496aHrwVQz4N6xflVcb7FgXGJUYxMszLxnVjvfs/V96GLtkCeaPwFh5rsUJLlgmoInJ6COjUie0ex3DuMO+wzBlUUaSEgRrBc1b4JItLEZDFJB4ITX931oupAoIGlscc7a5tW8bLZ9s920JewBwEHVZEWeJY4rWLzAxJvpGUO6l79FrUyvgnAhGWrKtvcKZl+LXjT30bATfh+CvBODp2wyQMvdt7Lc96/eXXuklAGMRU7ZrHAk9pLOl50LhbiOMs6zyR8A5vEbUq2x8Lc4N+WA/EPNTwzaIsgCgihoKoVxNBQY/6tXJr9B7+owmYqQulrVMnc81sr0ZfDv8JhNEr/Q9Gl4QC8tOU1irQLQYoIO3MxYzYwbD0SulcoPjxG5IprPCACLNu+IyJ5inomF5gswVF0iEL6S1ybgGL19IXen4uVXufqGebdvD4cUfpWLWszAT9bS3VyEx3UrNBF7HHsn6HBcSHFZL2Y3w1Hb0IGyylIZ4DG/c32o2aahjatF5FIOk4hAyxKER/vNuKFQcNF/pBZl7qDpGfQh8oogyeubH8FzDRhwrnMp5FwY= root@Ubuntu1910Desktop
|
||||
@@ -0,0 +1,2 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCvWBFUpRdXqyRAHLIqG2oFFaqXWZ1YORiIx0l7fiO1yG0Bn41SaQaWnzBrCWnPGJkWDnCb9d3b0o81i6ggibwTYPHF67eiJyyaNCEnYMJOcvovfmEB8q/A0ZfN+oqSOXBUoUZhvqdOG4FFzbI/faTVsiM8HH8piZH1eHcRK8i2UsKzNAykKzYxmdYIFrHP6McgRrta0TY6s8XWWNqwojrH8UPnUSxOYCX89eDs3wPKzyrysN+N/proLIqG0xPLjylKTOzc1Mg2cy84P8fLNiliZL1rWG6OU6PyPGJedwf+9rFjAi7ZORnAEjJ5zAn7K51sefo9wX12/ddIRGin02/ySrvc28+AqBHA2gd3usvsNvlQdLPVG4t36+KqttFi6naFd4M4v+oY2lwow+kvcWzxPsZJAUfADGhrPnIScr49m9E04dprb2NDCw50MRTEFvYE71L0jOvRB/6s2ZBOXA5PiexxEB5RokRiHroOmwowhdwY7JQxqRKl+DblMDc4ENUZQ0l3P5ijXWZGIzp8LbUK2N1nDc8uqQAaM1kSUTkUEZI5aUTcqdaOl5/14XHkWskBp09zJNsI6VoV17C1E/SdwznLbCDPf8WgJtYNKh5q3OR5ZFBiBsR2PYKZg9sk/wrrlnu8VfgVYQ5o4HzgZg8vYx79acfj9JosLnVGcyA5Ew==
|
||||
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLvxUyJKR9Jxxutu2sMEoZ1MsIxUdMNqYcpmbUX8Yu4kdGx+Wr4ktOXv8CseoXSkY2W2/lh8g/roE6N4H6cQWk8=
|
||||
@@ -53,6 +53,8 @@ namespace Renci.SshNet.TestTools.OpenSSH
|
||||
/// </value>
|
||||
public List<string> HostKeyFiles { get; }
|
||||
|
||||
public string? HostCertificate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value specifying whether challenge-response authentication is allowed.
|
||||
/// </summary>
|
||||
@@ -118,6 +120,11 @@ namespace Renci.SshNet.TestTools.OpenSSH
|
||||
/// </summary>
|
||||
public List<MessageAuthenticationCodeAlgorithm> MessageAuthenticationCodeAlgorithms { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the filepaths of the trusted user CA (certificate authority) keys.
|
||||
/// </summary>
|
||||
public string? TrustedUserCAKeys { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether <c>sshd</c> should print <c>/etc/motd</c> when a user logs in interactively.
|
||||
/// </summary>
|
||||
@@ -291,6 +298,11 @@ namespace Renci.SshNet.TestTools.OpenSSH
|
||||
writer.WriteLine("HostKeyAlgorithms " + string.Join(",", HostKeyAlgorithms.Select(c => c.Name).ToArray()));
|
||||
}
|
||||
|
||||
if (HostCertificate is not null)
|
||||
{
|
||||
writer.WriteLine("HostCertificate " + HostCertificate);
|
||||
}
|
||||
|
||||
if (KeyExchangeAlgorithms.Count > 0)
|
||||
{
|
||||
writer.WriteLine("KexAlgorithms " + string.Join(",", KeyExchangeAlgorithms.Select(c => c.Name).ToArray()));
|
||||
@@ -306,6 +318,11 @@ namespace Renci.SshNet.TestTools.OpenSSH
|
||||
writer.WriteLine("PubkeyAcceptedAlgorithms " + string.Join(",", PublicKeyAcceptedAlgorithms.Select(c => c.Name).ToArray()));
|
||||
}
|
||||
|
||||
if (TrustedUserCAKeys is not null)
|
||||
{
|
||||
writer.WriteLine("TrustedUserCAKeys " + TrustedUserCAKeys);
|
||||
}
|
||||
|
||||
foreach (var match in Matches)
|
||||
{
|
||||
_matchFormatter.Format(match, writer);
|
||||
@@ -384,6 +401,12 @@ namespace Renci.SshNet.TestTools.OpenSSH
|
||||
case "AllowTcpForwarding":
|
||||
sshdConfig.AllowTcpForwarding = ToBool(value);
|
||||
break;
|
||||
case "TrustedUserCAKeys":
|
||||
sshdConfig.TrustedUserCAKeys = value;
|
||||
break;
|
||||
case "HostCertificate":
|
||||
sshdConfig.HostCertificate = value;
|
||||
break;
|
||||
case "KeyRegenerationInterval":
|
||||
case "HostbasedAuthentication":
|
||||
case "ServerKeyBits":
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
@@ -16,6 +17,12 @@ namespace Renci.SshNet.Tests.Classes
|
||||
[TestClass]
|
||||
public class PrivateKeyFileTest : TestBase
|
||||
{
|
||||
#if NETFRAMEWORK
|
||||
private static readonly DateTimeOffset UnixEpoch = new(1970, 01, 01, 00, 00, 00, TimeSpan.Zero);
|
||||
#else
|
||||
private static readonly DateTimeOffset UnixEpoch = DateTimeOffset.UnixEpoch;
|
||||
#endif
|
||||
|
||||
private string _temporaryFile;
|
||||
|
||||
[TestInitialize]
|
||||
@@ -364,6 +371,111 @@ namespace Renci.SshNet.Tests.Classes
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Test_Certificate_OPENSSH_RSA()
|
||||
{
|
||||
PrivateKeyFile pkFile;
|
||||
|
||||
using (var privateKeyStream = GetData("Key.OPENSSH.RSA.txt"))
|
||||
using (var certificateStream = GetData("Key.OPENSSH.RSA-cert.pub"))
|
||||
{
|
||||
pkFile = new PrivateKeyFile(privateKeyStream, passPhrase: null, certificateStream);
|
||||
}
|
||||
|
||||
Certificate cert = pkFile.Certificate;
|
||||
|
||||
// ssh-keygen -L -f Key.OPENSSH.RSA-cert.pub
|
||||
|
||||
Assert.AreEqual("ssh-rsa-cert-v01@openssh.com", cert.Name);
|
||||
|
||||
Assert.IsInstanceOfType<RsaKey>(cert.Key);
|
||||
CollectionAssert.AreEqual(((RsaKey)pkFile.Key).Public, ((RsaKey)cert.Key).Public);
|
||||
Assert.AreEqual(0UL, cert.Serial);
|
||||
Assert.AreEqual(Certificate.CertificateType.User, cert.Type);
|
||||
Assert.AreEqual("rsa-cert-rsa", cert.KeyId);
|
||||
CollectionAssert.AreEqual(new string[] { "sshnet" }, cert.ValidPrincipals.ToList());
|
||||
Assert.AreEqual(0, cert.CriticalOptions.Count);
|
||||
Assert.IsTrue(cert.ValidAfter.EqualsExact(new DateTimeOffset(2024, 07, 17, 20, 50, 34, TimeSpan.Zero)));
|
||||
Assert.AreEqual(ulong.MaxValue, cert.ValidBeforeUnixSeconds);
|
||||
Assert.AreEqual(DateTimeOffset.MaxValue, cert.ValidBefore);
|
||||
CollectionAssert.AreEqual(new Dictionary<string, string>
|
||||
{
|
||||
["permit-X11-forwarding"] = "",
|
||||
["permit-agent-forwarding"] = "",
|
||||
["permit-port-forwarding"] = "",
|
||||
["permit-pty"] = "",
|
||||
["permit-user-rc"] = "",
|
||||
}, new Dictionary<string, string>(cert.Extensions));
|
||||
Assert.AreEqual("NqLEgdYti0XjUkYjGyQv2Ddy1O5v2NZDZFRtlfESLIA", cert.CertificateAuthorityKeyFingerPrint);
|
||||
|
||||
Assert.AreEqual(6, pkFile.HostKeyAlgorithms.Count);
|
||||
|
||||
var algorithms = pkFile.HostKeyAlgorithms.ToList();
|
||||
|
||||
Assert.AreEqual("rsa-sha2-512-cert-v01@openssh.com", algorithms[0].Name);
|
||||
Assert.AreEqual("rsa-sha2-256-cert-v01@openssh.com", algorithms[1].Name);
|
||||
Assert.AreEqual("ssh-rsa-cert-v01@openssh.com", algorithms[2].Name);
|
||||
Assert.AreEqual("ssh-rsa", algorithms[3].Name);
|
||||
Assert.AreEqual("rsa-sha2-512", algorithms[4].Name);
|
||||
Assert.AreEqual("rsa-sha2-256", algorithms[5].Name);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Test_CertificateKeyMismatch()
|
||||
{
|
||||
using (var privateKey = GetData("Key.OPENSSH.RSA.txt"))
|
||||
using (var certificate = GetData("Key.OPENSSH.ECDSA521-cert.pub"))
|
||||
{
|
||||
Assert.ThrowsException<ArgumentException>(() => new PrivateKeyFile(privateKey, passPhrase: null, certificate));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Test_Certificate_OPENSSH_ECDSA()
|
||||
{
|
||||
PrivateKeyFile pkFile;
|
||||
|
||||
using (var privateKeyStream = GetData("Key.OPENSSH.ECDSA521.txt"))
|
||||
using (var certificateStream = GetData("Key.OPENSSH.ECDSA521-cert.pub"))
|
||||
{
|
||||
pkFile = new PrivateKeyFile(privateKeyStream, passPhrase: null, certificateStream);
|
||||
}
|
||||
|
||||
Certificate cert = pkFile.Certificate;
|
||||
|
||||
// ssh-keygen -L -f Key.OPENSSH.ECDSA521-cert.pub
|
||||
|
||||
Assert.AreEqual("ecdsa-sha2-nistp521-cert-v01@openssh.com", cert.Name);
|
||||
|
||||
Assert.IsInstanceOfType<EcdsaKey>(cert.Key);
|
||||
CollectionAssert.AreEqual(((EcdsaKey)pkFile.Key).Public, ((EcdsaKey)cert.Key).Public);
|
||||
Assert.AreEqual(0UL, cert.Serial);
|
||||
Assert.AreEqual(Certificate.CertificateType.User, cert.Type);
|
||||
Assert.AreEqual("ecdsa521certEcdsa", cert.KeyId);
|
||||
CollectionAssert.AreEqual(new string[] { "sshnet" }, cert.ValidPrincipals.ToList());
|
||||
Assert.AreEqual(0, cert.CriticalOptions.Count);
|
||||
Assert.AreEqual(0UL, cert.ValidAfterUnixSeconds);
|
||||
Assert.IsTrue(cert.ValidAfter.EqualsExact(UnixEpoch));
|
||||
Assert.AreEqual(ulong.MaxValue, cert.ValidBeforeUnixSeconds);
|
||||
Assert.AreEqual(DateTimeOffset.MaxValue, cert.ValidBefore);
|
||||
CollectionAssert.AreEqual(new Dictionary<string, string>
|
||||
{
|
||||
["permit-X11-forwarding"] = "",
|
||||
["permit-agent-forwarding"] = "",
|
||||
["permit-port-forwarding"] = "",
|
||||
["permit-pty"] = "",
|
||||
["permit-user-rc"] = "",
|
||||
}, new Dictionary<string, string>(cert.Extensions));
|
||||
Assert.AreEqual("r/t6I+bZQzN5BhSuntFSHDHlrnNHVM2lAo6gbvynG/4", cert.CertificateAuthorityKeyFingerPrint);
|
||||
|
||||
Assert.AreEqual(2, pkFile.HostKeyAlgorithms.Count);
|
||||
|
||||
var algorithms = pkFile.HostKeyAlgorithms.ToList();
|
||||
|
||||
Assert.AreEqual("ecdsa-sha2-nistp521-cert-v01@openssh.com", algorithms[0].Name);
|
||||
Assert.AreEqual("ecdsa-sha2-nistp521", algorithms[1].Name);
|
||||
}
|
||||
|
||||
private void SaveStreamToFile(Stream stream, string fileName)
|
||||
{
|
||||
var buffer = new byte[4000];
|
||||
|
||||
@@ -0,0 +1,389 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Renci.SshNet.Security;
|
||||
using Renci.SshNet.Security.Cryptography;
|
||||
using Renci.SshNet.Tests.Common;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes.Security
|
||||
{
|
||||
[TestClass]
|
||||
public class CertificateHostAlgorithmTest : TestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void NoSuppliedDigitalSignature_PropertyIsKeyDigitalSignature()
|
||||
{
|
||||
(RsaKey key, Certificate certificate) = GetRsaKey();
|
||||
|
||||
CertificateHostAlgorithm algorithm = new("ssh-rsa-cert-v01@openssh.com", key, certificate);
|
||||
|
||||
Assert.AreEqual("ssh-rsa-cert-v01@openssh.com", algorithm.Name);
|
||||
Assert.AreSame(key, algorithm.Key);
|
||||
Assert.AreSame(certificate, algorithm.Certificate);
|
||||
Assert.AreSame(key.DigitalSignature, algorithm.DigitalSignature);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SuppliedDigitalSignature_PropertyIsSuppliedDigitalSignature()
|
||||
{
|
||||
(RsaKey key, Certificate certificate) = GetRsaKey();
|
||||
|
||||
RsaDigitalSignature rsaDigitalSignature = new(key, HashAlgorithmName.SHA256);
|
||||
|
||||
CertificateHostAlgorithm algorithm = new(
|
||||
"rsa-sha2-256-cert-v01@openssh.com",
|
||||
key,
|
||||
certificate,
|
||||
rsaDigitalSignature);
|
||||
|
||||
Assert.AreEqual("rsa-sha2-256-cert-v01@openssh.com", algorithm.Name);
|
||||
Assert.AreSame(key, algorithm.Key);
|
||||
Assert.AreSame(certificate, algorithm.Certificate);
|
||||
Assert.AreSame(rsaDigitalSignature, algorithm.DigitalSignature);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void HostAlgorithmData_IsRawCertificateBytes()
|
||||
{
|
||||
PrivateKeyFile pkFile;
|
||||
byte[] certificateData;
|
||||
|
||||
using (Stream keyStream = GetData("Key.OPENSSH.RSA.txt"))
|
||||
using (Stream certStream = GetData("Key.OPENSSH.RSA-cert.pub"))
|
||||
{
|
||||
using MemoryStream ms = new();
|
||||
certStream.CopyTo(ms);
|
||||
|
||||
certificateData = Convert.FromBase64String(Encoding.UTF8.GetString(ms.ToArray()).Split(' ')[1]);
|
||||
|
||||
ms.Position = 0;
|
||||
|
||||
pkFile = new PrivateKeyFile(keyStream, null, ms);
|
||||
}
|
||||
|
||||
List<CertificateHostAlgorithm> certAlgs = pkFile.HostKeyAlgorithms.OfType<CertificateHostAlgorithm>().ToList();
|
||||
|
||||
Assert.AreEqual(3, certAlgs.Count);
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
Assert.IsNotNull(certAlgs[i].Certificate);
|
||||
Assert.AreSame(pkFile.Certificate, certAlgs[i].Certificate);
|
||||
CollectionAssert.AreEqual(certificateData, certAlgs[i].Data);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SshRsa_SignAndVerify()
|
||||
{
|
||||
byte[] data = Encoding.UTF8.GetBytes("hello world");
|
||||
|
||||
(RsaKey key, Certificate certificate) = GetRsaKey();
|
||||
|
||||
CertificateHostAlgorithm algorithm = new("ssh-rsa-cert-v01@openssh.com", key, certificate);
|
||||
|
||||
byte[] expectedEncodedSignatureBytes = new byte[]
|
||||
{
|
||||
0, 0, 0, 7, // byte count of "ssh-rsa"
|
||||
(byte)'s', (byte)'s', (byte)'h', (byte)'-', (byte)'r', (byte)'s', (byte)'a', // ssh-rsa
|
||||
0, 0, 1, 0, // byte count of signature (=256)
|
||||
|
||||
// ssh-keygen -e -f Key.OPENSSH.RSA.txt -m PEM -p
|
||||
// echo -n 'hello world' | openssl dgst -sha1 -sign Key.OPENSSH.RSA.txt -out test.signed
|
||||
0x2d, 0x54, 0x2e, 0x6a, 0x5f, 0x7c, 0x29, 0x7d, 0x2d, 0x81, 0xf6, 0x34, 0x45, 0x7a, 0x3f, 0xd0,
|
||||
0xa5, 0x06, 0x55, 0x9c, 0xab, 0x8c, 0x28, 0x76, 0x27, 0xc0, 0x8a, 0x32, 0x23, 0xa4, 0x62, 0xd1,
|
||||
0x8c, 0x72, 0x05, 0x52, 0x47, 0x4d, 0xd0, 0xde, 0x86, 0xdd, 0xfc, 0x38, 0x54, 0x47, 0x4e, 0x17,
|
||||
0xef, 0x6b, 0x9a, 0x2e, 0x4d, 0x55, 0xf3, 0x2a, 0x11, 0xa7, 0x3a, 0x8b, 0x37, 0xbb, 0x61, 0x2d,
|
||||
0xb8, 0x4c, 0x1f, 0xa1, 0x0f, 0xb4, 0xbe, 0x06, 0xea, 0xc1, 0x4e, 0x17, 0x3c, 0x53, 0x01, 0x1b,
|
||||
0x41, 0x3b, 0x3c, 0x86, 0xb7, 0x55, 0x4d, 0xe6, 0xcb, 0x9d, 0x0e, 0x6f, 0x18, 0x10, 0x63, 0x3c,
|
||||
0xcd, 0x02, 0x32, 0x9f, 0xbe, 0x58, 0x22, 0xa1, 0x24, 0x61, 0xf3, 0x1e, 0xa8, 0xbd, 0xf7, 0x0e,
|
||||
0x9a, 0xeb, 0x42, 0x5c, 0xf5, 0xdb, 0x3b, 0x65, 0x22, 0xb1, 0x54, 0x7f, 0xe0, 0x62, 0xae, 0xb3,
|
||||
0xab, 0x7b, 0xfe, 0x4b, 0x80, 0x7a, 0xd1, 0x5e, 0xd2, 0x0a, 0xa3, 0x4d, 0x1a, 0xf5, 0xa8, 0xbf,
|
||||
0x87, 0xfc, 0x91, 0x57, 0xf1, 0xc2, 0x58, 0xea, 0x7a, 0xbc, 0xdf, 0x86, 0xb4, 0x24, 0x32, 0x10,
|
||||
0x72, 0x2e, 0x91, 0x15, 0xa7, 0x39, 0xb5, 0x22, 0x7a, 0xe1, 0x88, 0xbd, 0x23, 0xa6, 0x05, 0xe2,
|
||||
0x20, 0x22, 0x46, 0x68, 0x56, 0x34, 0x2e, 0x08, 0x35, 0xa7, 0x4b, 0x4f, 0x54, 0xcb, 0xf9, 0x53,
|
||||
0xd1, 0x41, 0xf6, 0xac, 0x23, 0xf8, 0x0e, 0x90, 0x1e, 0xea, 0x4c, 0xdb, 0xa3, 0xb6, 0xdb, 0x5f,
|
||||
0xf9, 0xc4, 0xf3, 0x08, 0x12, 0x32, 0xa8, 0xa2, 0xa1, 0x8c, 0x1d, 0x5f, 0xf7, 0x18, 0x79, 0x4c,
|
||||
0xd4, 0x28, 0xc6, 0xe9, 0x55, 0xbc, 0x80, 0xc2, 0x08, 0x1f, 0x8f, 0x8d, 0x35, 0x0b, 0xa9, 0x49,
|
||||
0x80, 0xba, 0x32, 0xba, 0xe0, 0xf6, 0x2f, 0x7f, 0xf2, 0xb7, 0xaf, 0xfa, 0xfd, 0xc8, 0x7a, 0x66,
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(expectedEncodedSignatureBytes, algorithm.Sign(data));
|
||||
|
||||
algorithm = new CertificateHostAlgorithm(
|
||||
"ssh-rsa-cert-v01@openssh.com",
|
||||
certificate,
|
||||
DefaultKeyAlgs);
|
||||
|
||||
Assert.IsTrue(algorithm.VerifySignature(data, expectedEncodedSignatureBytes));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RsaSha256_SignAndVerify()
|
||||
{
|
||||
byte[] data = Encoding.UTF8.GetBytes("hello world");
|
||||
|
||||
(RsaKey key, Certificate certificate) = GetRsaKey();
|
||||
|
||||
CertificateHostAlgorithm algorithm = new(
|
||||
"rsa-sha2-256-cert-v01@openssh.com",
|
||||
key,
|
||||
certificate,
|
||||
new RsaDigitalSignature(key, HashAlgorithmName.SHA256));
|
||||
|
||||
byte[] expectedEncodedSignatureBytes = new byte[]
|
||||
{
|
||||
0, 0, 0, 12, // byte count of "rsa-sha2-256"
|
||||
(byte)'r', (byte)'s', (byte)'a', (byte)'-', (byte)'s', (byte)'h', (byte)'a', (byte)'2',
|
||||
(byte)'-', (byte)'2', (byte)'5', (byte)'6',
|
||||
0, 0, 1, 0, // byte count of signature (=256)
|
||||
|
||||
// ssh-keygen -e -f Key.OPENSSH.RSA.txt -m PEM -p
|
||||
// echo -n 'hello world' | openssl dgst -sha256 -sign Key.OPENSSH.RSA.txt -out test.signed
|
||||
0x18, 0xf4, 0x3e, 0xa9, 0xdf, 0x89, 0x92, 0x6b, 0xc1, 0x6a, 0x35, 0x72, 0x42, 0x56, 0xf7, 0x50,
|
||||
0x32, 0x33, 0xff, 0xc4, 0x91, 0x3d, 0x49, 0x12, 0x37, 0x52, 0x98, 0x37, 0xb8, 0xeb, 0xeb, 0xaa,
|
||||
0xe5, 0x4e, 0xd4, 0x99, 0x74, 0xfd, 0xea, 0xd6, 0x8f, 0x34, 0xa0, 0x3a, 0x0e, 0xfd, 0xcb, 0xae,
|
||||
0x04, 0x20, 0x01, 0x1c, 0x67, 0x98, 0x94, 0x6c, 0xdb, 0x26, 0x9a, 0x0c, 0x5b, 0xcf, 0x9a, 0x06,
|
||||
0xa5, 0x90, 0xfb, 0x62, 0xe8, 0x56, 0x91, 0xdf, 0x63, 0x1f, 0xc3, 0xb1, 0xd3, 0x4f, 0x18, 0x2b,
|
||||
0x2e, 0xfa, 0xb4, 0x61, 0x1d, 0x54, 0xdd, 0x63, 0x14, 0x17, 0x31, 0x8e, 0x86, 0xe3, 0xc2, 0xb1,
|
||||
0x30, 0x42, 0x1e, 0x5a, 0x43, 0x87, 0x54, 0x64, 0xd5, 0xbb, 0xcb, 0x37, 0x7b, 0xa6, 0x97, 0x75,
|
||||
0xca, 0x3b, 0x0d, 0xb2, 0x24, 0x34, 0x0b, 0xfc, 0xde, 0x67, 0xbf, 0xdf, 0x2a, 0x8b, 0xc6, 0xac,
|
||||
0x51, 0x0d, 0x98, 0x54, 0xed, 0x57, 0x5e, 0xa9, 0xbe, 0x0f, 0x0c, 0x0f, 0x30, 0x23, 0x96, 0x83,
|
||||
0x65, 0x74, 0x87, 0x91, 0x99, 0x21, 0x88, 0x80, 0x6d, 0xe4, 0xec, 0xcb, 0x51, 0xe5, 0xe5, 0x3a,
|
||||
0x2b, 0x34, 0x9b, 0x10, 0x70, 0xef, 0x57, 0x40, 0x59, 0x45, 0x94, 0x58, 0xd0, 0x65, 0x84, 0x23,
|
||||
0x5e, 0xcd, 0x49, 0xea, 0x18, 0x51, 0x29, 0xdd, 0x84, 0x05, 0x24, 0xe4, 0x65, 0x0c, 0x38, 0x8e,
|
||||
0x42, 0x33, 0xdf, 0xcb, 0x3c, 0xa0, 0x0d, 0xe2, 0x2d, 0x13, 0xbd, 0xea, 0x51, 0x06, 0xdd, 0x61,
|
||||
0x87, 0x05, 0xbe, 0xef, 0xaa, 0x77, 0xe4, 0xef, 0x25, 0x6b, 0xbf, 0x24, 0xd7, 0xe4, 0xba, 0x25,
|
||||
0x28, 0x49, 0x26, 0xc2, 0x31, 0xca, 0xbb, 0x1a, 0x2c, 0x19, 0xa3, 0x7b, 0x62, 0x12, 0x59, 0x75,
|
||||
0x12, 0x03, 0x38, 0xc9, 0x69, 0x93, 0xe6, 0xec, 0xc8, 0x13, 0x25, 0x48, 0xd2, 0x6c, 0x67, 0x10,
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(expectedEncodedSignatureBytes, algorithm.Sign(data));
|
||||
|
||||
algorithm = new CertificateHostAlgorithm(
|
||||
"rsa-sha2-256-cert-v01@openssh.com",
|
||||
certificate,
|
||||
new RsaDigitalSignature((RsaKey)certificate.Key, HashAlgorithmName.SHA256),
|
||||
DefaultKeyAlgs);
|
||||
|
||||
Assert.IsTrue(algorithm.VerifySignature(data, expectedEncodedSignatureBytes));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RsaSha512_SignAndVerify()
|
||||
{
|
||||
byte[] data = Encoding.UTF8.GetBytes("hello world");
|
||||
|
||||
(RsaKey key, Certificate certificate) = GetRsaKey();
|
||||
|
||||
CertificateHostAlgorithm algorithm = new(
|
||||
"rsa-sha2-512-cert-v01@openssh.com",
|
||||
key,
|
||||
certificate,
|
||||
new RsaDigitalSignature(key, HashAlgorithmName.SHA512));
|
||||
|
||||
byte[] expectedEncodedSignatureBytes = new byte[]
|
||||
{
|
||||
0, 0, 0, 12, // byte count of "rsa-sha2-512"
|
||||
(byte)'r', (byte)'s', (byte)'a', (byte)'-', (byte)'s', (byte)'h', (byte)'a', (byte)'2',
|
||||
(byte)'-', (byte)'5', (byte)'1', (byte)'2',
|
||||
0, 0, 1, 0, // byte count of signature (=256)
|
||||
|
||||
// ssh-keygen -e -f Key.OPENSSH.RSA.txt -m PEM -p
|
||||
// echo -n 'hello world' | openssl dgst -sha512 -sign Key.OPENSSH.RSA.txt -out test.signed
|
||||
0x1d, 0x64, 0xc6, 0x82, 0xb0, 0xc4, 0x2b, 0xe1, 0x71, 0x13, 0x1f, 0x62, 0xab, 0x8f, 0xf8, 0x72,
|
||||
0x43, 0xe8, 0x95, 0x4c, 0x8d, 0xa6, 0xf7, 0xcd, 0x62, 0xc9, 0x6f, 0xe5, 0xbf, 0x23, 0x1b, 0xc7,
|
||||
0xa0, 0x93, 0xc6, 0xc0, 0xa2, 0x06, 0x2d, 0x07, 0x16, 0x59, 0xbc, 0x0d, 0xe5, 0x00, 0x39, 0x56,
|
||||
0xa7, 0xde, 0x4b, 0x17, 0xf4, 0x02, 0xf6, 0x5d, 0x8f, 0xc5, 0x76, 0xe2, 0xb7, 0xae, 0xe5, 0xa2,
|
||||
0x7f, 0xd8, 0x34, 0x04, 0x2c, 0xbc, 0xdf, 0x84, 0x51, 0x69, 0x83, 0xda, 0x7a, 0x74, 0x19, 0xe9,
|
||||
0x6e, 0x02, 0xf8, 0x51, 0x20, 0xa2, 0x67, 0x43, 0xbb, 0xde, 0x7a, 0xa7, 0x12, 0xe7, 0x89, 0x7c,
|
||||
0x50, 0xf3, 0xd5, 0x07, 0xc9, 0x70, 0x22, 0xed, 0x2e, 0x45, 0x1e, 0x49, 0x23, 0x94, 0x69, 0xae,
|
||||
0x8f, 0x5d, 0x3b, 0x34, 0xdb, 0xc8, 0x49, 0x26, 0x09, 0x81, 0x7d, 0xad, 0x77, 0xb5, 0x6d, 0xad,
|
||||
0x0c, 0x9f, 0x66, 0x29, 0x56, 0xff, 0xea, 0xa7, 0x6f, 0x7f, 0xcd, 0xc0, 0x15, 0x05, 0xdc, 0xee,
|
||||
0xfb, 0xac, 0xfd, 0x59, 0x19, 0x30, 0x32, 0x6e, 0x16, 0xe0, 0x4e, 0x74, 0x6a, 0x13, 0xa7, 0x9f,
|
||||
0x5b, 0x71, 0x75, 0x13, 0xcf, 0xa5, 0xf3, 0x07, 0x8f, 0xfb, 0xa2, 0xa2, 0x92, 0xc2, 0x41, 0xc4,
|
||||
0xbc, 0x14, 0x75, 0x22, 0xe3, 0x4b, 0xb7, 0xc0, 0x54, 0xc3, 0x25, 0x87, 0xbb, 0x52, 0xde, 0x70,
|
||||
0x69, 0xc6, 0x68, 0x66, 0x3a, 0x88, 0xf6, 0x3b, 0x8e, 0x44, 0x00, 0x25, 0x17, 0xc9, 0x44, 0x7c,
|
||||
0xcc, 0x0c, 0x63, 0xab, 0xa3, 0x2c, 0xaa, 0x4c, 0x34, 0xda, 0xe0, 0x96, 0x71, 0x83, 0xe5, 0x7a,
|
||||
0xec, 0x56, 0xbe, 0x85, 0x27, 0x7c, 0xe7, 0x79, 0xfd, 0xb8, 0x77, 0x41, 0x05, 0x25, 0x30, 0x57,
|
||||
0x24, 0x45, 0xa9, 0x12, 0x9e, 0xdc, 0x9e, 0x23, 0x43, 0x13, 0x67, 0x38, 0x59, 0xae, 0x4b, 0x76,
|
||||
};
|
||||
|
||||
CollectionAssert.AreEqual(expectedEncodedSignatureBytes, algorithm.Sign(data));
|
||||
|
||||
algorithm = new CertificateHostAlgorithm(
|
||||
"rsa-sha2-512-cert-v01@openssh.com",
|
||||
certificate,
|
||||
new RsaDigitalSignature((RsaKey)certificate.Key, HashAlgorithmName.SHA512),
|
||||
DefaultKeyAlgs);
|
||||
|
||||
Assert.IsTrue(algorithm.VerifySignature(data, expectedEncodedSignatureBytes));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VerifySignature_NoCorrespondingAlgorithm_ReturnsFalse()
|
||||
{
|
||||
byte[] data = Encoding.UTF8.GetBytes("hello world");
|
||||
|
||||
(RsaKey key, Certificate certificate) = GetRsaKey();
|
||||
|
||||
CertificateHostAlgorithm algorithm = new(
|
||||
"rsa-sha2-512-cert-v01@openssh.com",
|
||||
key,
|
||||
certificate,
|
||||
new RsaDigitalSignature(key, HashAlgorithmName.SHA512));
|
||||
|
||||
byte[] signature = algorithm.Sign(data);
|
||||
|
||||
algorithm = new CertificateHostAlgorithm(
|
||||
"rsa-sha2-512-cert-v01@openssh.com",
|
||||
certificate,
|
||||
new RsaDigitalSignature((RsaKey)certificate.Key, HashAlgorithmName.SHA512),
|
||||
new Dictionary<string, Func<byte[], KeyHostAlgorithm>>());
|
||||
|
||||
Assert.IsFalse(algorithm.VerifySignature(data, signature));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VerifySignature_NoSuppliedAlgorithms_Throws()
|
||||
{
|
||||
byte[] data = Encoding.UTF8.GetBytes("hello world");
|
||||
|
||||
(RsaKey key, Certificate certificate) = GetRsaKey();
|
||||
|
||||
CertificateHostAlgorithm algorithm = new(
|
||||
"rsa-sha2-512-cert-v01@openssh.com",
|
||||
key,
|
||||
certificate,
|
||||
new RsaDigitalSignature(key, HashAlgorithmName.SHA512));
|
||||
|
||||
byte[] signature = algorithm.Sign(data);
|
||||
|
||||
var ex = Assert.ThrowsException<InvalidOperationException>(() => algorithm.VerifySignature(data, signature));
|
||||
Assert.IsTrue(ex.Message.StartsWith("Invalid usage", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CertificateBadCASignature_VerifySignatureReturnsFalse()
|
||||
{
|
||||
// ssh-keygen -s Key.OPENSSH.ED25519.txt -I test Key.OPENSSH.ECDSA.txt
|
||||
string goodCertString = "ecdsa-sha2-nistp256-cert-v01@openssh.com " +
|
||||
"AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AA" +
|
||||
"AAg1vQFCYTYufJiCBFJBWc63sOGwnJ3BHQn4ig499dtB0AAAAAIbmlzdHAyNT" +
|
||||
"YAAABBBI/dlNvfssW9KYrB67TcDmz9zBzDf7eMvUupAroP3b3FjUnYnpL3Utc" +
|
||||
"4GkF/PiX7w2DuxaG70/+EX/CYHZBHKCsAAAAAAAAAAAAAAAEAAAAEdGVzdAAA" +
|
||||
"AAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3Y" +
|
||||
"XJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcG" +
|
||||
"VybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAA" +
|
||||
"OcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAg" +
|
||||
"DQlmcNCvFBlw0At9lgbss8BbUxgQa9VbmeN7s6UwYyIAAABTAAAAC3NzaC1lZ" +
|
||||
"DI1NTE5AAAAQA8+LXQ++nb1/gNEtURKt5Yo/geUc/+3+Bv3EPGno5JhxvekjJ" +
|
||||
"PD7/nXcyxnY3zALlPQTxb19EVx5lz58BS96gg=";
|
||||
|
||||
char[] chars = goodCertString.ToCharArray();
|
||||
chars[^10] = 'a';
|
||||
string badCertString = new string(chars);
|
||||
|
||||
Assert.IsTrue(VerifySignature(goodCertString));
|
||||
Assert.IsFalse(VerifySignature(badCertString));
|
||||
|
||||
static bool VerifySignature(string certString)
|
||||
{
|
||||
PrivateKeyFile pk;
|
||||
|
||||
using (Stream keyStream = GetData("Key.OPENSSH.ECDSA.txt"))
|
||||
using (MemoryStream certStream = new MemoryStream(Encoding.UTF8.GetBytes(certString)))
|
||||
{
|
||||
pk = new PrivateKeyFile(keyStream, null, certStream);
|
||||
}
|
||||
|
||||
Assert.IsNotNull(pk.Certificate);
|
||||
|
||||
byte[] data = Encoding.UTF8.GetBytes("hello world");
|
||||
|
||||
CertificateHostAlgorithm certificateAlgorithm = new(
|
||||
"ecdsa-sha2-nistp256-cert-v01@openssh.com",
|
||||
pk.Certificate,
|
||||
DefaultKeyAlgs);
|
||||
|
||||
KeyHostAlgorithm keyHostAlgorithm = new KeyHostAlgorithm("ecdsa-sha2-nistp256", pk.Key);
|
||||
|
||||
byte[] signature = keyHostAlgorithm.Sign(data);
|
||||
|
||||
Assert.IsTrue(keyHostAlgorithm.VerifySignature(data, signature));
|
||||
|
||||
return certificateAlgorithm.VerifySignature(data, signature);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CertificateValidityPeriodExpired_VerifySignatureReturnsFalse()
|
||||
{
|
||||
// ssh-keygen -s Key.OPENSSH.ED25519.txt -I nolongervalid -V always:20240101 Key.OPENSSH.ECDSA.txt
|
||||
string certString = "ecdsa-sha2-nistp256-cert-v01@openssh.com " +
|
||||
"AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb" +
|
||||
"20AAAAg5BUo6CqGzTDc0UgNcLUqna2bH3C69NZCzd9CrQ8apQUAAAAIbm" +
|
||||
"lzdHAyNTYAAABBBI/dlNvfssW9KYrB67TcDmz9zBzDf7eMvUupAroP3b3" +
|
||||
"FjUnYnpL3Utc4GkF/PiX7w2DuxaG70/+EX/CYHZBHKCsAAAAAAAAAAAAA" +
|
||||
"AAEAAAANbm9sb25nZXJ2YWxpZAAAAAAAAAAAAAAAAAAAAABlkgCAAAAAA" +
|
||||
"AAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaX" +
|
||||
"QtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2F" +
|
||||
"yZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXIt" +
|
||||
"cmMAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAgDQlmcNCvFBlw0" +
|
||||
"At9lgbss8BbUxgQa9VbmeN7s6UwYyIAAABTAAAAC3NzaC1lZDI1NTE5AA" +
|
||||
"AAQMonLi0J282GmuMVyHGKS/PRoLpdj5GgmR0wrIkExRRCzKZaycLfPDL" +
|
||||
"+CGMa2jsH2QhFhTCG5AtKWVQbkqdHVAY= (null)";
|
||||
|
||||
PrivateKeyFile pk;
|
||||
|
||||
using (Stream keyStream = GetData("Key.OPENSSH.ECDSA.txt"))
|
||||
using (MemoryStream certStream = new MemoryStream(Encoding.UTF8.GetBytes(certString)))
|
||||
{
|
||||
pk = new PrivateKeyFile(keyStream, null, certStream);
|
||||
}
|
||||
|
||||
Assert.IsNotNull(pk.Certificate);
|
||||
Assert.AreEqual(0uL, pk.Certificate.ValidAfterUnixSeconds);
|
||||
Assert.AreEqual(new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero), pk.Certificate.ValidBefore);
|
||||
|
||||
byte[] data = Encoding.UTF8.GetBytes("hello world");
|
||||
|
||||
CertificateHostAlgorithm certificateAlgorithm = new(
|
||||
"ecdsa-sha2-nistp256-cert-v01@openssh.com",
|
||||
pk.Certificate,
|
||||
DefaultKeyAlgs);
|
||||
|
||||
KeyHostAlgorithm keyHostAlgorithm = new KeyHostAlgorithm("ecdsa-sha2-nistp256", pk.Key);
|
||||
|
||||
byte[] signature = keyHostAlgorithm.Sign(data);
|
||||
|
||||
Assert.IsTrue(keyHostAlgorithm.VerifySignature(data, signature));
|
||||
Assert.IsFalse(certificateAlgorithm.VerifySignature(data, signature));
|
||||
}
|
||||
|
||||
private static (RsaKey Key, Certificate Certificate) GetRsaKey()
|
||||
{
|
||||
using (Stream keyStream = GetData("Key.OPENSSH.RSA.txt"))
|
||||
using (Stream certStream = GetData("Key.OPENSSH.RSA-cert.pub"))
|
||||
{
|
||||
var pkFile = new PrivateKeyFile(keyStream, null, certStream);
|
||||
return (Key: (RsaKey)pkFile.Key, pkFile.Certificate);
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, Func<byte[], KeyHostAlgorithm>> DefaultKeyAlgs
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Dictionary<string, Func<byte[], KeyHostAlgorithm>>(
|
||||
new PasswordConnectionInfo("x", "y", "z").HostKeyAlgorithms);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
|
||||
"version": "2024.1.1-prerelease.{height}",
|
||||
"version": "2024.2.0-prerelease.{height}",
|
||||
"publicReleaseRefSpec": [
|
||||
"^refs/heads/develop$",
|
||||
"^refs/tags/\\d{4}\\.\\d+\\.\\d+"
|
||||
|
||||
Reference in New Issue
Block a user