using Hazel.Crypto;
using System;
using System.Diagnostics;
using System.Security.Cryptography;
namespace Hazel.Dtls
{
///
/// ECDHE_RSA_*_256 cipher suite
///
public class X25519EcdheRsaSha256 : IHandshakeCipherSuite
{
private readonly ByteSpan privateAgreementKey;
private SHA256 sha256 = SHA256.Create();
///
/// Create a new instance of the x25519 key exchange
///
/// Random data source
public X25519EcdheRsaSha256(RandomNumberGenerator random)
{
byte[] buffer = new byte[X25519.KeySize];
random.GetBytes(buffer);
this.privateAgreementKey = buffer;
}
///
public void Dispose()
{
this.sha256?.Dispose();
this.sha256 = null;
}
///
public int SharedKeySize()
{
return X25519.KeySize;
}
///
/// Calculate the server message size given an RSA key size
///
///
/// Size of the private key (in bits)
///
///
/// Size of the ServerKeyExchange message in bytes
///
private static int CalculateServerMessageSize(int keySize)
{
int signatureSize = keySize / 8;
return 0
+ 1 // ECCurveType ServerKeyExchange.params.curve_params.curve_type
+ 2 // NamedCurve ServerKeyExchange.params.curve_params.namedcurve
+ 1 + X25519.KeySize // ECPoint ServerKeyExchange.params.public
+ 1 // HashAlgorithm ServerKeyExchange.algorithm.hash
+ 1 // SignatureAlgorithm ServerKeyExchange.signed_params.algorithm.signature
+ 2 // ServerKeyExchange.signed_params.size
+ signatureSize // ServerKeyExchange.signed_params.opaque
;
}
///
public int CalculateServerMessageSize(object privateKey)
{
RSA rsaPrivateKey = privateKey as RSA;
if (rsaPrivateKey == null)
{
throw new ArgumentException("Invalid private key", nameof(privateKey));
}
return CalculateServerMessageSize(rsaPrivateKey.KeySize);
}
///
public void EncodeServerKeyExchangeMessage(ByteSpan output, object privateKey)
{
RSA rsaPrivateKey = privateKey as RSA;
if (rsaPrivateKey == null)
{
throw new ArgumentException("Invalid private key", nameof(privateKey));
}
output[0] = (byte)ECCurveType.NamedCurve;
output.WriteBigEndian16((ushort)NamedCurve.x25519, 1);
output[3] = (byte)X25519.KeySize;
X25519.Func(output.Slice(4, X25519.KeySize), this.privateAgreementKey);
// Hash the key parameters
byte[] paramterDigest = this.sha256.ComputeHash(output.GetUnderlyingArray(), output.Offset, 4 + X25519.KeySize);
// Sign the paramter digest
RSAPKCS1SignatureFormatter signer = new RSAPKCS1SignatureFormatter(rsaPrivateKey);
signer.SetHashAlgorithm("SHA256");
ByteSpan signature = signer.CreateSignature(paramterDigest);
Debug.Assert(signature.Length == rsaPrivateKey.KeySize/8);
output[4 + X25519.KeySize] = (byte)HashAlgorithm.Sha256;
output[5 + X25519.KeySize] = (byte)SignatureAlgorithm.RSA;
output.Slice(6+X25519.KeySize).WriteBigEndian16((ushort)signature.Length);
signature.CopyTo(output.Slice(8+X25519.KeySize));
}
///
public bool VerifyServerMessageAndGenerateSharedKey(ByteSpan output, ByteSpan serverKeyExchangeMessage, object publicKey)
{
RSA rsaPublicKey = publicKey as RSA;
if (rsaPublicKey == null)
{
return false;
}
else if (output.Length != X25519.KeySize)
{
return false;
}
// Verify message is compatible with this cipher suite
if (serverKeyExchangeMessage.Length != CalculateServerMessageSize(rsaPublicKey.KeySize))
{
return false;
}
else if (serverKeyExchangeMessage[0] != (byte)ECCurveType.NamedCurve)
{
return false;
}
else if (serverKeyExchangeMessage.ReadBigEndian16(1) != (ushort)NamedCurve.x25519)
{
return false;
}
else if (serverKeyExchangeMessage[3] != X25519.KeySize)
{
return false;
}
else if (serverKeyExchangeMessage[4 + X25519.KeySize] != (byte)HashAlgorithm.Sha256)
{
return false;
}
else if (serverKeyExchangeMessage[5 + X25519.KeySize] != (byte)SignatureAlgorithm.RSA)
{
return false;
}
ByteSpan keyParameters = serverKeyExchangeMessage.Slice(0, 4+X25519.KeySize);
ByteSpan othersPublicKey = keyParameters.Slice(4);
ushort signatureSize = serverKeyExchangeMessage.ReadBigEndian16(6 + X25519.KeySize);
ByteSpan signature = serverKeyExchangeMessage.Slice(4+keyParameters.Length);
if (signatureSize != signature.Length)
{
return false;
}
// Hash the key parameters
byte[] parameterDigest = this.sha256.ComputeHash(keyParameters.GetUnderlyingArray(), keyParameters.Offset, keyParameters.Length);
// Verify the signature
RSAPKCS1SignatureDeformatter verifier = new RSAPKCS1SignatureDeformatter(rsaPublicKey);
verifier.SetHashAlgorithm("SHA256");
if (!verifier.VerifySignature(parameterDigest, signature.ToArray()))
{
return false;
}
// Signature has been validated, generate the shared key
return X25519.Func(output, this.privateAgreementKey, othersPublicKey);
}
private static int ClientMessageSize = 0
+ 1 + X25519.KeySize // ECPoint ClientKeyExchange.ecdh_Yc
;
///
public int CalculateClientMessageSize()
{
return ClientMessageSize;
}
///
public void EncodeClientKeyExchangeMessage(ByteSpan output)
{
output[0] = (byte)X25519.KeySize;
X25519.Func(output.Slice(1, X25519.KeySize), this.privateAgreementKey);
}
///
public bool VerifyClientMessageAndGenerateSharedKey(ByteSpan output, ByteSpan clientKeyExchangeMessage)
{
if (clientKeyExchangeMessage.Length != ClientMessageSize)
{
return false;
}
else if (clientKeyExchangeMessage[0] != (byte)X25519.KeySize)
{
return false;
}
ByteSpan othersPublicKey = clientKeyExchangeMessage.Slice(1);
return X25519.Func(output, this.privateAgreementKey, othersPublicKey);
}
}
}