Skip to content

Commit e727f4a

Browse files
darinkesdrieseng
authored andcommitted
Agent auth and Keygen (#794)
* Allow to set PrivateKeyFile Key directly So you can add your own Key-Classes to SSH.NET * Add ED25519 ctor for just pub key part. * Make ECDSA Key Bits accessible You cant export imported CngKeys. To be able to export them to agent or Key-Files make the private bits also accessible. * Better NETFRAMEWORK vs NETSTANDARD handling * Add Comment Property to Key * Add IPrivateKeySource So Extension can add own PrivateKeyFiles, e.g. PuttyKeyFile.
1 parent 8b7167e commit e727f4a

12 files changed

+149
-101
lines changed

src/Renci.SshNet/IPrivateKeySource.cs

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Renci.SshNet.Security;
2+
3+
namespace Renci.SshNet
4+
{
5+
/// <summary>
6+
/// Represents private key source interface.
7+
/// </summary>
8+
public interface IPrivateKeySource
9+
{
10+
/// <summary>
11+
/// Gets the host key.
12+
/// </summary>
13+
HostAlgorithm HostKey { get; }
14+
}
15+
}

src/Renci.SshNet/NetConfClient.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public NetConfClient(string host, string username, string password)
103103
/// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is <c>null</c> or contains only whitespace characters.</exception>
104104
/// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="IPEndPoint.MinPort"/> and <see cref="IPEndPoint.MaxPort"/>.</exception>
105105
[SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
106-
public NetConfClient(string host, int port, string username, params PrivateKeyFile[] keyFiles)
106+
public NetConfClient(string host, int port, string username, params IPrivateKeySource[] keyFiles)
107107
: this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), true)
108108
{
109109
}
@@ -116,7 +116,7 @@ public NetConfClient(string host, int port, string username, params PrivateKeyFi
116116
/// <param name="keyFiles">Authentication private key file(s) .</param>
117117
/// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is <c>null</c>.</exception>
118118
/// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is <c>null</c> or contains only whitespace characters.</exception>
119-
public NetConfClient(string host, string username, params PrivateKeyFile[] keyFiles)
119+
public NetConfClient(string host, string username, params IPrivateKeySource[] keyFiles)
120120
: this(host, ConnectionInfo.DefaultPort, username, keyFiles)
121121
{
122122
}
@@ -163,7 +163,7 @@ internal NetConfClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, I
163163
/// <value>
164164
/// The NetConf server capabilities.
165165
/// </value>
166-
public XmlDocument ServerCapabilities
166+
public XmlDocument ServerCapabilities
167167
{
168168
get { return _netConfSession.ServerCapabilities; }
169169
}
@@ -277,4 +277,4 @@ private INetConfSession CreateAndConnectNetConfSession()
277277
}
278278
}
279279
}
280-
}
280+
}

src/Renci.SshNet/PrivateKeyAuthenticationMethod.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,21 @@ public override string Name
2828
/// <summary>
2929
/// Gets the key files used for authentication.
3030
/// </summary>
31-
public ICollection<PrivateKeyFile> KeyFiles { get; private set; }
31+
public ICollection<IPrivateKeySource> KeyFiles { get; private set; }
3232

3333
/// <summary>
3434
/// Initializes a new instance of the <see cref="PrivateKeyAuthenticationMethod"/> class.
3535
/// </summary>
3636
/// <param name="username">The username.</param>
3737
/// <param name="keyFiles">The key files.</param>
3838
/// <exception cref="ArgumentException"><paramref name="username"/> is whitespace or <c>null</c>.</exception>
39-
public PrivateKeyAuthenticationMethod(string username, params PrivateKeyFile[] keyFiles)
39+
public PrivateKeyAuthenticationMethod(string username, params IPrivateKeySource[] keyFiles)
4040
: base(username)
4141
{
4242
if (keyFiles == null)
4343
throw new ArgumentNullException("keyFiles");
4444

45-
KeyFiles = new Collection<PrivateKeyFile>(keyFiles);
45+
KeyFiles = new Collection<IPrivateKeySource>(keyFiles);
4646
}
4747

4848
/// <summary>
@@ -250,4 +250,4 @@ protected override void SaveData()
250250
}
251251
}
252252
}
253-
}
253+
}

src/Renci.SshNet/PrivateKeyConnectionInfo.cs

+10-10
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class PrivateKeyConnectionInfo : ConnectionInfo, IDisposable
1515
/// <summary>
1616
/// Gets the key files used for authentication.
1717
/// </summary>
18-
public ICollection<PrivateKeyFile> KeyFiles { get; private set; }
18+
public ICollection<IPrivateKeySource> KeyFiles { get; private set; }
1919

