-
-
Notifications
You must be signed in to change notification settings - Fork 940
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for mlkem768x25519-sha256 key exchange method
- Loading branch information
Showing
12 changed files
with
349 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -348,6 +348,7 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy | |
|
||
KeyExchangeAlgorithms = new Dictionary<string, Func<IKeyExchange>> | ||
{ | ||
{ "mlkem768x25519-sha256", () => new KeyExchangeMLKem768x25519Sha256() }, | ||
{ "curve25519-sha256", () => new KeyExchangeECCurve25519() }, | ||
{ "[email protected]", () => new KeyExchangeECCurve25519() }, | ||
{ "ecdh-sha2-nistp256", () => new KeyExchangeECDH256() }, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
src/Renci.SshNet/Messages/Transport/KeyExchangeEcdhReplyMessage.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 85 additions & 0 deletions
85
src/Renci.SshNet/Messages/Transport/KeyExchangeHybridInitMessage.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
using System; | ||
|
||
namespace Renci.SshNet.Messages.Transport | ||
{ | ||
/// <summary> | ||
/// Represents SSH_MSG_KEX_HYBRID_INIT message. | ||
/// </summary> | ||
internal sealed class KeyExchangeHybridInitMessage : Message, IKeyExchangedAllowed | ||
{ | ||
/// <inheritdoc /> | ||
public override string MessageName | ||
{ | ||
get | ||
{ | ||
return "SSH_MSG_KEX_HYBRID_INIT"; | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override byte MessageNumber | ||
{ | ||
get | ||
{ | ||
return 30; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Gets the client init data. | ||
/// </summary> | ||
/// <remarks> | ||
/// The init data is the concatenation of C_PK2 and C_PK1 (C_INIT = C_PK2 || C_PK1, where || depicts concatenation). | ||
/// C_PK1 and C_PK2 represent the ephemeral client public keys used for each key exchange of the PQ/T Hybrid mechanism. | ||
/// Typically, C_PK1 represents a traditional / classical (i.e., ECDH) key exchange public key. | ||
/// C_PK2 represents the 'pk' output of the corresponding post-quantum KEM's 'KeyGen' at the client. | ||
/// </remarks> | ||
public byte[] CInit { get; private set; } | ||
|
||
/// <summary> | ||
/// Gets the size of the message in bytes. | ||
/// </summary> | ||
/// <value> | ||
/// The size of the messages in bytes. | ||
/// </value> | ||
protected override int BufferCapacity | ||
{ | ||
get | ||
{ | ||
var capacity = base.BufferCapacity; | ||
capacity += 4; // CInit length | ||
capacity += CInit.Length; // CInit | ||
return capacity; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="KeyExchangeHybridInitMessage"/> class. | ||
/// </summary> | ||
public KeyExchangeHybridInitMessage(byte[] init) | ||
{ | ||
CInit = init; | ||
} | ||
|
||
/// <summary> | ||
/// Called when type specific data need to be loaded. | ||
/// </summary> | ||
protected override void LoadData() | ||
{ | ||
CInit = ReadBinary(); | ||
} | ||
|
||
/// <summary> | ||
/// Called when type specific data need to be saved. | ||
/// </summary> | ||
protected override void SaveData() | ||
{ | ||
WriteBinaryString(CInit); | ||
} | ||
|
||
internal override void Process(Session session) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
} | ||
} |
95 changes: 95 additions & 0 deletions
95
src/Renci.SshNet/Messages/Transport/KeyExchangeHybridReplyMessage.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
namespace Renci.SshNet.Messages.Transport | ||
{ | ||
/// <summary> | ||
/// Represents SSH_MSG_KEX_HYBRID_REPLY message. | ||
/// </summary> | ||
public class KeyExchangeHybridReplyMessage : Message | ||
{ | ||
/// <inheritdoc /> | ||
public override string MessageName | ||
{ | ||
get | ||
{ | ||
return "SSH_MSG_KEX_HYBRID_REPLY"; | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override byte MessageNumber | ||
{ | ||
get | ||
{ | ||
return 31; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Gets a string encoding an X.509v3 certificate containing the server's ECDSA public host key. | ||
/// </summary> | ||
/// <value>The host key.</value> | ||
public byte[] KS { get; private set; } | ||
|
||
/// <summary> | ||
/// Gets the server reply. | ||
/// </summary> | ||
/// <remarks> | ||
/// The server reply is the concatenation of S_CT2 and S_PK1 (S_REPLY = S_CT2 || S_PK1). | ||
/// Typically, S_PK1 represents the ephemeral (EC)DH server public key. | ||
/// S_CT2 represents the ciphertext 'ct' output of the corresponding KEM's 'Encaps' algorithm generated by | ||
/// the server which encapsulates a secret to the client public key C_PK2. | ||
/// </remarks> | ||
public byte[] SReply { get; private set; } | ||
|
||
/// <summary> | ||
/// Gets an octet string containing the server's signature of the newly established exchange hash value. | ||
/// </summary> | ||
/// <value>The signature.</value> | ||
public byte[] Signature { get; private set; } | ||
|
||
/// <summary> | ||
/// Gets the size of the message in bytes. | ||
/// </summary> | ||
/// <value> | ||
/// The size of the messages in bytes. | ||
/// </value> | ||
protected override int BufferCapacity | ||
{ | ||
get | ||
{ | ||
var capacity = base.BufferCapacity; | ||
capacity += 4; // KS length | ||
capacity += KS.Length; // KS | ||
capacity += 4; // SReply length | ||
capacity += SReply.Length; // SReply | ||
capacity += 4; // Signature length | ||
capacity += Signature.Length; // Signature | ||
return capacity; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Called when type specific data need to be loaded. | ||
/// </summary> | ||
protected override void LoadData() | ||
{ | ||
KS = ReadBinary(); | ||
SReply = ReadBinary(); | ||
Signature = ReadBinary(); | ||
} | ||
|
||
/// <summary> | ||
/// Called when type specific data need to be saved. | ||
/// </summary> | ||
protected override void SaveData() | ||
{ | ||
WriteBinaryString(KS); | ||
WriteBinaryString(SReply); | ||
WriteBinaryString(Signature); | ||
} | ||
|
||
internal override void Process(Session session) | ||
{ | ||
session.OnKeyExchangeHybridReplyMessageReceived(this); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
134 changes: 134 additions & 0 deletions
134
src/Renci.SshNet/Security/KeyExchangeMLKem768x25519Sha256.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
using System.Globalization; | ||
using System.Linq; | ||
|
||
using Org.BouncyCastle.Crypto.Agreement; | ||
using Org.BouncyCastle.Crypto.Generators; | ||
using Org.BouncyCastle.Crypto.Kems; | ||
using Org.BouncyCastle.Crypto.Parameters; | ||
|
||
using Renci.SshNet.Abstractions; | ||
using Renci.SshNet.Common; | ||
using Renci.SshNet.Messages.Transport; | ||
|
||
namespace Renci.SshNet.Security | ||
{ | ||
internal sealed class KeyExchangeMLKem768x25519Sha256 : KeyExchangeEC | ||
{ | ||
private MLKemDecapsulator _mlkemDecapsulator; | ||
private X25519Agreement _x25519Agreement; | ||
|
||
/// <summary> | ||
/// Gets algorithm name. | ||
/// </summary> | ||
public override string Name | ||
{ | ||
get { return "mlkem768x25519-sha256"; } | ||
} | ||
|
||
/// <summary> | ||
/// Gets the size, in bits, of the computed hash code. | ||
/// </summary> | ||
/// <value> | ||
/// The size, in bits, of the computed hash code. | ||
/// </value> | ||
protected override int HashSize | ||
{ | ||
get { return 256; } | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override void Start(Session session, KeyExchangeInitMessage message, bool sendClientInitMessage) | ||
{ | ||
base.Start(session, message, sendClientInitMessage); | ||
|
||
Session.RegisterMessage("SSH_MSG_KEX_HYBRID_REPLY"); | ||
|
||
Session.KeyExchangeHybridReplyMessageReceived += Session_KeyExchangeHybridReplyMessageReceived; | ||
|
||
var mlkem768KeyPairGenerator = new MLKemKeyPairGenerator(); | ||
mlkem768KeyPairGenerator.Init(new MLKemKeyGenerationParameters(CryptoAbstraction.SecureRandom, MLKemParameters.ml_kem_768)); | ||
var mlkem768KeyPair = mlkem768KeyPairGenerator.GenerateKeyPair(); | ||
|
||
_mlkemDecapsulator = new MLKemDecapsulator(MLKemParameters.ml_kem_768); | ||
_mlkemDecapsulator.Init(mlkem768KeyPair.Private); | ||
|
||
var x25519KeyPairGenerator = new X25519KeyPairGenerator(); | ||
x25519KeyPairGenerator.Init(new X25519KeyGenerationParameters(CryptoAbstraction.SecureRandom)); | ||
var x25519KeyPair = x25519KeyPairGenerator.GenerateKeyPair(); | ||
|
||
_x25519Agreement = new X25519Agreement(); | ||
_x25519Agreement.Init(x25519KeyPair.Private); | ||
|
||
var mlkem768PublicKey = ((MLKemPublicKeyParameters)mlkem768KeyPair.Public).GetEncoded(); | ||
var x25519PublicKey = ((X25519PublicKeyParameters)x25519KeyPair.Public).GetEncoded(); | ||
|
||
_clientExchangeValue = mlkem768PublicKey.Concat(x25519PublicKey); | ||
|
||
SendMessage(new KeyExchangeHybridInitMessage(_clientExchangeValue)); | ||
} | ||
|
||
/// <summary> | ||
/// Finishes key exchange algorithm. | ||
/// </summary> | ||
public override void Finish() | ||
{ | ||
base.Finish(); | ||
|
||
Session.KeyExchangeHybridReplyMessageReceived -= Session_KeyExchangeHybridReplyMessageReceived; | ||
} | ||
|
||
/// <summary> | ||
/// Hashes the specified data bytes. | ||
/// </summary> | ||
/// <param name="hashData">The hash data.</param> | ||
/// <returns> | ||
/// The hash of the data. | ||
/// </returns> | ||
protected override byte[] Hash(byte[] hashData) | ||
{ | ||
return CryptoAbstraction.HashSHA256(hashData); | ||
} | ||
|
||
private void Session_KeyExchangeHybridReplyMessageReceived(object sender, MessageEventArgs<KeyExchangeHybridReplyMessage> e) | ||
{ | ||
var message = e.Message; | ||
|
||
// Unregister message once received | ||
Session.UnRegisterMessage("SSH_MSG_KEX_HYBRID_REPLY"); | ||
|
||
HandleServerHybridReply(message.KS, message.SReply, message.Signature); | ||
|
||
// When SSH_MSG_KEX_HYBRID_REPLY received key exchange is completed | ||
Finish(); | ||
} | ||
|
||
/// <summary> | ||
/// Handles the server hybrid reply message. | ||
/// </summary> | ||
/// <param name="hostKey">The host key.</param> | ||
/// <param name="serverExchangeValue">The server exchange value.</param> | ||
/// <param name="signature">The signature.</param> | ||
private void HandleServerHybridReply(byte[] hostKey, byte[] serverExchangeValue, byte[] signature) | ||
{ | ||
_serverExchangeValue = serverExchangeValue; | ||
_hostKey = hostKey; | ||
_signature = signature; | ||
|
||
if (serverExchangeValue.Length != _mlkemDecapsulator.EncapsulationLength + _x25519Agreement.AgreementSize) | ||
{ | ||
throw new SshConnectionException( | ||
string.Format(CultureInfo.CurrentCulture, "Bad S_Reply length: {0}.", serverExchangeValue.Length), | ||
DisconnectReason.KeyExchangeFailed); | ||
} | ||
|
||
var secret = new byte[_mlkemDecapsulator.SecretLength + _x25519Agreement.AgreementSize]; | ||
|
||
_mlkemDecapsulator.Decapsulate(serverExchangeValue, 0, _mlkemDecapsulator.EncapsulationLength, secret, 0, _mlkemDecapsulator.SecretLength); | ||
|
||
var x25519PublicKey = serverExchangeValue.Take(_mlkemDecapsulator.EncapsulationLength, _x25519Agreement.AgreementSize); | ||
_x25519Agreement.CalculateAgreement(new X25519PublicKeyParameters(x25519PublicKey), secret, _mlkemDecapsulator.SecretLength); | ||
|
||
SharedKey = CryptoAbstraction.HashSHA256(secret); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.