From dd41ac28839390cc3a061ad06457a661487f64ed Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 23 May 2022 19:30:07 -0500 Subject: [PATCH] add support for reading sk keys This adds partial support for "sk" keys that are associated with a hardware key. We can only read keys but can't use them for signing. Reference: https://api.libssh.org/rfc/PROTOCOL.u2f Issue: https://github.com/dlech/KeeAgent/issues/300 --- SshAgentLib/Agent.cs | 6 +- SshAgentLib/AgentClient.cs | 13 ++- SshAgentLib/BlobParser.cs | 69 ++++++++++++-- SshAgentLib/IAgent.cs | 3 +- SshAgentLib/ISshKey.cs | 11 +++ SshAgentLib/Keys/OpensshPrivateKey.cs | 7 +- SshAgentLib/Keys/SshPublicKey.cs | 38 +++++++- SshAgentLib/PublicKeyAlgorithm.cs | 91 ++++++++++++++++++- SshAgentLib/SshKey.cs | 69 ++++++++++++-- .../Keys/OpensshPublicKeyTests.cs | 61 +++++++++---- 10 files changed, 324 insertions(+), 44 deletions(-) diff --git a/SshAgentLib/Agent.cs b/SshAgentLib/Agent.cs index 1f013d7f..9e51b40e 100644 --- a/SshAgentLib/Agent.cs +++ b/SshAgentLib/Agent.cs @@ -792,7 +792,8 @@ public void AnswerMessage(Stream messageStream, Process process = null) { var publicKeyParams = messageParser.ReadSsh2PublicKeyData( out var nonce, - out var cert + out var cert, + out var application ); var privateKeyParams = messageParser.ReadSsh2KeyData(publicKeyParams); var key = new SshKey( @@ -801,7 +802,8 @@ out var cert privateKeyParams, "", nonce, - cert + cert, + application ) { Comment = messageParser.ReadString(), diff --git a/SshAgentLib/AgentClient.cs b/SshAgentLib/AgentClient.cs index 7f8a49fc..37ec70ad 100644 --- a/SshAgentLib/AgentClient.cs +++ b/SshAgentLib/AgentClient.cs @@ -229,11 +229,20 @@ public ICollection ListKeys(SshVersion aVersion) var publicKeyParser = new BlobParser(publicKeyBlob); var publicKeyParams = publicKeyParser.ReadSsh2PublicKeyData( out var nonce, - out var cert + out var cert, + out var application ); var comment = replyParser.ReadString(); keyCollection.Add( - new SshKey(SshVersion.SSH2, publicKeyParams, null, comment, nonce, cert) + new SshKey( + SshVersion.SSH2, + publicKeyParams, + null, + comment, + nonce, + cert, + application + ) ); } break; diff --git a/SshAgentLib/BlobParser.cs b/SshAgentLib/BlobParser.cs index 155a43af..6171ada6 100644 --- a/SshAgentLib/BlobParser.cs +++ b/SshAgentLib/BlobParser.cs @@ -159,11 +159,13 @@ OpensshCertificateInfo ReadCertificate() /// AsymmetricKeyParameter containing the public key public AsymmetricKeyParameter ReadSsh2PublicKeyData( out byte[] nonce, - out OpensshCertificateInfo cert + out OpensshCertificateInfo cert, + out string application ) { nonce = null; cert = null; + application = null; var algorithm = KeyFormatIdentifier.Parse(ReadString()); switch (algorithm) @@ -189,7 +191,11 @@ out OpensshCertificateInfo cert // we are being called from SSH2_AGENTC_ADD_IDENTITY and this blob // is the whole certificate, not the nonce var certParser = new BlobParser(nonceOrPublicKey); - return certParser.ReadSsh2PublicKeyData(out nonce, out cert); + return certParser.ReadSsh2PublicKeyData( + out nonce, + out cert, + out application + ); } else { @@ -222,7 +228,11 @@ out OpensshCertificateInfo cert // we are being called from SSH2_AGENTC_ADD_IDENTITY and this blob // is the whole certificate, not the nonce var certParser = new BlobParser(nonceOrPublicKey); - return certParser.ReadSsh2PublicKeyData(out nonce, out cert); + return certParser.ReadSsh2PublicKeyData( + out nonce, + out cert, + out application + ); } else { @@ -242,10 +252,18 @@ out OpensshCertificateInfo cert case PublicKeyAlgorithm.EcdsaSha2Nistp256: case PublicKeyAlgorithm.EcdsaSha2Nistp384: case PublicKeyAlgorithm.EcdsaSha2Nistp521: + case PublicKeyAlgorithm.SkEcdsaSha2Nistp256: + case PublicKeyAlgorithm.SkEcdsaSha2Nistp384: + case PublicKeyAlgorithm.SkEcdsaSha2Nistp521: { var curveName = ReadString(); var publicKey = ReadBlob(); + if (algorithm.HasApplication()) + { + application = ReadString(); + } + var x9Params = NistNamedCurves.GetByName(curveName.Replace("nistp", "P-")); var domainParams = new ECDomainParameters( x9Params.Curve, @@ -260,20 +278,33 @@ out OpensshCertificateInfo cert case PublicKeyAlgorithm.EcdsaSha2Nistp256CertV1: case PublicKeyAlgorithm.EcdsaSha2Nistp384CertV1: case PublicKeyAlgorithm.EcdsaSha2Nistp521CertV1: + case PublicKeyAlgorithm.SkEcdsaSha2Nistp256CertV1: + case PublicKeyAlgorithm.SkEcdsaSha2Nistp384CertV1: + case PublicKeyAlgorithm.SkEcdsaSha2Nistp521CertV1: { var nonceOrPublicKey = ReadBlob(); + if (nonceOrPublicKey.Length != 32) { // we are being called from SSH2_AGENTC_ADD_IDENTITY and this blob // is the whole certificate, not the nonce var certParser = new BlobParser(nonceOrPublicKey); - return certParser.ReadSsh2PublicKeyData(out nonce, out cert); + return certParser.ReadSsh2PublicKeyData( + out nonce, + out cert, + out application + ); } else { var curveName = ReadString(); var publicKey = ReadBlob(); + if (algorithm.HasApplication()) + { + application = ReadString(); + } + nonce = nonceOrPublicKey; cert = ReadCertificate(); @@ -291,12 +322,20 @@ out OpensshCertificateInfo cert } case PublicKeyAlgorithm.SshEd25519: + case PublicKeyAlgorithm.SkSshEd25519: { var publicKey = ReadBlob(); + + if (algorithm.HasApplication()) + { + application = ReadString(); + } + return new Ed25519PublicKeyParameters(publicKey); } case PublicKeyAlgorithm.SshEd25519CertV1: + case PublicKeyAlgorithm.SkSshEd25519CertV1: { var nonceOrPublicKey = ReadBlob(); if (nonceOrPublicKey.Length != 32) @@ -304,7 +343,7 @@ out OpensshCertificateInfo cert // we are being called from SSH2_AGENTC_ADD_IDENTITY and this blob // is the whole certificate, not the nonce var certParser = new BlobParser(nonceOrPublicKey); - certParser.ReadSsh2PublicKeyData(out nonce, out cert); + certParser.ReadSsh2PublicKeyData(out nonce, out cert, out application); var publicKey = ReadBlob(); return new Ed25519PublicKeyParameters(publicKey); @@ -313,6 +352,11 @@ out OpensshCertificateInfo cert { var publicKey = ReadBlob(); + if (algorithm.HasApplication()) + { + application = ReadString(); + } + nonce = nonceOrPublicKey; cert = ReadCertificate(); @@ -321,12 +365,20 @@ out OpensshCertificateInfo cert } case PublicKeyAlgorithm.SshEd448: + case PublicKeyAlgorithm.SkSshEd448: { var publicKey = ReadBlob(); + + if (algorithm.HasApplication()) + { + application = ReadString(); + } + return new Ed448PublicKeyParameters(publicKey); } case PublicKeyAlgorithm.SshEd448CertV1: + case PublicKeyAlgorithm.SkSshEd448CertV1: { var nonceOrPublicKey = ReadBlob(); if (nonceOrPublicKey.Length != 32) @@ -334,7 +386,7 @@ out OpensshCertificateInfo cert // we are being called from SSH2_AGENTC_ADD_IDENTITY and this blob // is the whole certificate, not the nonce var certParser = new BlobParser(nonceOrPublicKey); - certParser.ReadSsh2PublicKeyData(out nonce, out cert); + certParser.ReadSsh2PublicKeyData(out nonce, out cert, out application); var publicKey = ReadBlob(); return new Ed448PublicKeyParameters(publicKey); @@ -343,6 +395,11 @@ out OpensshCertificateInfo cert { var publicKey = ReadBlob(); + if (algorithm.HasApplication()) + { + application = ReadString(); + } + nonce = nonceOrPublicKey; cert = ReadCertificate(); diff --git a/SshAgentLib/IAgent.cs b/SshAgentLib/IAgent.cs index 45872711..45bb41a8 100644 --- a/SshAgentLib/IAgent.cs +++ b/SshAgentLib/IAgent.cs @@ -111,7 +111,8 @@ public static ISshKey AddKeyFromFile( privateKey.Decrypt(getPassPhraseCallback, progress), privateKey.PublicKey.Comment, publicKey.Nonce, - publicKey.Certificate + publicKey.Certificate, + publicKey.Application ); if (constraints != null) diff --git a/SshAgentLib/ISshKey.cs b/SshAgentLib/ISshKey.cs index dc122da5..3c4cb522 100644 --- a/SshAgentLib/ISshKey.cs +++ b/SshAgentLib/ISshKey.cs @@ -62,6 +62,12 @@ public interface ISshKey : IDisposable /// OpensshCertificateInfo Certificate { get; } + /// + /// Gets the application for keys associated with a hardware key or + /// null for keys that are not associated with a hardware key. + /// + string Application { get; } + /// /// returns true if key does not have private key parameters /// @@ -217,6 +223,11 @@ public static byte[] GetPublicKeyBlob(this ISshKey key, bool cert = true) ); } + if (key.Application != null) + { + builder.AddStringBlob(key.Application); + } + if (cert && key.Certificate != null) { var principalsBuilder = new BlobBuilder(); diff --git a/SshAgentLib/Keys/OpensshPrivateKey.cs b/SshAgentLib/Keys/OpensshPrivateKey.cs index 7ed415bd..ba16fe43 100644 --- a/SshAgentLib/Keys/OpensshPrivateKey.cs +++ b/SshAgentLib/Keys/OpensshPrivateKey.cs @@ -204,11 +204,16 @@ public static SshPrivateKey Read(Stream stream) throw new FormatException("checkint does not match in private key."); } - var publicKey = privateKeyParser.ReadSsh2PublicKeyData(out var nonce, out var cert); + var publicKey = privateKeyParser.ReadSsh2PublicKeyData( + out var nonce, + out var cert, + out var application + ); var privateKey = privateKeyParser.ReadSsh2KeyData(publicKey); // var comment = privateKeyParser.ReadString(); // TODO: what to do with comment? // TODO: should we throw if nonce/cert is not null? + // TODO: do we expect application? return privateKey; }; diff --git a/SshAgentLib/Keys/SshPublicKey.cs b/SshAgentLib/Keys/SshPublicKey.cs index c6f1caf4..3bfdac10 100644 --- a/SshAgentLib/Keys/SshPublicKey.cs +++ b/SshAgentLib/Keys/SshPublicKey.cs @@ -63,7 +63,9 @@ public string AuthorizedKeysString { var builder = new StringBuilder(); - builder.Append(GetAlgorithmIdentifier(Parameter, Certificate != null)); + builder.Append( + GetAlgorithmIdentifier(Parameter, Certificate != null, Application != null) + ); builder.Append(' '); builder.Append(Convert.ToBase64String(KeyBlob)); @@ -81,6 +83,12 @@ public string AuthorizedKeysString public OpensshCertificateInfo Certificate { get; } + /// + /// Gets the application for hardware security keys or null if + /// this key is not associated with a hardware key. + /// + public string Application { get; } + /// /// Creates a new public key. /// @@ -101,9 +109,14 @@ public SshPublicKey(SshVersion version, byte[] keyBlob, string comment = "") Parameter = parser.ReadSsh1PublicKeyData(); break; case SshVersion.SSH2: - Parameter = parser.ReadSsh2PublicKeyData(out var nonce, out var certificate); + Parameter = parser.ReadSsh2PublicKeyData( + out var nonce, + out var certificate, + out var application + ); Nonce = nonce; Certificate = certificate; + Application = application; break; default: throw new ArgumentException("unsupported SSH version", nameof(version)); @@ -136,8 +149,12 @@ public SshPublicKey WithoutCertificate() // separate the key from the certificate var parser = new BlobParser(KeyBlob); - var parameters = parser.ReadSsh2PublicKeyData(out var nonce, out var certificate); - var key = new SshKey(Version, parameters); + var parameters = parser.ReadSsh2PublicKeyData( + out var nonce, + out var certificate, + out var application + ); + var key = new SshKey(Version, parameters, null, "", null, null, application); return new SshPublicKey(Version, key.GetPublicKeyBlob(), Comment); } @@ -176,7 +193,8 @@ public static SshPublicKey Read(Stream stream) private static string GetAlgorithmIdentifier( AsymmetricKeyParameter parameters, - bool hasCertificate + bool hasCertificate, + bool hasApplication ) { var algorithm = GetBaseAlgorithmIdentifier(parameters); @@ -186,6 +204,16 @@ bool hasCertificate algorithm += "-cert-v01@openssh.com"; } + if (hasApplication) + { + algorithm = "sk-" + algorithm; + + if (!hasCertificate) + { + algorithm += "@openssh.com"; + } + } + return algorithm; } diff --git a/SshAgentLib/PublicKeyAlgorithm.cs b/SshAgentLib/PublicKeyAlgorithm.cs index 5f626df3..1a2219cb 100644 --- a/SshAgentLib/PublicKeyAlgorithm.cs +++ b/SshAgentLib/PublicKeyAlgorithm.cs @@ -25,6 +25,7 @@ using System; using System.Reflection; +using System.Text.RegularExpressions; using SshAgentLib; namespace dlech.SshAgentLib @@ -64,17 +65,47 @@ public enum PublicKeyAlgorithm [KeyFormatIdentifier("ecdsa-sha2-nistp521-cert-v01@openssh.com")] EcdsaSha2Nistp521CertV1, + [KeyFormatIdentifier("sk-ecdsa-sha2-nistp256@openssh.com")] + SkEcdsaSha2Nistp256, + + [KeyFormatIdentifier("sk-ecdsa-sha2-nistp256-cert-v01@openssh.com")] + SkEcdsaSha2Nistp256CertV1, + + [KeyFormatIdentifier("sk-ecdsa-sha2-nistp384@openssh.com")] + SkEcdsaSha2Nistp384, + + [KeyFormatIdentifier("sk-ecdsa-sha2-nistp384-cert-v01@openssh.com")] + SkEcdsaSha2Nistp384CertV1, + + [KeyFormatIdentifier("sk-ecdsa-sha2-nistp521@openssh.com")] + SkEcdsaSha2Nistp521, + + [KeyFormatIdentifier("sk-ecdsa-sha2-nistp521-cert-v01@openssh.com")] + SkEcdsaSha2Nistp521CertV1, + [KeyFormatIdentifier("ssh-ed25519")] SshEd25519, [KeyFormatIdentifier("ssh-ed25519-cert-v01@openssh.com")] SshEd25519CertV1, + [KeyFormatIdentifier("sk-ssh-ed25519@openssh.com")] + SkSshEd25519, + + [KeyFormatIdentifier("sk-ssh-ed25519-cert-v01@openssh.com")] + SkSshEd25519CertV1, + [KeyFormatIdentifier("ssh-ed448")] SshEd448, [KeyFormatIdentifier("ssh-ed448-cert-v01@openssh.com")] SshEd448CertV1, + + [KeyFormatIdentifier("sk-ssh-ed448@openssh.com")] + SkSshEd448, + + [KeyFormatIdentifier("sk-ssh-ed448-cert-v01@openssh.com")] + SkSshEd448CertV1, } public static class PublicKeyAlgorithmExt @@ -101,15 +132,20 @@ public static string GetCurveDomainIdentifier(this PublicKeyAlgorithm algo) { var id = algo.GetIdentifier(); - if (!id.StartsWith("ecdsa-sha2-", StringComparison.Ordinal)) + var match = Regex.Match(id, @"(nistp\d+)"); + + if (!match.Success) { throw new ArgumentException("requires elliptic curve algorithm"); } - return id.Replace("ecdsa-sha2-", string.Empty) - .Replace("-cert-v01@openssh.com", string.Empty); + return match.Captures[0].Value; } + /// + /// Tests if a public key algorithm has nonce/certificate info in the + /// Openssh public key file format. + /// public static bool HasCert(this PublicKeyAlgorithm algorithm) { switch (algorithm) @@ -119,20 +155,69 @@ public static bool HasCert(this PublicKeyAlgorithm algorithm) case PublicKeyAlgorithm.EcdsaSha2Nistp256: case PublicKeyAlgorithm.EcdsaSha2Nistp384: case PublicKeyAlgorithm.EcdsaSha2Nistp521: + case PublicKeyAlgorithm.SkEcdsaSha2Nistp256: + case PublicKeyAlgorithm.SkEcdsaSha2Nistp384: + case PublicKeyAlgorithm.SkEcdsaSha2Nistp521: case PublicKeyAlgorithm.SshEd25519: case PublicKeyAlgorithm.SshEd448: + case PublicKeyAlgorithm.SkSshEd25519: + case PublicKeyAlgorithm.SkSshEd448: return false; case PublicKeyAlgorithm.SshRsaCertV1: case PublicKeyAlgorithm.SshDssCertV1: case PublicKeyAlgorithm.EcdsaSha2Nistp256CertV1: case PublicKeyAlgorithm.EcdsaSha2Nistp384CertV1: case PublicKeyAlgorithm.EcdsaSha2Nistp521CertV1: + case PublicKeyAlgorithm.SkEcdsaSha2Nistp256CertV1: + case PublicKeyAlgorithm.SkEcdsaSha2Nistp384CertV1: + case PublicKeyAlgorithm.SkEcdsaSha2Nistp521CertV1: case PublicKeyAlgorithm.SshEd25519CertV1: case PublicKeyAlgorithm.SshEd448CertV1: + case PublicKeyAlgorithm.SkSshEd25519CertV1: + case PublicKeyAlgorithm.SkSshEd448CertV1: return true; default: throw new NotSupportedException("unsupported algorithm"); } } + + /// + /// Tests if a public key algorithm has an application field int he + // public key file format. + /// + public static bool HasApplication(this PublicKeyAlgorithm algorithm) + { + switch (algorithm) + { + case PublicKeyAlgorithm.SkEcdsaSha2Nistp256: + case PublicKeyAlgorithm.SkEcdsaSha2Nistp256CertV1: + case PublicKeyAlgorithm.SkEcdsaSha2Nistp384: + case PublicKeyAlgorithm.SkEcdsaSha2Nistp384CertV1: + case PublicKeyAlgorithm.SkEcdsaSha2Nistp521: + case PublicKeyAlgorithm.SkEcdsaSha2Nistp521CertV1: + case PublicKeyAlgorithm.SkSshEd25519: + case PublicKeyAlgorithm.SkSshEd25519CertV1: + case PublicKeyAlgorithm.SkSshEd448: + case PublicKeyAlgorithm.SkSshEd448CertV1: + return true; + case PublicKeyAlgorithm.SshRsa: + case PublicKeyAlgorithm.SshDss: + case PublicKeyAlgorithm.EcdsaSha2Nistp256: + case PublicKeyAlgorithm.EcdsaSha2Nistp384: + case PublicKeyAlgorithm.EcdsaSha2Nistp521: + case PublicKeyAlgorithm.SshEd25519: + case PublicKeyAlgorithm.SshEd448: + case PublicKeyAlgorithm.SshRsaCertV1: + case PublicKeyAlgorithm.SshDssCertV1: + case PublicKeyAlgorithm.EcdsaSha2Nistp256CertV1: + case PublicKeyAlgorithm.EcdsaSha2Nistp384CertV1: + case PublicKeyAlgorithm.EcdsaSha2Nistp521CertV1: + case PublicKeyAlgorithm.SshEd25519CertV1: + case PublicKeyAlgorithm.SshEd448CertV1: + return false; + default: + throw new NotSupportedException("unsupported algorithm"); + } + } } } diff --git a/SshAgentLib/SshKey.cs b/SshAgentLib/SshKey.cs index 639119ab..3fce632f 100644 --- a/SshAgentLib/SshKey.cs +++ b/SshAgentLib/SshKey.cs @@ -49,7 +49,8 @@ public SshKey( AsymmetricKeyParameter privateKeyParameter = null, string comment = "", byte[] nonce = null, - OpensshCertificateInfo certificate = null + OpensshCertificateInfo certificate = null, + string application = null ) { IsPublicOnly = privateKeyParameter == null; @@ -61,6 +62,7 @@ public SshKey( Comment = comment ?? throw new ArgumentNullException(nameof(comment)); Nonce = nonce; Certificate = certificate; + Application = application; if ((nonce == null && certificate != null) || (nonce != null && certificate == null)) { @@ -77,9 +79,18 @@ public SshKey( AsymmetricCipherKeyPair cipherKeyPair, string comment = "", byte[] nonce = null, - OpensshCertificateInfo certificate = null - ) : this(version, cipherKeyPair.Public, cipherKeyPair.Private, comment, nonce, certificate) - { } + OpensshCertificateInfo certificate = null, + string application = null + ) + : this( + version, + cipherKeyPair.Public, + cipherKeyPair.Private, + comment, + nonce, + certificate, + application + ) { } public SshVersion Version { get; private set; } @@ -110,20 +121,53 @@ public PublicKeyAlgorithm Algorithm case 256: if (Certificate != null) { + if (Application != null) + { + return PublicKeyAlgorithm.SkEcdsaSha2Nistp256CertV1; + } + return PublicKeyAlgorithm.EcdsaSha2Nistp256CertV1; } + + if (Application != null) + { + return PublicKeyAlgorithm.SkEcdsaSha2Nistp256; + } + return PublicKeyAlgorithm.EcdsaSha2Nistp256; case 384: if (Certificate != null) { + if (Application != null) + { + return PublicKeyAlgorithm.SkEcdsaSha2Nistp384CertV1; + } + return PublicKeyAlgorithm.EcdsaSha2Nistp384CertV1; } + + if (Application != null) + { + return PublicKeyAlgorithm.SkEcdsaSha2Nistp384; + } + return PublicKeyAlgorithm.EcdsaSha2Nistp384; case 521: if (Certificate != null) { + if (Application != null) + { + return PublicKeyAlgorithm.SkEcdsaSha2Nistp521CertV1; + } + return PublicKeyAlgorithm.EcdsaSha2Nistp521CertV1; } + + if (Application != null) + { + return PublicKeyAlgorithm.SkEcdsaSha2Nistp521; + } + return PublicKeyAlgorithm.EcdsaSha2Nistp521; } } @@ -131,8 +175,19 @@ public PublicKeyAlgorithm Algorithm { if (Certificate != null) { + if (Application != null) + { + return PublicKeyAlgorithm.SkSshEd25519CertV1; + } + return PublicKeyAlgorithm.SshEd25519CertV1; } + + if (Application != null) + { + return PublicKeyAlgorithm.SkSshEd25519; + } + return PublicKeyAlgorithm.SshEd25519; } throw new Exception("Unknown algorithm"); @@ -141,9 +196,11 @@ public PublicKeyAlgorithm Algorithm public byte[] Nonce { get; } - public OpensshCertificateInfo Certificate { get; private set; } + public OpensshCertificateInfo Certificate { get; } + + public string Application { get; } - public bool IsPublicOnly { get; private set; } + public bool IsPublicOnly { get; } public int Size { diff --git a/SshAgentLibTests/Keys/OpensshPublicKeyTests.cs b/SshAgentLibTests/Keys/OpensshPublicKeyTests.cs index d41ae218..8477f3bc 100644 --- a/SshAgentLibTests/Keys/OpensshPublicKeyTests.cs +++ b/SshAgentLibTests/Keys/OpensshPublicKeyTests.cs @@ -2,7 +2,6 @@ // Copyright (c) 2015,2022 David Lechner // Run tests on keys from OpenSSH source code tests. -// Expected hashes come from `ssh-keygen -l -f `. using dlech.SshAgentLib; using NUnit.Framework; @@ -25,7 +24,7 @@ public class OpensshPublicKeyTests public void TestThatReadingRsaPublicKeyWorks(string keyName, bool withCert) { var fileBase = $"{keyName}{(withCert ? "-cert" : "")}"; - var fileNumber = keyName.Substring(keyName.Length - 1, 1); + var commentNumber = keyName.Substring(keyName.Length - 1, 1); using (var file = OpenResourceFile("OpenSshTestData", $"{fileBase}.pub")) { @@ -44,7 +43,7 @@ public void TestThatReadingRsaPublicKeyWorks(string keyName, bool withCert) key.Sha256Fingerprint, Is.EqualTo(ReadStringResourceFile("OpenSshTestData", $"{fileBase}.fp")) ); - Assert.That(key.Comment, Is.EqualTo($"RSA test key #{fileNumber}")); + Assert.That(key.Comment, Is.EqualTo($"RSA test key #{commentNumber}")); Assert.That( key.AuthorizedKeysString, Is.EqualTo(ReadStringResourceFile("OpenSshTestData", $"{fileBase}.pub")) @@ -69,7 +68,7 @@ public void TestThatReadingRsaPublicKeyWorks(string keyName, bool withCert) public void TestThatReadingDsaPublicKeyWorks(string keyName, bool withCert) { var fileBase = $"{keyName}{(withCert ? "-cert" : "")}"; - var fileNumber = keyName.Substring(keyName.Length - 1, 1); + var commentNumber = keyName.Substring(keyName.Length - 1, 1); using (var file = OpenResourceFile("OpenSshTestData", $"{fileBase}.pub")) { @@ -92,7 +91,7 @@ public void TestThatReadingDsaPublicKeyWorks(string keyName, bool withCert) key.Sha256Fingerprint, Is.EqualTo(ReadStringResourceFile("OpenSshTestData", $"{fileBase}.fp")) ); - Assert.That(key.Comment, Is.EqualTo($"DSA test key #{fileNumber}")); + Assert.That(key.Comment, Is.EqualTo($"DSA test key #{commentNumber}")); Assert.That( key.AuthorizedKeysString, Is.EqualTo(ReadStringResourceFile("OpenSshTestData", $"{fileBase}.pub")) @@ -114,10 +113,14 @@ public void TestThatReadingDsaPublicKeyWorks(string keyName, bool withCert) [TestCase("ecdsa_1", false)] [TestCase("ecdsa_1", true)] [TestCase("ecdsa_2", false)] + [TestCase("ecdsa_sk1", false)] + [TestCase("ecdsa_sk1", true)] + [TestCase("ecdsa_sk2", false)] public void TestThatReadingEcdsaPublicKeyWorks(string keyName, bool withCert) { var fileBase = $"{keyName}{(withCert ? "-cert" : "")}"; - var fileNumber = keyName.Substring(keyName.Length - 1, 1); + var commentSuffix = keyName.Contains("_sk") ? "-SK" : ""; + var commentNumber = keyName.Substring(keyName.Length - 1, 1); using (var file = OpenResourceFile("OpenSshTestData", $"{fileBase}.pub")) { @@ -126,23 +129,31 @@ public void TestThatReadingEcdsaPublicKeyWorks(string keyName, bool withCert) Assert.That(key.Version, Is.EqualTo(SshVersion.SSH2)); Assert.That(key.Parameter, Is.TypeOf()); - var ec = (ECPublicKeyParameters)key.Parameter; - var curve = ReadStringResourceFile("OpenSshTestData", $"{keyName}.param.curve"); - var pub = ReadStringResourceFile("OpenSshTestData", $"{keyName}.param.pub"); + if (!keyName.Contains("_sk")) + { + var ec = (ECPublicKeyParameters)key.Parameter; + var curve = ReadStringResourceFile("OpenSshTestData", $"{keyName}.param.curve"); + var pub = ReadStringResourceFile("OpenSshTestData", $"{keyName}.param.pub"); - Assert.That( - ec.Parameters.Curve, - Is.EqualTo( - (X962NamedCurves.GetByName(curve) ?? SecNamedCurves.GetByName(curve)).Curve - ) - ); - Assert.That(ec.Q, Is.EqualTo(ec.Parameters.Curve.DecodePoint(Hex.Decode(pub)))); + Assert.That( + ec.Parameters.Curve, + Is.EqualTo( + ( + X962NamedCurves.GetByName(curve) ?? SecNamedCurves.GetByName(curve) + ).Curve + ) + ); + Assert.That(ec.Q, Is.EqualTo(ec.Parameters.Curve.DecodePoint(Hex.Decode(pub)))); + } Assert.That( key.Sha256Fingerprint, Is.EqualTo(ReadStringResourceFile("OpenSshTestData", $"{fileBase}.fp")) ); - Assert.That(key.Comment, Is.EqualTo($"ECDSA test key #{fileNumber}")); + Assert.That( + key.Comment, + Is.EqualTo($"ECDSA{commentSuffix} test key #{commentNumber}") + ); Assert.That( key.AuthorizedKeysString, Is.EqualTo(ReadStringResourceFile("OpenSshTestData", $"{fileBase}.pub")) @@ -164,9 +175,20 @@ public void TestThatReadingEcdsaPublicKeyWorks(string keyName, bool withCert) [TestCase("ed25519_1", false)] [TestCase("ed25519_1", true)] [TestCase("ed25519_2", false)] + [TestCase("ed25519_sk1", false)] + [TestCase("ed25519_sk1", true)] + [TestCase("ed25519_sk2", false)] public void TestThatReadingEd25519PublicKeyWorks(string keyName, bool withCert) { var fileBase = $"{keyName}{(withCert ? "-cert" : "")}"; + var commentSuffix = keyName.Contains("_sk") ? "-SK" : ""; + var commentNumber = keyName.Substring(keyName.Length - 1, 1); + + // HACK: upstream file has "wrong" comment + if (keyName == "ed25519_2") + { + commentNumber = "1"; + } using (var file = OpenResourceFile("OpenSshTestData", $"{fileBase}.pub")) { @@ -179,7 +201,10 @@ public void TestThatReadingEd25519PublicKeyWorks(string keyName, bool withCert) key.Sha256Fingerprint, Is.EqualTo(ReadStringResourceFile("OpenSshTestData", $"{fileBase}.fp")) ); - Assert.That(key.Comment, Is.EqualTo("ED25519 test key #1")); + Assert.That( + key.Comment, + Is.EqualTo($"ED25519{commentSuffix} test key #{commentNumber}") + ); Assert.That( key.AuthorizedKeysString, Is.EqualTo(ReadStringResourceFile("OpenSshTestData", $"{fileBase}.pub"))