2020
/// <summary>
2121
/// Initializes a new instance of the <see cref="PrivateKeyConnectionInfo"/> class.
@@ -40,7 +40,7 @@ public PrivateKeyConnectionInfo(string host, string username, params PrivateKeyF
4040
/// <param name="port">Connection port.</param>
4141
/// <param name="username">Connection username.</param>
4242
/// <param name="keyFiles">Connection key files.</param>
43-
public PrivateKeyConnectionInfo(string host, int port, string username, params PrivateKeyFile[] keyFiles)
43+
public PrivateKeyConnectionInfo(string host, int port, string username, params IPrivateKeySource[] keyFiles)
4444
: this(host, port, username, ProxyTypes.None, string.Empty, 0, string.Empty, string.Empty, keyFiles)
4545
{
4646
}
@@ -55,7 +55,7 @@ public PrivateKeyConnectionInfo(string host, int port, string username, params P
5555
/// <param name="proxyHost">The proxy host.</param>
5656
/// <param name="proxyPort">The proxy port.</param>
5757
/// <param name="keyFiles">The key files.</param>
58-
public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, params PrivateKeyFile[] keyFiles)
58+
public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, params IPrivateKeySource[] keyFiles)
5959
: this(host, port, username, proxyType, proxyHost, proxyPort, string.Empty, string.Empty, keyFiles)
6060
{
6161
}
@@ -71,7 +71,7 @@ public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTyp
7171
/// <param name="proxyPort">The proxy port.</param>
7272
/// <param name="proxyUsername">The proxy username.</param>
7373
/// <param name="keyFiles">The key files.</param>
74-
public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, params PrivateKeyFile[] keyFiles)
74+
public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, params IPrivateKeySource[] keyFiles)
7575
: this(host, port, username, proxyType, proxyHost, proxyPort, proxyUsername, string.Empty, keyFiles)
7676
{
7777
}
@@ -85,7 +85,7 @@ public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTyp
8585
/// <param name="proxyHost">The proxy host.</param>
8686
/// <param name="proxyPort">The proxy port.</param>
8787
/// <param name="keyFiles">The key files.</param>
88-
public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, params PrivateKeyFile[] keyFiles)
88+
public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, params IPrivateKeySource[] keyFiles)
8989
: this(host, DefaultPort, username, proxyType, proxyHost, proxyPort, string.Empty, string.Empty, keyFiles)
9090
{
9191
}
@@ -100,7 +100,7 @@ public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyTy
100100
/// <param name="proxyPort">The proxy port.</param>
101101
/// <param name="proxyUsername">The proxy username.</param>
102102
/// <param name="keyFiles">The key files.</param>
103-
public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, params PrivateKeyFile[] keyFiles)
103+
public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, params IPrivateKeySource[] keyFiles)
104104
: this(host, DefaultPort, username, proxyType, proxyHost, proxyPort, proxyUsername, string.Empty, keyFiles)
105105
{
106106
}
@@ -116,7 +116,7 @@ public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyTy
116116
/// <param name="proxyUsername">The proxy username.</param>
117117
/// <param name="proxyPassword">The proxy password.</param>
118118
/// <param name="keyFiles">The key files.</param>
119-
public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params PrivateKeyFile[] keyFiles)
119+
public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params IPrivateKeySource[] keyFiles)
120120
: this(host, DefaultPort, username, proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword, keyFiles)
121121
{
122122
}
@@ -133,10 +133,10 @@ public PrivateKeyConnectionInfo(string host, string username, ProxyTypes proxyTy
133133
/// <param name="proxyUsername">The proxy username.</param>
134134
/// <param name="proxyPassword">The proxy password.</param>
135135
/// <param name="keyFiles">The key files.</param>
136-
public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params PrivateKeyFile[] keyFiles)
136+
public PrivateKeyConnectionInfo(string host, int port, string username, ProxyTypes proxyType, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword, params IPrivateKeySource[] keyFiles)
137137
: base(host, port, username, proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword, new PrivateKeyAuthenticationMethod(username, keyFiles))
138138
{
139-
KeyFiles = new Collection<PrivateKeyFile>(keyFiles);
139+
KeyFiles = new Collection<IPrivateKeySource>(keyFiles);
140140
}
141141

142142
#region IDisposable Members
@@ -194,4 +194,4 @@ protected virtual void Dispose(bool disposing)
194194

195195
#endregion
196196
}
197-
}
197+
}

src/Renci.SshNet/PrivateKeyFile.cs

