Files

816 lines
30 KiB
C#

/* ========================================================================
* Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Security.Cryptography;
#if CURVE25519
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Crypto.Agreement;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Digests;
#endif
namespace Opc.Ua
{
/// <summary>
/// Represents a cryptographic nonce used for secure communication.
/// </summary>
public class Nonce : IDisposable
{
private ECDiffieHellman? m_ecdh;
private RSADiffieHellman? m_rsadh;
private static readonly RandomNumberGenerator s_rng = RandomNumberGenerator.Create();
private static uint s_minNonceLength = 32;
/// <summary>
/// Constructor
/// </summary>
private Nonce()
{
m_ecdh = null;
m_rsadh = null;
Data = default!;
}
/// <summary>
/// Gets the nonce data.
/// </summary>
public byte[]? Data { get; private set; }
internal byte[]? GenerateSecret(
Nonce remoteNonce,
byte[]? previousSecret)
{
byte[]? ikm = null;
#if OPCUA_CryptoTrace
CryptoTrace.Start(ConsoleColor.Cyan, $"GenerateSecret");
#endif
#if NET8_0_OR_GREATER
if (m_ecdh != null)
{
ikm = m_ecdh.DeriveRawSecretAgreement(remoteNonce.m_ecdh!.PublicKey);
}
else if (m_rsadh != null)
{
ikm = m_rsadh.DeriveRawSecretAgreement(remoteNonce.m_rsadh!);
}
#else // !NET8_0_OR_GREATER (NET78 and NET80)
if (m_ecdh != null)
{
ikm = m_ecdh.DeriveKeyMaterial(remoteNonce.m_ecdh!.PublicKey);
}
else if (m_rsadh != null)
{
ikm = m_rsadh.DeriveRawSecretAgreement(remoteNonce.m_rsadh!);
}
#endif
#if OPCUA_CryptoTrace
CryptoTrace.WriteLine($"IKM-Raw={CryptoTrace.KeyToString(ikm)}");
CryptoTrace.WriteLine($"Previous-IKM={CryptoTrace.KeyToString(previousSecret)}");
#endif
if (ikm != null && previousSecret != null)
{
for (int ii = 0; ii < ikm.Length && ii < previousSecret.Length; ii++)
{
ikm[ii] ^= previousSecret[ii];
}
}
#if OPCUA_CryptoTrace
CryptoTrace.WriteLine($"IKM-XOR={CryptoTrace.KeyToString(ikm)}");
CryptoTrace.Finish("GenerateSecret");
#endif
return ikm;
}
/// <summary>
/// Derives a key from the remote nonce, using the specified salt,
/// hash algorithm, and length.
/// </summary>
/// <param name="secret">The secret to use in key derivation.</param>
/// <param name="salt">The salt to use in key derivation.</param>
/// <param name="algorithm">The hash algorithm to use in key derivation.</param>
/// <param name="length">The length of the derived key.</param>
/// <returns>The derived key.</returns>
public byte[] DeriveKeyData(
byte[] secret,
byte[] salt,
KeyDerivationAlgorithm algorithm,
int length)
{
#if OPCUA_CryptoTrace
CryptoTrace.Start(ConsoleColor.DarkCyan, $"DeriveKeyData");
CryptoTrace.WriteLine($"Secret={CryptoTrace.KeyToString(secret)}");
CryptoTrace.WriteLine($"Salt={CryptoTrace.KeyToString(salt)}");
#endif
using HMAC extract = algorithm switch
{
KeyDerivationAlgorithm.HKDFSha256 => new HMACSHA256(salt),
KeyDerivationAlgorithm.HKDFSha384 => new HMACSHA384(salt),
_ => new HMACSHA256(salt)
};
byte[] prk = extract.ComputeHash(secret);
#if OPCUA_CryptoTrace
CryptoTrace.WriteLine($"PRK={CryptoTrace.KeyToString(prk)}");
#endif
using HMAC expand = algorithm switch
{
KeyDerivationAlgorithm.HKDFSha256 => new HMACSHA256(prk),
KeyDerivationAlgorithm.HKDFSha384 => new HMACSHA384(prk),
_ => new HMACSHA256(prk)
};
byte[] output = new byte[length];
byte counter = 1;
byte[] info = new byte[(expand.HashSize / 8) + salt.Length + 1];
Buffer.BlockCopy(salt, 0, info, 0, salt.Length);
info[salt.Length] = counter++;
// computer T(1)
byte[] hash = expand.ComputeHash(info, 0, salt.Length + 1);
#if OPCUA_CryptoTrace
CryptoTrace.WriteLine($"T(1)={CryptoTrace.KeyToString(hash)}");
#endif
int pos = 0;
for (int ii = 0; ii < hash.Length && pos < length; ii++)
{
output[pos++] = hash[ii];
}
while (pos < length)
{
Buffer.BlockCopy(hash, 0, info, 0, hash.Length);
Buffer.BlockCopy(salt, 0, info, hash.Length, salt.Length);
info[^1] = counter++;
hash = expand.ComputeHash(info, 0, info.Length);
#if OPCUA_CryptoTrace
CryptoTrace.WriteLine($"T({counter - 1})={CryptoTrace.KeyToString(hash)}");
#endif
for (int ii = 0; ii < hash.Length && pos < length; ii++)
{
output[pos++] = hash[ii];
}
}
#if OPCUA_CryptoTrace
CryptoTrace.WriteLine($"KeyData={CryptoTrace.KeyToString(output)}");
CryptoTrace.Finish("DeriveKeyData");
#endif
return output;
}
/// <summary>
/// Generates a Nonce for cryptographic functions of a given length.
/// </summary>
/// <returns>The requested Nonce as a</returns>
public static Nonce CreateNonce(int length)
{
return new Nonce { Data = CreateRandomNonceData(length) };
}
/// <summary>
/// Creates a nonce for the specified security policy URI and nonce length.
/// </summary>
public static Nonce CreateNonce(string securityPolicyUri)
{
SecurityPolicyInfo? info = SecurityPolicies.GetInfo(securityPolicyUri);
return CreateNonce(info!);
}
/// <summary>
/// Creates a nonce for the specified security policy and nonce length.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="securityPolicy"/> is <c>null</c>.</exception>
public static Nonce CreateNonce(SecurityPolicyInfo securityPolicy)
{
if (securityPolicy == null)
{
throw new ArgumentNullException(nameof(securityPolicy));
}
switch (securityPolicy.EphemeralKeyAlgorithm)
{
case CertificateKeyAlgorithm.RSADH:
return securityPolicy.MinAsymmetricKeyLength switch
{
//2048 => CreateNonce(RSADiffieHellmanGroup.FFDHE2048),
//3072 => CreateNonce(RSADiffieHellmanGroup.FFDHE3072),
//4096 => CreateNonce(RSADiffieHellmanGroup.FFDHE4096),
_ => CreateNonce(RSADiffieHellmanGroup.FFDHE3072)
};
case CertificateKeyAlgorithm.NistP256:
return CreateNonce(ECCurve.NamedCurves.nistP256);
case CertificateKeyAlgorithm.NistP384:
return CreateNonce(ECCurve.NamedCurves.nistP384);
case CertificateKeyAlgorithm.BrainpoolP256r1:
return CreateNonce(ECCurve.NamedCurves.brainpoolP256r1);
case CertificateKeyAlgorithm.BrainpoolP384r1:
return CreateNonce(ECCurve.NamedCurves.brainpoolP384r1);
default:
// Basic128Rsa15 keeps the legacy 16-byte nonce for compatibility.
bool enforceMinimumLength = !string.Equals(
securityPolicy.Uri,
SecurityPolicies.Basic128Rsa15,
StringComparison.Ordinal);
return new Nonce
{
Data = CreateRandomNonceData(
securityPolicy.SecureChannelNonceLength,
enforceMinimumLength)
};
}
}
/// <summary>
/// Creates a new Nonce object for the specified RSA DiffieHellman group.
/// </summary>
public static Nonce CreateNonce(RSADiffieHellmanGroup group)
{
var nonce = new Nonce
{
m_rsadh = RSADiffieHellman.Create(group)
};
nonce.Data = nonce.m_rsadh.GetNonce();
return nonce;
}
/// <summary>
/// Creates a nonce for the specified security policy URI and nonce data.
/// </summary>
public static Nonce CreateNonce(string securityPolicyUri, byte[] nonceData)
{
SecurityPolicyInfo? info = SecurityPolicies.GetInfo(securityPolicyUri);
return CreateNonce(info!, nonceData);
}
/// <summary>
/// Creates a new Nonce object for the specified security policy and nonce data.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="securityPolicy"/> is <c>null</c>.</exception>
public static Nonce CreateNonce(SecurityPolicyInfo securityPolicy, byte[] nonceData)
{
if (securityPolicy == null)
{
throw new ArgumentNullException(nameof(securityPolicy));
}
if (nonceData == null)
{
throw new ArgumentNullException(nameof(nonceData));
}
if (securityPolicy.EphemeralKeyAlgorithm == CertificateKeyAlgorithm.RSADH)
{
return new Nonce
{
m_rsadh = RSADiffieHellman.Create(nonceData),
Data = nonceData
};
}
switch (securityPolicy.EphemeralKeyAlgorithm)
{
case CertificateKeyAlgorithm.NistP256:
return CreateNonce(ECCurve.NamedCurves.nistP256, nonceData);
case CertificateKeyAlgorithm.NistP384:
return CreateNonce(ECCurve.NamedCurves.nistP384, nonceData);
case CertificateKeyAlgorithm.BrainpoolP256r1:
return CreateNonce(ECCurve.NamedCurves.brainpoolP256r1, nonceData);
case CertificateKeyAlgorithm.BrainpoolP384r1:
return CreateNonce(ECCurve.NamedCurves.brainpoolP384r1, nonceData);
case CertificateKeyAlgorithm.Curve25519:
return CreateNonceForCurve25519(nonceData);
case CertificateKeyAlgorithm.Curve448:
return CreateNonceForCurve448(nonceData);
default:
return new Nonce { Data = nonceData };
}
}
/// <summary>
/// Generates a Nonce for cryptographic functions of a given length.
/// </summary>
/// <returns>The requested Nonce as a</returns>
public static byte[] CreateRandomNonceData(int length)
{
return CreateRandomNonceData(length, true);
}
/// <summary>
/// Generates nonce data with optional minimum length enforcement.
/// </summary>
public static byte[] CreateRandomNonceData(int length, bool enforceMinimumLength)
{
if (enforceMinimumLength && length < s_minNonceLength)
{
length = (int)s_minNonceLength;
}
byte[] randomBytes = new byte[length];
s_rng.GetBytes(randomBytes);
return randomBytes;
}
/// <summary>
/// Validates the nonce for a message security mode and security policy.
/// </summary>
public static bool ValidateNonce(
byte[] nonce,
MessageSecurityMode securityMode,
SecurityPolicyInfo securityPolicy)
{
return ValidateNonce(nonce, securityMode, securityPolicy.SecureChannelNonceLength);
}
/// <summary>
/// Validates the nonce for a message security mode and a minimum length.
/// </summary>
public static bool ValidateNonce(
byte[] nonce,
MessageSecurityMode securityMode,
int minNonceLength)
{
// no nonce needed for no security.
if (securityMode == MessageSecurityMode.None)
{
return true;
}
// check the length.
if (nonce == null || nonce.Length < minNonceLength)
{
return false;
}
// try to catch programming errors by rejecting nonces with all zeros.
for (int ii = 0; ii < nonce.Length; ii++)
{
if (nonce[ii] != 0)
{
return true;
}
}
return false;
}
/// <summary>
/// Compare Nonce for equality.
/// </summary>
public static bool CompareNonce(byte[] a, byte[] b)
{
if (a == null || b == null)
{
return false;
}
if (a.Length != b.Length)
{
return false;
}
byte result = 0;
for (int i = 0; i < a.Length; i++)
{
result |= (byte)(a[i] ^ b[i]);
}
return result == 0;
}
/// <summary>
/// Sets the minimum nonce value to be used as default
/// </summary>
public static void SetMinNonceValue(uint nonceLength)
{
s_minNonceLength = nonceLength;
}
/// <summary>
/// Creates a new Nonce object for use with Curve25519.
/// </summary>
/// <param name="nonceData">The nonce data to use.</param>
/// <returns>A new Nonce object.</returns>
private static Nonce CreateNonceForCurve25519(byte[] nonceData)
{
return new Nonce { Data = nonceData };
}
/// <summary>
/// Creates a new Nonce instance for Curve448.
/// </summary>
/// <param name="nonceData">The nonce data.</param>
/// <returns>A new Nonce instance.</returns>
private static Nonce CreateNonceForCurve448(byte[] nonceData)
{
return new Nonce { Data = nonceData };
}
/// <summary>
/// Creates a new Nonce instance with the specified ECC curve and nonce data.
/// </summary>
/// <param name="curve">The elliptic curve to use for the ECDH key exchange.</param>
/// <param name="nonceData">The nonce data to use for the ECDH key exchange.</param>
/// <returns>A new Nonce instance with the specified curve and nonce data.</returns>
/// <exception cref="ArgumentException"></exception>
private static Nonce CreateNonce(ECCurve curve, byte[] nonceData)
{
var nonce = new Nonce { Data = nonceData };
int keyLength = nonceData.Length;
byte[] qx = new byte[keyLength / 2];
byte[] qy = new byte[keyLength / 2];
Buffer.BlockCopy(nonceData, 0, qx, 0, keyLength / 2);
Buffer.BlockCopy(nonceData, keyLength / 2, qy, 0, keyLength / 2);
var ecdhParameters = new ECParameters { Curve = curve, Q = { X = qx, Y = qy } };
//validate curve parameters as ECDiffieHellman.Create expects already validated curve parameters
try
{
ecdhParameters.Validate();
nonce.m_ecdh = ECDiffieHellman.Create(ecdhParameters);
}
catch (CryptographicException e)
{
throw new ArgumentException("Invalid nonce data provided", nameof(nonceData), e);
}
//On Windows a PlatformNotSupportedException is thrown when invalid parameters are provided
catch (PlatformNotSupportedException e)
{
throw new ArgumentException("Invalid nonce data provided", nameof(nonceData), e);
}
return nonce;
}
/// <summary>
/// Creates a new Nonce instance using the specified elliptic curve.
/// </summary>
/// <param name="curve">The elliptic curve to use for the ECDH key exchange.</param>
/// <returns>A new Nonce instance.</returns>
private static Nonce CreateNonce(ECCurve curve)
{
var ecdh = ECDiffieHellman.Create(curve);
ECParameters ecdhParameters = ecdh.ExportParameters(false);
int xLen = ecdhParameters.Q.X!.Length;
int yLen = ecdhParameters.Q.Y!.Length;
byte[] senderNonce = new byte[xLen + yLen];
Array.Copy(ecdhParameters.Q.X, senderNonce, xLen);
Array.Copy(ecdhParameters.Q.Y, 0, senderNonce, xLen, yLen);
return new Nonce { Data = senderNonce, m_ecdh = ecdh };
}
/// <summary>
/// Frees any unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// An overrideable version of the Dispose.
/// </summary>
protected virtual void Dispose(bool disposing)
{
if (disposing && m_ecdh != null)
{
m_ecdh.Dispose();
m_ecdh = null;
}
}
}
/// <summary>
/// The known RSA Diffie-Hellman groups.
/// </summary>
public enum RSADiffieHellmanGroup
{
/// <summary>
/// The 2048-bit finite field Diffie-Hellman ephemeral group defined in RFC 7919.
/// </summary>
FFDHE2048,
/// <summary>
/// The 3072-bit finite field Diffie-Hellman ephemeral group defined in RFC 7919.
/// </summary>
FFDHE3072,
/// <summary>
/// The 4096-bit finite field Diffie-Hellman ephemeral group defined in RFC 7919.
/// </summary>
FFDHE4096
}
/// <summary>
/// A RSA Diffie-Hellman key exchange implementation.
/// </summary>
public class RSADiffieHellman
{
private BigInteger m_privateKey;
private BigInteger m_publicKey;
private int m_nonceLength;
/// <summary>
/// ffdhe2048 prime from RFC 7919 (hex, without whitespace).
/// (RFC 7919 Appendix A.3 — use this canonical modulus in production.)
/// </summary>
private const string FFDHE2048_HEX = @"
FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1
D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9
7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561
2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935
984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735
30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB
B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19
0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61
9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73
3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA
886B4238 61285C97 FFFFFFFF FFFFFFFF";
private static readonly Lazy<BigInteger> s_P2048 = new(() => RfcTextToBytes(FFDHE2048_HEX));
private const int k_FFDHE2048_MinExponent = 224;
private const int k_FFDHE2048_MaxExponent = 255;
private const string FFDHE3072_HEX = @"
FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1
D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9
7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561
2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935
984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735
30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB
B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19
0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61
9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73
3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA
886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238
61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C
AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3
64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D
ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF
3C1B20EE 3FD59D7C 25E41D2B 66C62E37 FFFFFFFF FFFFFFFF";
private static readonly Lazy<BigInteger> s_P3072 = new(() => RfcTextToBytes(FFDHE3072_HEX));
private const int k_FFDHE3072_MinExponent = 275;
private const int k_FFDHE3072_MaxExponent = 383;
private const string FFDHE4096_HEX = @"
FFFFFFFF FFFFFFFF ADF85458 A2BB4A9A AFDC5620 273D3CF1
D8B9C583 CE2D3695 A9E13641 146433FB CC939DCE 249B3EF9
7D2FE363 630C75D8 F681B202 AEC4617A D3DF1ED5 D5FD6561
2433F51F 5F066ED0 85636555 3DED1AF3 B557135E 7F57C935
984F0C70 E0E68B77 E2A689DA F3EFE872 1DF158A1 36ADE735
30ACCA4F 483A797A BC0AB182 B324FB61 D108A94B B2C8E3FB
B96ADAB7 60D7F468 1D4F42A3 DE394DF4 AE56EDE7 6372BB19
0B07A7C8 EE0A6D70 9E02FCE1 CDF7E2EC C03404CD 28342F61
9172FE9C E98583FF 8E4F1232 EEF28183 C3FE3B1B 4C6FAD73
3BB5FCBC 2EC22005 C58EF183 7D1683B2 C6F34A26 C1B2EFFA
886B4238 611FCFDC DE355B3B 6519035B BC34F4DE F99C0238
61B46FC9 D6E6C907 7AD91D26 91F7F7EE 598CB0FA C186D91C
AEFE1309 85139270 B4130C93 BC437944 F4FD4452 E2D74DD3
64F2E21E 71F54BFF 5CAE82AB 9C9DF69E E86D2BC5 22363A0D
ABC52197 9B0DEADA 1DBF9A42 D5C4484E 0ABCD06B FA53DDEF
3C1B20EE 3FD59D7C 25E41D2B 669E1EF1 6E6F52C3 164DF4FB
7930E9E4 E58857B6 AC7D5F42 D69F6D18 7763CF1D 55034004
87F55BA5 7E31CC7A 7135C886 EFB4318A ED6A1E01 2D9E6832
A907600A 918130C4 6DC778F9 71AD0038 092999A3 33CB8B7A
1A1DB93D 7140003C 2A4ECEA9 F98D0ACC 0A8291CD CEC97DCF
8EC9B55A 7F88A46B 4DB5A851 F44182E1 C68A007E 5E655F6A
FFFFFFFF FFFFFFFF";
private static readonly Lazy<BigInteger> s_P4096 = new(() => RfcTextToBytes(FFDHE4096_HEX));
private const int k_FFDHE4096_MinExponent = 325;
private const int k_FFDHE4096_MaxExponent = 511;
private static readonly Lazy<RandomNumberGenerator> s_rng = new(RandomNumberGenerator.Create);
/// <summary>
/// Generator for FFDHE groups is 2
/// </summary>
private static readonly BigInteger s_G = new(2);
/// <summary>
/// Creates a new RSADiffieHellman instance for the specified group.
/// </summary>
/// <exception cref="NotSupportedException"></exception>
public static RSADiffieHellman Create(RSADiffieHellmanGroup group)
{
BigInteger p;
int min;
int max;
switch (group)
{
case RSADiffieHellmanGroup.FFDHE2048:
p = s_P2048.Value;
min = k_FFDHE2048_MinExponent;
max = k_FFDHE2048_MaxExponent;
break;
case RSADiffieHellmanGroup.FFDHE3072:
p = s_P3072.Value;
min = k_FFDHE3072_MinExponent;
max = k_FFDHE3072_MaxExponent;
break;
case RSADiffieHellmanGroup.FFDHE4096:
p = s_P4096.Value;
min = k_FFDHE4096_MinExponent;
max = k_FFDHE4096_MaxExponent;
break;
default:
throw new NotSupportedException("Unsupported RSA DH finite group type.");
}
var dh = new RSADiffieHellman();
byte[] seed = new byte[1];
s_rng.Value.GetBytes(seed);
int keyLength = (seed[0] % (max - min + 1)) + min;
byte[] key = new byte[1 + ((keyLength + 7) / 8)];
s_rng.Value.GetBytes(key);
key[^1] = 0;
dh.m_privateKey = new BigInteger(key);
dh.m_publicKey = BigInteger.ModPow(s_G, dh.m_privateKey, p);
dh.m_nonceLength = max + 1;
return dh;
}
/// <summary>
/// Creates a new RSADiffieHellman instance from the nonce.
/// </summary>
public static RSADiffieHellman Create(byte[] nonce)
{
var dh = new RSADiffieHellman();
byte[] bytes = new byte[nonce.Length + 1];
for (int ii = 0; ii < nonce.Length; ii++)
{
bytes[ii] = nonce[nonce.Length - ii - 1];
}
dh.m_publicKey = new BigInteger(bytes);
dh.m_nonceLength = nonce.Length;
return dh;
}
/// <summary>
/// Returns the nonce representing the public key.
/// </summary>
public byte[] GetNonce()
{
byte[] nonce = new byte[m_nonceLength];
byte[] publicKey = m_publicKey.ToByteArray();
for (int ii = 0; ii < publicKey.Length && ii < nonce.Length; ii++)
{
nonce[nonce.Length - 1 - ii] = publicKey[ii];
}
return nonce;
}
/// <summary>
/// Derives the raw secret agreement from the remote key.
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="NotSupportedException"></exception>
public byte[] DeriveRawSecretAgreement(RSADiffieHellman remoteKey)
{
if (m_privateKey.IsZero)
{
throw new InvalidOperationException("Private key not available.");
}
BigInteger p;
switch (m_nonceLength)
{
case 256:
p = s_P2048.Value;
break;
case 384:
p = s_P3072.Value;
break;
case 512:
p = s_P4096.Value;
break;
default:
throw new NotSupportedException("Unsupported RSA DH finite group type.");
}
var shared = BigInteger.ModPow(remoteKey.m_publicKey, m_privateKey, p);
byte[] bytes = shared.ToByteArray();
if (bytes.Length < m_nonceLength)
{
byte[] padded = new byte[m_nonceLength];
Array.Copy(bytes, 0, padded, 0, bytes.Length);
bytes = padded;
}
else if (bytes.Length > m_nonceLength)
{
byte[] trucated = new byte[m_nonceLength];
Array.Copy(bytes, 0, trucated, 0, m_nonceLength);
bytes = trucated;
}
// make sure bytes are in big-endian order.
Array.Reverse(bytes);
return bytes;
}
private static BigInteger RfcTextToBytes(string rfcText)
{
var bytes = new List<byte>();
char[] digit = new char[2];
int pos = 0;
bytes.Add(0);
for (int ii = 0; ii < rfcText.Length; ii++)
{
if (char.IsWhiteSpace(rfcText[ii]))
{
continue;
}
digit[pos++] = rfcText[ii];
if (pos == 2)
{
bytes.Add(Convert.ToByte(new string(digit), 16));
pos = 0;
}
}
bytes.Reverse();
return new BigInteger([.. bytes]);
}
}
}