Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support auth using SSH Agent keys. #281

Merged
merged 12 commits into from
Dec 20, 2024
9 changes: 1 addition & 8 deletions src/Tmds.Ssh/ChaCha20Poly1305PacketDecryptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,7 @@ public bool TryDecrypt(Sequence receiveBuffer, uint sequenceNumber, int maxLengt
ConfigureCiphers(sequenceNumber);

Span<byte> length_encrypted = stackalloc byte[LengthSize];
if (receiveBuffer.FirstSpan.Length >= LengthSize)
{
receiveBuffer.FirstSpan.Slice(0, LengthSize).CopyTo(length_encrypted);
}
else
{
receiveBuffer.AsReadOnlySequence().Slice(0, LengthSize).CopyTo(length_encrypted);
}
receiveBuffer.CopyTo(length_encrypted, LengthSize);

LengthCipher.ProcessBytes(length_encrypted, length_unencrypted);

Expand Down
4 changes: 2 additions & 2 deletions src/Tmds.Ssh/ECDsaPrivateKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ sealed class ECDsaPrivateKey : PrivateKey
private readonly Name _curveName;
private readonly HashAlgorithmName _hashAlgorithm;

public ECDsaPrivateKey(ECDsa ecdsa, Name algorithm, Name curveName, HashAlgorithmName hashAlgorithm) :
base([algorithm])
public ECDsaPrivateKey(ECDsa ecdsa, Name algorithm, Name curveName, HashAlgorithmName hashAlgorithm, byte[]? sshPublicKey) :
base([algorithm], sshPublicKey)
{
_ecdsa = ecdsa ?? throw new ArgumentNullException(nameof(ecdsa));
_algorithm = algorithm;
Expand Down
4 changes: 2 additions & 2 deletions src/Tmds.Ssh/Ed25519PrivateKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ sealed class Ed25519PrivateKey : PrivateKey
private readonly byte[] _privateKey;
private readonly byte[] _publicKey;

public Ed25519PrivateKey(byte[] privateKey, byte[] publicKey) :
base([AlgorithmNames.SshEd25519])
public Ed25519PrivateKey(byte[] privateKey, byte[] publicKey, byte[]? sshPublicKey) :
base([AlgorithmNames.SshEd25519], sshPublicKey)
{
_privateKey = privateKey;
_publicKey = publicKey;
Expand Down
4 changes: 3 additions & 1 deletion src/Tmds.Ssh/PrivateKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ namespace Tmds.Ssh;

abstract class PrivateKey : IDisposable
{
private protected PrivateKey(Name[] algorithms)
private protected PrivateKey(Name[] algorithms, byte[]? sshPublicKey)
{
Algorithms = algorithms;
PublicKey = sshPublicKey;
}

public Name[] Algorithms { get; }
public byte[]? PublicKey { get; }

public abstract void Dispose();

Expand Down
4 changes: 2 additions & 2 deletions src/Tmds.Ssh/PrivateKeyCredential.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ protected readonly struct Key

public Key(RSA rsa)
{
PrivateKey = new RsaPrivateKey(rsa);
PrivateKey = new RsaPrivateKey(rsa, sshPublicKey: null);
}

public Key(ReadOnlyMemory<char> rawKey, Func<string?>? passwordPrompt = null)
Expand Down Expand Up @@ -112,7 +112,7 @@ public Key(ECDsa ecdsa)
throw new NotSupportedException($"Curve '{oid.FriendlyName ?? oid.Value}' is not known.");
}

PrivateKey = new ECDsaPrivateKey(ecdsa, algorithm, curveName, hashAlgorithm);
PrivateKey = new ECDsaPrivateKey(ecdsa, algorithm, curveName, hashAlgorithm, sshPublicKey: null);
}

internal Key(PrivateKey key)
Expand Down
21 changes: 11 additions & 10 deletions src/Tmds.Ssh/PrivateKeyParser.OpenSsh.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ string publickeyN
{
throw new FormatException($"The data contains multiple keys.");
}
reader.SkipString(); // skip the public key
byte[] publicKey = reader.ReadStringAsBytes().ToArray();
ReadOnlySequence<byte> privateKeyList;
if (cipherName == AlgorithmNames.None)
{
Expand Down Expand Up @@ -94,15 +94,15 @@ byte padlen % 255
Name keyType = reader.ReadName();
if (keyType == AlgorithmNames.SshRsa)
{
return ParseOpenSshRsaKey(reader);
return ParseOpenSshRsaKey(publicKey, reader);
}
else if (keyType.ToString().StartsWith("ecdsa-sha2-"))
{
return ParseOpenSshEcdsaKey(keyType, reader);
return ParseOpenSshEcdsaKey(publicKey, keyType, reader);
}
else if (keyType == AlgorithmNames.SshEd25519)
{
return ParseOpenSshEd25519Key(reader);
return ParseOpenSshEd25519Key(publicKey, reader);
}
else
{
Expand Down Expand Up @@ -166,7 +166,7 @@ uint32 rounds
}
}

private static PrivateKey ParseOpenSshRsaKey(SequenceReader reader)
private static PrivateKey ParseOpenSshRsaKey(byte[] publicKey, SequenceReader reader)
{
// .NET RSA's class has some length expectations:
// D must have the same length as Modulus.
Expand Down Expand Up @@ -198,7 +198,7 @@ private static PrivateKey ParseOpenSshRsaKey(SequenceReader reader)
try
{
rsa.ImportParameters(parameters);
return new RsaPrivateKey(rsa);
return new RsaPrivateKey(rsa, publicKey);
}
catch (Exception ex)
{
Expand All @@ -207,7 +207,7 @@ private static PrivateKey ParseOpenSshRsaKey(SequenceReader reader)
}
}

private static PrivateKey ParseOpenSshEcdsaKey(Name keyIdentifier, SequenceReader reader)
private static PrivateKey ParseOpenSshEcdsaKey(byte[] publicKey, Name keyIdentifier, SequenceReader reader)
{
Name curveName = reader.ReadName();

Expand Down Expand Up @@ -247,7 +247,7 @@ private static PrivateKey ParseOpenSshEcdsaKey(Name keyIdentifier, SequenceReade
};

ecdsa.ImportParameters(parameters);
return new ECDsaPrivateKey(ecdsa, keyIdentifier, curveName, allowedHashAlgo);
return new ECDsaPrivateKey(ecdsa, keyIdentifier, curveName, allowedHashAlgo, publicKey);
}
catch (Exception ex)
{
Expand All @@ -256,7 +256,7 @@ private static PrivateKey ParseOpenSshEcdsaKey(Name keyIdentifier, SequenceReade
}
}

private static PrivateKey ParseOpenSshEd25519Key(SequenceReader reader)
private static PrivateKey ParseOpenSshEd25519Key(byte[]? sshPublicKey, SequenceReader reader)
{
// https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent-14#section-3.2.3
/*
Expand All @@ -275,7 +275,8 @@ concatenation of the private key k and the public ENC(A) key. Why it is

return new Ed25519PrivateKey(
keyData.Slice(0, keyData.Length - publicKey.Length).ToArray(),
publicKey.ToArray());
publicKey.ToArray(),
sshPublicKey);
}
catch (Exception ex)
{
Expand Down
22 changes: 22 additions & 0 deletions src/Tmds.Ssh/ROSExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// This file is part of Tmds.Ssh which is released under MIT.
// See file LICENSE for full license details.

using System.Buffers;

namespace Tmds.Ssh;

static class ROSExtensions
{
public static void CopyTo(this ReadOnlySequence<byte> src, Span<byte> dst, int length)
{
ReadOnlySpan<byte> firstSpan = src.FirstSpan;
if (firstSpan.Length >= length)
{
firstSpan.Slice(0, length).CopyTo(dst);
}
else
{
src.Slice(0, length).CopyTo(dst);
}
}
}
4 changes: 2 additions & 2 deletions src/Tmds.Ssh/RsaPrivateKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ sealed class RsaPrivateKey : PrivateKey
{
private readonly RSA _rsa;

public RsaPrivateKey(RSA rsa) :
base(AlgorithmNames.SshRsaAlgorithms)
public RsaPrivateKey(RSA rsa, byte[]? sshPublicKey) :
base(AlgorithmNames.SshRsaAlgorithms, sshPublicKey)
{
_rsa = rsa ?? throw new ArgumentNullException(nameof(rsa));
}
Expand Down
13 changes: 13 additions & 0 deletions src/Tmds.Ssh/Sequence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,19 @@ public ReadOnlySequence<byte> AsReadOnlySequence()
}
}

public void CopyTo(Span<byte> dst, int length)
{
ReadOnlySpan<byte> firstSpan = FirstSpan;
if (firstSpan.Length >= length)
{
firstSpan.Slice(0, length).CopyTo(dst);
}
else
{
AsReadOnlySequence().Slice(0, length).CopyTo(dst);
}
}

public Sequence Clone()
{
Sequence sequence = SequencePool.RentSequence();
Expand Down
Loading