+13-5
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ namespace Renci.SshNet
6363
/// </list>
6464
/// </para>
6565
/// </remarks>
66-
public class PrivateKeyFile : IDisposable
66+
public class PrivateKeyFile : IPrivateKeySource, IDisposable
6767
{
6868
private static readonly Regex PrivateKeyRegex = new Regex(@"^-+ *BEGIN (?<keyName>\w+( \w+)*) PRIVATE KEY *-+\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)+)-+ *END \k<keyName> PRIVATE KEY *-+",
6969
#if FEATURE_REGEX_COMPILE
@@ -79,6 +79,15 @@ public class PrivateKeyFile : IDisposable
7979
/// </summary>
8080
public HostAlgorithm HostKey { get; private set; }
8181

82+
/// <summary>
83+
/// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.
84+
/// </summary>
85+
/// <param name="key">The key.</param>
86+
public PrivateKeyFile(Key key)
87+
{
88+
HostKey = new KeyHostAlgorithm(key.ToString(), key);
89+
}
90+
8291
/// <summary>
8392
/// Initializes a new instance of the <see cref="PrivateKeyFile"/> class.
8493
/// </summary>
@@ -262,7 +271,7 @@ private void Open(Stream privateKey, string passPhrase)
262271

263272
if (decryptedLength > blobSize - 4)
264273
throw new SshException("Invalid passphrase.");
265-
274+
266275
if (keyType == "if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}")
267276
{
268277
var exponent = reader.ReadBigIntWithBits();//e
@@ -515,8 +524,7 @@ private Key ParseOpenSshV1Key(byte[] keyFileData, string passPhrase)
515524
throw new SshException("OpenSSH key type '" + keyType + "' is not supported.");
516525
}
517526

518-
//comment, we don't need this but we could log it, not sure if necessary
519-
var comment = privateKeyReader.ReadString(Encoding.UTF8);
527+
parsedKey.Comment = privateKeyReader.ReadString(Encoding.UTF8);
520528

521529
//The list of privatekey/comment pairs is padded with the bytes 1, 2, 3, ...
522530
//until the total length is a multiple of the cipher block size.
@@ -642,4 +650,4 @@ protected override void SaveData()
642650
}
643651
}
644652
}
645-
}
653+
}

src/Renci.SshNet/ScpClient.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ public ScpClient(string host, string username, string password)
142142
/// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is <c>null</c> or contains only whitespace characters.</exception>
143143
/// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="IPEndPoint.MinPort"/> and <see cref="IPEndPoint.MaxPort"/>.</exception>
144144
[SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
145-
public ScpClient(string host, int port, string username, params PrivateKeyFile[] keyFiles)
145+
public ScpClient(string host, int port, string username, params IPrivateKeySource[] keyFiles)
146146
: this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), true)
147147
{
148148
}
@@ -155,7 +155,7 @@ public ScpClient(string host, int port, string username, params PrivateKeyFile[]
155155
/// <param name="keyFiles">Authentication private key file(s) .</param>
156156
/// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is <c>null</c>.</exception>
157157
/// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is <c>null</c> or contains only whitespace characters.</exception>
158-
public ScpClient(string host, string username, params PrivateKeyFile[] keyFiles)
158+
public ScpClient(string host, string username, params IPrivateKeySource[] keyFiles)
159159
: this(host, ConnectionInfo.DefaultPort, username, keyFiles)
160160
{
161161
}
@@ -466,4 +466,4 @@ private static SshException SecureExecutionRequestRejectedException()
466466
throw new SshException("Secure copy execution request was rejected by the server. Please consult the server logs.");
467467
}
468468
}
469-
}
469+
}

src/Renci.SshNet/Security/Cryptography/ED25519Key.cs

+9
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ public ED25519Key()
9999
{
100100
}
101101

102+
/// <summary>
103+
/// Initializes a new instance of the <see cref="ED25519Key"/> class.
104+
/// </summary>
105+
/// <param name="pk">pk data.</param>
106+
public ED25519Key(byte[] pk)
107+
{
108+
publicKey = pk.TrimLeadingZeros().Pad(Ed25519.PublicKeySizeInBytes);
109+
}
110+
102111
/// <summary>
103112
/// Initializes a new instance of the <see cref="ED25519Key"/> class.
104113
/// </summary>

src/Renci.SshNet/Security/Cryptography/EcdsaDigitalSignature.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ public override bool Verify(byte[] input, byte[] signature)
3939
// for 521 sig_size is 132
4040
var sig_size = _key.KeyLength == 521 ? 132 : _key.KeyLength / 4;
4141
var ssh_data = new SshDataSignature(signature, sig_size);
42-
#if NETSTANDARD2_0
43-
return _key.Ecdsa.VerifyData(input, ssh_data.Signature, _key.HashAlgorithm);
44-
#else
42+
#if NETFRAMEWORK
4543
var ecdsa = (ECDsaCng)_key.Ecdsa;
4644
ecdsa.HashAlgorithm = _key.HashAlgorithm;
4745
return ecdsa.VerifyData(input, ssh_data.Signature);
46+
#else
47+
return _key.Ecdsa.VerifyData(input, ssh_data.Signature, _key.HashAlgorithm);
4848
#endif
4949
}
5050

@@ -57,12 +57,12 @@ public override bool Verify(byte[] input, byte[] signature)
5757
/// </returns>
5858
public override byte[] Sign(byte[] input)
5959
{
60-
#if NETSTANDARD2_0
61-
var signed = _key.Ecdsa.SignData(input, _key.HashAlgorithm);
62-
#else
60+
#if NETFRAMEWORK
6361
var ecdsa = (ECDsaCng)_key.Ecdsa;
6462
ecdsa.HashAlgorithm = _key.HashAlgorithm;
6563
var signed = ecdsa.SignData(input);
64+
#else
65+
var signed = _key.Ecdsa.SignData(input, _key.HashAlgorithm);
6666
#endif
6767
var ssh_data = new SshDataSignature(signed.Length);
6868
ssh_data.Signature = signed;

0 commit comments

Comments
 (0)