Skip to content

Commit fa44a94

Browse files
committed
Make keys immutable
This makes it easier to reason about Key instances in e.g. DigitalSignature implementations, because we know that the Key is initialised with its data and will not change.
1 parent 6d9d032 commit fa44a94

File tree

15 files changed

+295
-516
lines changed

15 files changed

+295
-516
lines changed

src/Renci.SshNet/Common/SshData.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ protected ulong ReadUInt64()
243243
/// <returns>
244244
/// The <see cref="string"/> that was read.
245245
/// </returns>
246-
protected string ReadString(Encoding encoding)
246+
protected string ReadString(Encoding encoding = null)
247247
{
248248
return _stream.ReadString(encoding);
249249
}

src/Renci.SshNet/Common/SshDataStream.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,12 +207,14 @@ public ulong ReadUInt64()
207207
/// <summary>
208208
/// Reads the next <see cref="string"/> data type from the SSH data stream.
209209
/// </summary>
210-
/// <param name="encoding">The character encoding to use.</param>
210+
/// <param name="encoding">The character encoding to use. Defaults to <see cref="Encoding.UTF8"/>.</param>
211211
/// <returns>
212212
/// The <see cref="string"/> read from the SSH data stream.
213213
/// </returns>
214-
public string ReadString(Encoding encoding)
214+
public string ReadString(Encoding encoding = null)
215215
{
216+
encoding ??= Encoding.UTF8;
217+
216218
var length = ReadUInt32();
217219

218220
if (length > int.MaxValue)

src/Renci.SshNet/ConnectionInfo.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -394,16 +394,16 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy
394394

395395
HostKeyAlgorithms = new Dictionary<string, Func<byte[], KeyHostAlgorithm>>
396396
{
397-
{ "ssh-ed25519", data => new KeyHostAlgorithm("ssh-ed25519", new ED25519Key(), data) },
398-
{ "ecdsa-sha2-nistp256", data => new KeyHostAlgorithm("ecdsa-sha2-nistp256", new EcdsaKey(), data) },
399-
{ "ecdsa-sha2-nistp384", data => new KeyHostAlgorithm("ecdsa-sha2-nistp384", new EcdsaKey(), data) },
400-
{ "ecdsa-sha2-nistp521", data => new KeyHostAlgorithm("ecdsa-sha2-nistp521", new EcdsaKey(), data) },
397+
{ "ssh-ed25519", data => new KeyHostAlgorithm("ssh-ed25519", new ED25519Key(new SshKeyData(data))) },
398+
{ "ecdsa-sha2-nistp256", data => new KeyHostAlgorithm("ecdsa-sha2-nistp256", new EcdsaKey(new SshKeyData(data))) },
399+
{ "ecdsa-sha2-nistp384", data => new KeyHostAlgorithm("ecdsa-sha2-nistp384", new EcdsaKey(new SshKeyData(data))) },
400+
{ "ecdsa-sha2-nistp521", data => new KeyHostAlgorithm("ecdsa-sha2-nistp521", new EcdsaKey(new SshKeyData(data))) },
401401
#pragma warning disable SA1107 // Code should not contain multiple statements on one line
402-
{ "rsa-sha2-512", data => { var key = new RsaKey(); return new KeyHostAlgorithm("rsa-sha2-512", key, data, new RsaDigitalSignature(key, HashAlgorithmName.SHA512)); } },
403-
{ "rsa-sha2-256", data => { var key = new RsaKey(); return new KeyHostAlgorithm("rsa-sha2-256", key, data, new RsaDigitalSignature(key, HashAlgorithmName.SHA256)); } },
402+
{ "rsa-sha2-512", data => { var key = new RsaKey(new SshKeyData(data)); return new KeyHostAlgorithm("rsa-sha2-512", key, new RsaDigitalSignature(key, HashAlgorithmName.SHA512)); } },
403+
{ "rsa-sha2-256", data => { var key = new RsaKey(new SshKeyData(data)); return new KeyHostAlgorithm("rsa-sha2-256", key, new RsaDigitalSignature(key, HashAlgorithmName.SHA256)); } },
404404
#pragma warning restore SA1107 // Code should not contain multiple statements on one line
405-
{ "ssh-rsa", data => new KeyHostAlgorithm("ssh-rsa", new RsaKey(), data) },
406-
{ "ssh-dss", data => new KeyHostAlgorithm("ssh-dss", new DsaKey(), data) },
405+
{ "ssh-rsa", data => new KeyHostAlgorithm("ssh-rsa", new RsaKey(new SshKeyData(data))) },
406+
{ "ssh-dss", data => new KeyHostAlgorithm("ssh-dss", new DsaKey(new SshKeyData(data))) },
407407
};
408408

409409
CompressionAlgorithms = new Dictionary<string, Type>

src/Renci.SshNet/PrivateKeyFile.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -573,12 +573,14 @@ private static Key ParseOpenSshV1Key(byte[] keyFileData, string passPhrase)
573573
switch (keyType)
574574
{
575575
case "ssh-ed25519":
576-
// public key
577-
publicKey = privateKeyReader.ReadBignum2();
576+
// https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent-11#section-3.2.3
578577

579-
// private key
578+
// ENC(A)
579+
_ = privateKeyReader.ReadBignum2();
580+
581+
// k || ENC(A)
580582
unencryptedPrivateKey = privateKeyReader.ReadBignum2();
581-
parsedKey = new ED25519Key(publicKey.Reverse(), unencryptedPrivateKey);
583+
parsedKey = new ED25519Key(unencryptedPrivateKey);
582584
break;
583585
case "ecdsa-sha2-nistp256":
584586
case "ecdsa-sha2-nistp384":

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

Lines changed: 53 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
23
using Renci.SshNet.Common;
34
using Renci.SshNet.Security.Cryptography;
45

@@ -15,57 +16,27 @@ public class DsaKey : Key, IDisposable
1516
/// <summary>
1617
/// Gets the P.
1718
/// </summary>
18-
public BigInteger P
19-
{
20-
get
21-
{
22-
return _privateKey[0];
23-
}
24-
}
19+
public BigInteger P { get; }
2520

2621
/// <summary>
2722
/// Gets the Q.
2823
/// </summary>
29-
public BigInteger Q
30-
{
31-
get
32-
{
33-
return _privateKey[1];
34-
}
35-
}
24+
public BigInteger Q { get; }
3625

3726
/// <summary>
3827
/// Gets the G.
3928
/// </summary>
40-
public BigInteger G
41-
{
42-
get
43-
{
44-
return _privateKey[2];
45-
}
46-
}
29+
public BigInteger G { get; }
4730

4831
/// <summary>
4932
/// Gets public key Y.
5033
/// </summary>
51-
public BigInteger Y
52-
{
53-
get
54-
{
55-
return _privateKey[3];
56-
}
57-
}
34+
public BigInteger Y { get; }
5835

5936
/// <summary>
6037
/// Gets private key X.
6138
/// </summary>
62-
public BigInteger X
63-
{
64-
get
65-
{
66-
return _privateKey[4];
67-
}
68-
}
39+
public BigInteger X { get; }
6940

7041
/// <summary>
7142
/// Gets the length of the key.
@@ -94,46 +65,70 @@ protected internal override DigitalSignature DigitalSignature
9465
}
9566

9667
/// <summary>
97-
/// Gets or sets the public.
68+
/// Gets the DSA public key.
9869
/// </summary>
9970
/// <value>
100-
/// The public.
71+
/// An array whose values are:
72+
/// <list>
73+
/// <item><term>0</term><description><see cref="P"/></description></item>
74+
/// <item><term>1</term><description><see cref="Q"/></description></item>
75+
/// <item><term>2</term><description><see cref="G"/></description></item>
76+
/// <item><term>3</term><description><see cref="Y"/></description></item>
77+
/// </list>
10178
/// </value>
10279
public override BigInteger[] Public
10380
{
10481
get
10582
{
10683
return new[] { P, Q, G, Y };
10784
}
108-
set
109-
{
110-
if (value.Length != 4)
111-
{
112-
throw new InvalidOperationException("Invalid public key.");
113-
}
114-
115-
_privateKey = value;
116-
}
11785
}
11886

11987
/// <summary>
12088
/// Initializes a new instance of the <see cref="DsaKey"/> class.
12189
/// </summary>
122-
public DsaKey()
90+
/// <param name="publicKeyData">The encoded public key data.</param>
91+
public DsaKey(SshKeyData publicKeyData)
12392
{
124-
_privateKey = new BigInteger[5];
93+
if (publicKeyData is null)
94+
{
95+
throw new ArgumentNullException(nameof(publicKeyData));
96+
}
97+
98+
if (publicKeyData.Name != "ssh-dss" || publicKeyData.Keys.Length != 4)
99+
{
100+
throw new ArgumentException($"Invalid DSA public key data. ({publicKeyData.Name}, {publicKeyData.Keys.Length}).", nameof(publicKeyData));
101+
}
102+
103+
P = publicKeyData.Keys[0];
104+
Q = publicKeyData.Keys[1];
105+
G = publicKeyData.Keys[2];
106+
Y = publicKeyData.Keys[3];
125107
}
126108

127109
/// <summary>
128110
/// Initializes a new instance of the <see cref="DsaKey"/> class.
129111
/// </summary>
130-
/// <param name="data">DER encoded private key data.</param>
131-
public DsaKey(byte[] data)
132-
: base(data)
112+
/// <param name="privateKeyData">DER encoded private key data.</param>
113+
public DsaKey(byte[] privateKeyData)
133114
{
134-
if (_privateKey.Length != 5)
115+
if (privateKeyData is null)
116+
{
117+
throw new ArgumentNullException(nameof(privateKeyData));
118+
}
119+
120+
var der = new DerData(privateKeyData);
121+
_ = der.ReadBigInteger(); // skip version
122+
123+
P = der.ReadBigInteger();
124+
Q = der.ReadBigInteger();
125+
G = der.ReadBigInteger();
126+
Y = der.ReadBigInteger();
127+
X = der.ReadBigInteger();
128+
129+
if (!der.IsEndOfData)
135130
{
136-
throw new InvalidOperationException("Invalid private key.");
131+
throw new InvalidOperationException("Invalid private key (expected EOF).");
137132
}
138133
}
139134

@@ -147,7 +142,11 @@ public DsaKey(byte[] data)
147142
/// <param name="x">The x.</param>
148143
public DsaKey(BigInteger p, BigInteger q, BigInteger g, BigInteger y, BigInteger x)
149144
{
150-
_privateKey = new BigInteger[5] { p, q, g, y, x };
145+
P = p;
146+
Q = q;
147+
G = g;
148+
Y = y;
149+
X = x;
151150
}
152151

153152
/// <summary>

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

Lines changed: 26 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,7 @@ namespace Renci.SshNet.Security
1111
/// </summary>
1212
public class ED25519Key : Key, IDisposable
1313
{
14-
#pragma warning disable IDE1006 // Naming Styles
15-
#pragma warning disable SX1309 // Field names should begin with underscore
16-
private readonly byte[] privateKey = new byte[Ed25519.ExpandedPrivateKeySizeInBytes];
17-
#pragma warning restore SX1309 // Field names should begin with underscore
18-
#pragma warning restore IDE1006 // Naming Styles
1914
private ED25519DigitalSignature _digitalSignature;
20-
private byte[] _publicKey = new byte[Ed25519.PublicKeySizeInBytes];
2115
private bool _isDisposed;
2216

2317
/// <summary>
@@ -32,20 +26,16 @@ public override string ToString()
3226
}
3327

3428
/// <summary>
35-
/// Gets or sets the public.
29+
/// Gets the Ed25519 public key.
3630
/// </summary>
3731
/// <value>
38-
/// The public.
32+
/// An array with <see cref="PublicKey"/> encoded at index 0.
3933
/// </value>
4034
public override BigInteger[] Public
4135
{
4236
get
4337
{
44-
return new BigInteger[] { _publicKey.ToBigInteger2() };
45-
}
46-
set
47-
{
48-
_publicKey = value[0].ToByteArray().Reverse().TrimLeadingZeros().Pad(Ed25519.PublicKeySizeInBytes);
38+
return new BigInteger[] { PublicKey.ToBigInteger2() };
4939
}
5040
}
5141

@@ -78,52 +68,46 @@ protected internal override DigitalSignature DigitalSignature
7868
/// <summary>
7969
/// Gets the PublicKey Bytes.
8070
/// </summary>
81-
public byte[] PublicKey
82-
{
83-
get
84-
{
85-
return _publicKey;
86-
}
87-
}
71+
public byte[] PublicKey { get; }
8872

8973
/// <summary>
9074
/// Gets the PrivateKey Bytes.
9175
/// </summary>
92-
public byte[] PrivateKey
93-
{
94-
get
95-
{
96-
return privateKey;
97-
}
98-
}
76+
public byte[] PrivateKey { get; }
9977

10078
/// <summary>
10179
/// Initializes a new instance of the <see cref="ED25519Key"/> class.
10280
/// </summary>
103-
public ED25519Key()
81+
/// <param name="publicKeyData">The encoded public key data.</param>
82+
public ED25519Key(SshKeyData publicKeyData)
10483
{
105-
}
84+
if (publicKeyData is null)
85+
{
86+
throw new ArgumentNullException(nameof(publicKeyData));
87+
}
10688

107-
/// <summary>
108-
/// Initializes a new instance of the <see cref="ED25519Key"/> class.
109-
/// </summary>
110-
/// <param name="pk">pk data.</param>
111-
public ED25519Key(byte[] pk)
112-
{
113-
_publicKey = pk.TrimLeadingZeros().Pad(Ed25519.PublicKeySizeInBytes);
89+
if (publicKeyData.Name != "ssh-ed25519" || publicKeyData.Keys.Length != 1)
90+
{
91+
throw new ArgumentException($"Invalid Ed25519 public key data ({publicKeyData.Name}, {publicKeyData.Keys.Length}).", nameof(publicKeyData));
92+
}
93+
94+
PublicKey = publicKeyData.Keys[0].ToByteArray().Reverse().TrimLeadingZeros().Pad(Ed25519.PublicKeySizeInBytes);
95+
PrivateKey = new byte[Ed25519.ExpandedPrivateKeySizeInBytes];
11496
}
11597

11698
/// <summary>
11799
/// Initializes a new instance of the <see cref="ED25519Key"/> class.
118100
/// </summary>
119-
/// <param name="pk">pk data.</param>
120-
/// <param name="sk">sk data.</param>
121-
public ED25519Key(byte[] pk, byte[] sk)
101+
/// <param name="privateKeyData">
102+
/// The private key data <c>k || ENC(A)</c> as described in RFC 8032.
103+
/// </param>
104+
public ED25519Key(byte[] privateKeyData)
122105
{
123-
_publicKey = pk.TrimLeadingZeros().Pad(Ed25519.PublicKeySizeInBytes);
124106
var seed = new byte[Ed25519.PrivateKeySeedSizeInBytes];
125-
Buffer.BlockCopy(sk, 0, seed, 0, seed.Length);
126-
Ed25519.KeyPairFromSeed(out _publicKey, out privateKey, seed);
107+
Buffer.BlockCopy(privateKeyData, 0, seed, 0, seed.Length);
108+
Ed25519.KeyPairFromSeed(out var publicKey, out var privateKey, seed);
109+
PublicKey = publicKey;
110+
PrivateKey = privateKey;
127111
}
128112

129113
/// <summary>

0 commit comments

Comments
 (0)