diff --git a/Applications/ConsoleReferenceClient/ClientSamples.cs b/Applications/ConsoleReferenceClient/ClientSamples.cs
index efc833da47..af3bf080c0 100644
--- a/Applications/ConsoleReferenceClient/ClientSamples.cs
+++ b/Applications/ConsoleReferenceClient/ClientSamples.cs
@@ -857,7 +857,7 @@ await uaClient
result.Sort((x, y) => x.NodeId.CompareTo(y.NodeId));
m_logger.LogInformation(
- "ManagedBrowseFullAddressSpace found {Count} references on server in {Duration}ms.",
+ "ManagedBrowseFullAddressSpace found {Count} references on server in {Duration} ms.",
result.Count,
stopWatch.ElapsedMilliseconds);
@@ -1078,7 +1078,7 @@ BrowseDescriptionCollection browseDescriptionCollection
result.Sort((x, y) => x.NodeId.CompareTo(y.NodeId));
m_logger.LogInformation(
- "BrowseFullAddressSpace found {Count} references on server in {Duration}ms.",
+ "BrowseFullAddressSpace found {Count} references on server in {Duration} ms.",
referenceDescriptions.Count,
stopWatch.ElapsedMilliseconds);
diff --git a/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml b/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml
index a6f2503138..158c6a601e 100644
--- a/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml
+++ b/Applications/ConsoleReferenceServer/Quickstarts.ReferenceServer.Config.xml
@@ -166,6 +166,38 @@
SignAndEncrypt_3
http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP384r1
+
+ Sign_2
+ http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP256_AesGcm
+
+
+ SignAndEncrypt_3
+ http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP256_AesGcm
+
+
+ Sign_2
+ http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP256r1_ChaChaPoly
+
+
+ SignAndEncrypt_3
+ http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP256r1_ChaChaPoly
+
+
+ Sign_2
+ http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP384r1_ChaChaPoly
+
+
+ SignAndEncrypt_3
+ http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP384r1_ChaChaPoly
+
+
+ Sign_2
+ http://opcfoundation.org/UA/SecurityPolicy#RSA_DH_AesGcm
+
+
+ SignAndEncrypt_3
+ http://opcfoundation.org/UA/SecurityPolicy#RSA_DH_AesGcm
+
None_1
http://opcfoundation.org/UA/SecurityPolicy#None
diff --git a/Libraries/Opc.Ua.Client/Session/Session.cs b/Libraries/Opc.Ua.Client/Session/Session.cs
index b56b6e90cd..7d57ca3b83 100644
--- a/Libraries/Opc.Ua.Client/Session/Session.cs
+++ b/Libraries/Opc.Ua.Client/Session/Session.cs
@@ -327,7 +327,7 @@ private void ValidateServerNonce(
if (!Nonce.ValidateNonce(
serverNonce,
MessageSecurityMode.SignAndEncrypt,
- (uint)m_configuration.SecurityConfiguration.NonceLength))
+ m_configuration.SecurityConfiguration.NonceLength))
{
if (channelSecurityMode == MessageSecurityMode.SignAndEncrypt ||
m_configuration.SecurityConfiguration.SuppressNonceValidationErrors)
@@ -893,9 +893,11 @@ public virtual void Restore(SessionState state)
///
public void Snapshot(out SessionConfiguration sessionConfiguration)
{
- var serverNonce = Nonce.CreateNonce(
- m_endpoint.Description?.SecurityPolicyUri,
- m_serverNonce);
+ byte[]? serverNonce = m_serverNonce != null ? [.. m_serverNonce] : null;
+ byte[]? clientNonce = m_clientNonce != null ? [.. m_clientNonce] : null;
+ byte[]? serverEccEphemeralKey = m_eccServerEphemeralKey?.Data != null
+ ? [.. m_eccServerEphemeralKey.Data]
+ : null;
sessionConfiguration = new SessionConfiguration
{
SessionName = SessionName,
@@ -905,7 +907,8 @@ public void Snapshot(out SessionConfiguration sessionConfiguration)
ConfiguredEndpoint = ConfiguredEndpoint,
CheckDomain = CheckDomain,
ServerNonce = serverNonce,
- ServerEccEphemeralKey = m_eccServerEphemeralKey,
+ ClientNonce = clientNonce,
+ ServerEccEphemeralKey = serverEccEphemeralKey,
UserIdentityTokenPolicy = m_userTokenSecurityPolicyUri
};
}
@@ -922,9 +925,27 @@ public void Restore(SessionConfiguration sessionConfiguration)
: null;
m_identity = sessionConfiguration.Identity ?? new UserIdentity();
m_checkDomain = sessionConfiguration.CheckDomain;
- m_serverNonce = sessionConfiguration.ServerNonce?.Data;
+ m_serverNonce = sessionConfiguration.ServerNonce != null
+ ? [.. sessionConfiguration.ServerNonce]
+ : null;
+ m_clientNonce = sessionConfiguration.ClientNonce != null
+ ? [.. sessionConfiguration.ClientNonce]
+ : null;
m_userTokenSecurityPolicyUri = sessionConfiguration.UserIdentityTokenPolicy;
- m_eccServerEphemeralKey = sessionConfiguration.ServerEccEphemeralKey;
+ if (sessionConfiguration.ServerEccEphemeralKey?.Length > 0)
+ {
+ string? ephemeralKeyPolicyUri = !string.IsNullOrEmpty(m_userTokenSecurityPolicyUri)
+ ? m_userTokenSecurityPolicyUri
+ : m_endpoint.Description?.SecurityPolicyUri ?? SecurityPolicies.None;
+ SecurityPolicyInfo ephemeralKeyPolicy = SecurityPolicies.GetInfo(ephemeralKeyPolicyUri);
+ m_eccServerEphemeralKey = Nonce.CreateNonce(
+ ephemeralKeyPolicy,
+ sessionConfiguration.ServerEccEphemeralKey);
+ }
+ else
+ {
+ m_eccServerEphemeralKey = null;
+ }
lock (m_lock)
{
@@ -1101,7 +1122,7 @@ public async Task OpenAsync(
out bool requireEncryption);
// validate the server certificate /certificate chain.
- IUserIdentityTokenHandler identityToken = identity.TokenHandler;
+ using IUserIdentityTokenHandler identityToken = identity.TokenHandler.Copy();
X509Certificate2? serverCertificate = null;
byte[]? certificateData = m_endpoint.Description.ServerCertificate;
@@ -1139,8 +1160,8 @@ await m_configuration
}
// create a nonce.
- uint length = (uint)m_configuration.SecurityConfiguration.NonceLength;
- byte[] clientNonce = Nonce.CreateRandomNonceData(length);
+ int length = m_configuration.SecurityConfiguration.NonceLength;
+ m_clientNonce = Nonce.CreateRandomNonceData(length);
// send the application instance certificate for the client.
BuildCertificateData(
@@ -1168,10 +1189,10 @@ await m_configuration
bool successCreateSession = false;
CreateSessionResponse? response = null;
- //if security none, first try to connect without certificate
+ // if security none, first try to connect without certificate
if (m_endpoint.Description.SecurityPolicyUri == SecurityPolicies.None)
{
- //first try to connect with client certificate NULL
+ // first try to connect with client certificate NULL
try
{
response = await base.CreateSessionAsync(
@@ -1180,7 +1201,7 @@ await m_configuration
m_endpoint.Description.Server.ApplicationUri,
m_endpoint.EndpointUrl.ToString(),
sessionName,
- clientNonce,
+ m_clientNonce,
null,
sessionTimeout,
maxMessageSize,
@@ -1203,7 +1224,7 @@ await m_configuration
m_endpoint.Description.Server.ApplicationUri,
m_endpoint.EndpointUrl.ToString(),
sessionName,
- clientNonce,
+ m_clientNonce,
clientCertificateChainData ?? clientCertificateData,
sessionTimeout,
maxMessageSize,
@@ -1252,52 +1273,76 @@ await m_configuration
serverSignature,
clientCertificateData,
clientCertificateChainData,
- clientNonce);
+ m_clientNonce,
+ serverNonce);
// process additional header
ProcessResponseAdditionalHeader(response.ResponseHeader, serverCertificate);
// create the client signature.
- byte[] dataToSign = Utils.Append(serverCertificate?.RawData, serverNonce);
- SignatureData clientSignature = SecurityPolicies.Sign(
- m_instanceCertificate,
+ SecurityPolicyInfo securityPolicy = SecurityPolicies.GetInfo(securityPolicyUri);
+
+ // create the client signature.
+ byte[] dataToSign = securityPolicy.GetClientSignatureData(
+ TransportChannel.ChannelThumbprint,
+ serverNonce,
+ serverCertificate?.RawData,
+ TransportChannel.ServerChannelCertificate,
+ TransportChannel.ClientChannelCertificate,
+ m_clientNonce ?? []);
+
+ SignatureData clientSignature = SecurityPolicies.CreateSignatureData(
securityPolicyUri,
+ m_instanceCertificate,
dataToSign);
// select the security policy for the user token.
- string? tokenSecurityPolicyUri = identityPolicy.SecurityPolicyUri;
-
- if (string.IsNullOrEmpty(tokenSecurityPolicyUri))
- {
- tokenSecurityPolicyUri = m_endpoint.Description.SecurityPolicyUri;
- }
-
- // save previous nonce
- byte[]? previousServerNonce = GetCurrentTokenServerNonce();
+ string? tokenSecurityPolicyUri = string.IsNullOrEmpty(identityPolicy.SecurityPolicyUri)
+ ? m_endpoint.Description.SecurityPolicyUri ?? SecurityPolicies.None
+ : identityPolicy.SecurityPolicyUri;
// validate server nonce and security parameters for user identity.
ValidateServerNonce(
identity,
serverNonce,
tokenSecurityPolicyUri,
- previousServerNonce,
+ m_previousServerNonce,
m_endpoint.Description.SecurityMode);
- // sign data with user token.
- SignatureData userTokenSignature = identityToken.Sign(
- dataToSign,
- tokenSecurityPolicyUri);
+ // remember the policy actually used to encrypt the user token
+ m_userTokenSecurityPolicyUri = tokenSecurityPolicyUri;
- // encrypt token.
- identityToken.Encrypt(
- serverCertificate,
- serverNonce,
- m_userTokenSecurityPolicyUri,
- MessageContext,
- m_eccServerEphemeralKey,
- m_instanceCertificate,
- m_instanceCertificateChain,
- m_endpoint.Description.SecurityMode != MessageSecurityMode.None);
+ SignatureData? userTokenSignature = null;
+
+ if (identityToken.Token is X509IdentityToken)
+ {
+ // sign data with user token.
+ dataToSign = securityPolicy.GetUserTokenSignatureData(
+ TransportChannel.ChannelThumbprint,
+ serverNonce,
+ serverCertificate?.RawData,
+ TransportChannel.ServerChannelCertificate,
+ m_instanceCertificate?.RawData,
+ TransportChannel.ClientChannelCertificate,
+ m_clientNonce ?? []);
+
+ userTokenSignature = identityToken.Sign(
+ dataToSign,
+ tokenSecurityPolicyUri);
+ }
+ else
+ {
+ // encrypt token.
+ identityToken.Encrypt(
+ serverCertificate,
+ serverNonce,
+ tokenSecurityPolicyUri,
+ MessageContext,
+ m_eccServerEphemeralKey,
+ m_instanceCertificate,
+ m_instanceCertificateChain,
+ m_endpoint.Description.SecurityMode != MessageSecurityMode.None);
+ }
// copy the preferred locales if provided.
if (preferredLocales != null && preferredLocales.Count > 0)
@@ -1305,15 +1350,18 @@ await m_configuration
m_preferredLocales = [.. preferredLocales];
}
+ var header = CreateRequestHeaderForActivateSession(securityPolicy, tokenSecurityPolicyUri!);
+
// activate session.
ActivateSessionResponse activateResponse = await ActivateSessionAsync(
- null,
+ header,
clientSignature,
[],
m_preferredLocales,
new ExtensionObject(identityToken.Token),
userTokenSignature,
- ct).ConfigureAwait(false);
+ ct)
+ .ConfigureAwait(false);
// process additional header
ProcessResponseAdditionalHeader(activateResponse.ResponseHeader, serverCertificate);
@@ -1342,7 +1390,7 @@ await m_configuration
// save nonces.
m_sessionName = sessionName;
m_identity = identity;
- m_previousServerNonce = previousServerNonce;
+ m_previousServerNonce = m_serverNonce;
m_serverNonce = serverNonce;
m_serverCertificate = serverCertificate;
@@ -1428,12 +1476,20 @@ public async Task UpdateSessionAsync(
// get the identity token.
string securityPolicyUri =
m_endpoint.Description.SecurityPolicyUri ?? SecurityPolicies.None;
+ SecurityPolicyInfo securityPolicy = SecurityPolicies.GetInfo(securityPolicyUri);
// create the client signature.
- byte[] dataToSign = Utils.Append(m_serverCertificate?.RawData, serverNonce);
- SignatureData clientSignature = SecurityPolicies.Sign(
- m_instanceCertificate,
+ byte[] dataToSign = securityPolicy.GetClientSignatureData(
+ TransportChannel.ChannelThumbprint,
+ serverNonce,
+ m_serverCertificate?.RawData,
+ TransportChannel.ServerChannelCertificate,
+ TransportChannel.ClientChannelCertificate,
+ m_clientNonce ?? []);
+
+ SignatureData clientSignature = SecurityPolicies.CreateSignatureData(
securityPolicyUri,
+ m_instanceCertificate,
dataToSign);
// choose a default token.
@@ -1442,20 +1498,30 @@ public async Task UpdateSessionAsync(
// check that the user identity is supported by the endpoint.
UserTokenPolicy identityPolicy =
m_endpoint.Description.FindUserTokenPolicy(
- identity.TokenType,
- identity.IssuedTokenType,
- securityPolicyUri)
- ?? throw ServiceResultException.Create(
- StatusCodes.BadIdentityTokenRejected,
- "Endpoint does not support the user identity type provided.");
+ identity.TokenHandler.Token.PolicyId,
+ securityPolicyUri);
- // select the security policy for the user token.
- string? tokenSecurityPolicyUri = identityPolicy.SecurityPolicyUri;
- if (string.IsNullOrEmpty(tokenSecurityPolicyUri))
+ if (identityPolicy == null)
{
- tokenSecurityPolicyUri = securityPolicyUri;
+ identityPolicy =
+ m_endpoint.Description.FindUserTokenPolicy(
+ identity.TokenType,
+ identity.IssuedTokenType,
+ securityPolicyUri);
+
+ if (identityPolicy == null)
+ {
+ throw ServiceResultException.Create(
+ StatusCodes.BadIdentityTokenRejected,
+ "Endpoint does not support the user identity type provided.");
+ }
}
+ // select the security policy for the user token.
+ string? tokenSecurityPolicyUri = string.IsNullOrEmpty(identityPolicy.SecurityPolicyUri)
+ ? securityPolicyUri
+ : identityPolicy.SecurityPolicyUri;
+
bool requireEncryption = tokenSecurityPolicyUri != SecurityPolicies.None;
// validate the server certificate before encrypting tokens.
@@ -1476,28 +1542,49 @@ await m_configuration.CertificateValidator.ValidateAsync(
m_previousServerNonce,
m_endpoint.Description.SecurityMode);
- // sign data with user token.
- using var identityToken = (IUserIdentityTokenHandler)identity.TokenHandler.Clone();
+ // sign/encrypt with a disposable token handler copy to avoid mutating stored credentials.
+ using IUserIdentityTokenHandler identityToken = identity.TokenHandler.Copy();
identityToken.UpdatePolicy(identityPolicy);
- SignatureData userTokenSignature = identityToken.Sign(
- dataToSign,
- tokenSecurityPolicyUri);
+
+ SignatureData? userTokenSignature = null;
+
+ if (identityToken.Token is X509IdentityToken)
+ {
+ dataToSign = securityPolicy.GetUserTokenSignatureData(
+ TransportChannel.ChannelThumbprint,
+ serverNonce,
+ m_serverCertificate?.RawData,
+ TransportChannel.ServerChannelCertificate,
+ m_instanceCertificate?.RawData,
+ TransportChannel.ClientChannelCertificate,
+ m_clientNonce ?? []);
+
+ userTokenSignature = identityToken.Sign(
+ dataToSign,
+ tokenSecurityPolicyUri);
+ }
+ else
+ {
+ // encrypt token.
+ identityToken.Encrypt(
+ m_serverCertificate,
+ serverNonce,
+ tokenSecurityPolicyUri,
+ MessageContext,
+ m_eccServerEphemeralKey,
+ m_instanceCertificate,
+ m_instanceCertificateChain,
+ m_endpoint.Description.SecurityMode != MessageSecurityMode.None);
+ }
m_userTokenSecurityPolicyUri = tokenSecurityPolicyUri;
- // encrypt token.
- identityToken.Encrypt(
- m_serverCertificate,
- serverNonce,
- m_userTokenSecurityPolicyUri,
- MessageContext,
- m_eccServerEphemeralKey,
- m_instanceCertificate,
- m_instanceCertificateChain,
- m_endpoint.Description.SecurityMode != MessageSecurityMode.None);
+ RequestHeader? requestHeader = CreateRequestHeaderForActivateSession(
+ securityPolicy,
+ tokenSecurityPolicyUri!);
ActivateSessionResponse response = await ActivateSessionAsync(
- null,
+ requestHeader,
clientSignature,
[],
preferredLocales,
@@ -2312,13 +2399,9 @@ public async Task ReconnectAsync(
//
await LoadInstanceCertificateAsync(true, ct).ConfigureAwait(false);
- // create the client signature.
- byte[] dataToSign = Utils.Append(m_serverCertificate?.RawData, m_serverNonce);
+ string securityPolicyUri = m_endpoint.Description.SecurityPolicyUri ?? SecurityPolicies.None;
+ SecurityPolicyInfo securityPolicy = SecurityPolicies.GetInfo(securityPolicyUri);
EndpointDescription endpoint = m_endpoint.Description;
- SignatureData clientSignature = SecurityPolicies.Sign(
- m_instanceCertificate,
- endpoint.SecurityPolicyUri,
- dataToSign);
// check that the user identity is supported by the endpoint.
UserTokenPolicy identityPolicy = endpoint.FindUserTokenPolicy(
@@ -2337,11 +2420,9 @@ public async Task ReconnectAsync(
}
// select the security policy for the user token.
- string? tokenSecurityPolicyUri = identityPolicy.SecurityPolicyUri;
- if (string.IsNullOrEmpty(tokenSecurityPolicyUri))
- {
- tokenSecurityPolicyUri = endpoint.SecurityPolicyUri;
- }
+ string? tokenSecurityPolicyUri = string.IsNullOrEmpty(identityPolicy.SecurityPolicyUri)
+ ? endpoint.SecurityPolicyUri ?? SecurityPolicies.None
+ : identityPolicy.SecurityPolicyUri;
m_userTokenSecurityPolicyUri = tokenSecurityPolicyUri;
// validate server nonce and security parameters for user identity.
@@ -2352,23 +2433,9 @@ public async Task ReconnectAsync(
m_previousServerNonce,
m_endpoint.Description.SecurityMode);
- // sign data with user token.
- using var identityToken = (IUserIdentityTokenHandler)m_identity.TokenHandler.Clone();
+ // sign/encrypt with a disposable token handler copy to avoid mutating stored credentials.
+ using IUserIdentityTokenHandler identityToken = m_identity.TokenHandler.Copy();
identityToken.UpdatePolicy(identityPolicy);
- SignatureData userTokenSignature = identityToken.Sign(
- dataToSign,
- tokenSecurityPolicyUri);
-
- // encrypt token.
- identityToken.Encrypt(
- m_serverCertificate,
- m_serverNonce,
- m_userTokenSecurityPolicyUri,
- MessageContext,
- m_eccServerEphemeralKey,
- m_instanceCertificate,
- m_instanceCertificateChain,
- m_endpoint.Description.SecurityMode != MessageSecurityMode.None);
m_logger.LogInformation("Session REPLACING channel for {SessionId}.", SessionId);
@@ -2434,9 +2501,83 @@ public async Task ReconnectAsync(
}
}
+ ITransportChannel activeChannel = TransportChannel;
+
+ byte[] channelThumbprint = activeChannel.ChannelThumbprint;
+ byte[] serverChannelCertificate = activeChannel.ServerChannelCertificate;
+ byte[] clientChannelCertificate = activeChannel.ClientChannelCertificate;
+
+ // HTTPS channels populate the server TLS certificate lazily after the first request.
+ // Reconnect signs before that, so use the server certificate from the session state.
+ if ((serverChannelCertificate == null || serverChannelCertificate.Length == 0) &&
+ m_serverCertificate != null)
+ {
+ serverChannelCertificate = m_serverCertificate.RawData;
+ }
+
+ if ((clientChannelCertificate == null || clientChannelCertificate.Length == 0) &&
+ m_instanceCertificate != null)
+ {
+ clientChannelCertificate = m_instanceCertificate.RawData;
+ }
+
+ // create the client signature.
+ byte[] dataToSign = securityPolicy.GetClientSignatureData(
+ channelThumbprint,
+ m_serverNonce,
+ m_serverCertificate?.RawData,
+ serverChannelCertificate,
+ clientChannelCertificate,
+ m_clientNonce ?? []);
+
+ SignatureData clientSignature = SecurityPolicies.CreateSignatureData(
+ endpoint.SecurityPolicyUri,
+ m_instanceCertificate,
+ dataToSign);
+
+ dataToSign = securityPolicy.GetUserTokenSignatureData(
+ channelThumbprint,
+ m_serverNonce,
+ m_serverCertificate?.RawData,
+ serverChannelCertificate,
+ m_instanceCertificate?.RawData,
+ clientChannelCertificate,
+ m_clientNonce ?? []);
+
+ SignatureData? userTokenSignature = null;
+
+ if (identityToken.Token is X509IdentityToken)
+ {
+ userTokenSignature = identityToken.Sign(
+ dataToSign,
+ tokenSecurityPolicyUri);
+ }
+ else
+ {
+ // encrypt token.
+ identityToken.Encrypt(
+ m_serverCertificate,
+ m_serverNonce,
+ tokenSecurityPolicyUri,
+ MessageContext,
+ m_eccServerEphemeralKey,
+ m_instanceCertificate,
+ m_instanceCertificateChain,
+ m_endpoint.Description.SecurityMode != MessageSecurityMode.None);
+ }
+
m_logger.LogInformation("Session RE-ACTIVATING {SessionId}.", SessionId);
- var header = new RequestHeader { TimeoutHint = kReconnectTimeout };
+ var header = CreateRequestHeaderForActivateSession(
+ securityPolicy,
+ tokenSecurityPolicyUri!);
+
+ if (header == null)
+ {
+ header = new RequestHeader();
+ }
+
+ header.TimeoutHint = kReconnectTimeout;
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct);
timeout.CancelAfter(TimeSpan.FromMilliseconds(kReconnectTimeout / 2));
@@ -3877,36 +4018,48 @@ private void ValidateServerSignature(
SignatureData serverSignature,
byte[]? clientCertificateData,
byte[]? clientCertificateChainData,
- byte[] clientNonce)
+ byte[]? clientNonce,
+ byte[]? serverNonce)
{
if (serverSignature == null || serverSignature.Signature == null)
{
m_logger.LogInformation("Server signature is null or empty.");
-
- //throw ServiceResultException.Create(
- // StatusCodes.BadSecurityChecksFailed,
- // "Server signature is null or empty.");
+ return;
}
// validate the server's signature.
- byte[] dataToSign = Utils.Append(clientCertificateData, clientNonce);
+ SecurityPolicyInfo securityPolicy = SecurityPolicies.GetInfo(m_endpoint.Description.SecurityPolicyUri);
+
+ byte[] dataToSign = securityPolicy.GetServerSignatureData(
+ TransportChannel.ChannelThumbprint,
+ clientNonce,
+ TransportChannel.ServerChannelCertificate,
+ clientCertificateData,
+ TransportChannel.ClientChannelCertificate,
+ serverNonce);
- if (!SecurityPolicies.Verify(
+ if (!SecurityPolicies.VerifySignatureData(
+ serverSignature,
+ securityPolicy,
serverCertificate,
- m_endpoint.Description.SecurityPolicyUri,
- dataToSign,
- serverSignature))
+ dataToSign))
{
// validate the signature with complete chain if the check with leaf certificate failed.
if (clientCertificateChainData != null)
{
- dataToSign = Utils.Append(clientCertificateChainData, clientNonce);
-
- if (!SecurityPolicies.Verify(
- serverCertificate,
- m_endpoint.Description.SecurityPolicyUri,
- dataToSign,
- serverSignature))
+ dataToSign = securityPolicy.GetServerSignatureData(
+ TransportChannel.ChannelThumbprint,
+ clientNonce,
+ TransportChannel.ServerChannelCertificate,
+ clientCertificateChainData,
+ TransportChannel.ClientChannelCertificate,
+ serverNonce);
+
+ if (!SecurityPolicies.VerifySignatureData(
+ serverSignature,
+ securityPolicy,
+ serverCertificate,
+ dataToSign))
{
throw ServiceResultException.Create(
StatusCodes.BadApplicationSignatureInvalid,
@@ -4168,15 +4321,6 @@ private static void UpdateDescription(
return (result, error);
}
- ///
- /// If available, returns the current nonce or null.
- ///
- private byte[]? GetCurrentTokenServerNonce()
- {
- ChannelToken? currentToken = (NullableTransportChannel as ISecureChannel)?.CurrentToken;
- return currentToken?.ServerNonce;
- }
-
///
/// Processes the response from a publish request.
///
@@ -4768,25 +4912,63 @@ private RequestHeader CreateRequestHeaderPerUserTokenPolicy(
string? endpointSecurityPolicyUri)
{
var requestHeader = new RequestHeader();
- string? userTokenSecurityPolicyUri = identityTokenSecurityPolicyUri;
+ string userTokenSecurityPolicyUri = identityTokenSecurityPolicyUri ?? string.Empty;
if (string.IsNullOrEmpty(userTokenSecurityPolicyUri))
{
- userTokenSecurityPolicyUri = m_endpoint.Description.SecurityPolicyUri;
+ userTokenSecurityPolicyUri = m_endpoint.Description.SecurityPolicyUri ?? SecurityPolicies.None;
}
+
m_userTokenSecurityPolicyUri = userTokenSecurityPolicyUri;
- if (EccUtils.IsEccPolicy(userTokenSecurityPolicyUri))
+ var securityPolicy = SecurityPolicies.GetInfo(userTokenSecurityPolicyUri);
+
+ if (securityPolicy.EphemeralKeyAlgorithm != CertificateKeyAlgorithm.None)
{
var parameters = new AdditionalParametersType();
parameters.Parameters.Add(
new KeyValuePair
{
- Key = QualifiedName.From("ECDHPolicyUri"),
+ Key = QualifiedName.From(AdditionalParameterNames.ECDHPolicyUri),
Value = userTokenSecurityPolicyUri
});
requestHeader.AdditionalHeader = new ExtensionObject(parameters);
+
+ m_logger.LogWarning("Request EphemeralKey for {Policy}.", userTokenSecurityPolicyUri);
+ }
+
+ return requestHeader;
+ }
+
+ private RequestHeader? CreateRequestHeaderForActivateSession(
+ SecurityPolicyInfo securityPolicy,
+ string userTokenSecurityPolicyUri)
+ {
+ var requestHeader = new RequestHeader();
+ var parameters = new AdditionalParametersType();
+
+ if (!string.IsNullOrEmpty(userTokenSecurityPolicyUri))
+ {
+ var userTokenSecurityPolicy = SecurityPolicies.GetInfo(userTokenSecurityPolicyUri);
+
+ if (userTokenSecurityPolicy.EphemeralKeyAlgorithm != CertificateKeyAlgorithm.None)
+ {
+ parameters.Parameters.Add(
+ new KeyValuePair
+ {
+ Key = QualifiedName.From(AdditionalParameterNames.ECDHPolicyUri),
+ Value = userTokenSecurityPolicyUri
+ });
+
+ m_logger.LogWarning("Requesting new EphmeralKey using {SecurityPolicyUri}.", userTokenSecurityPolicyUri);
+ }
+ }
+
+ if (parameters.Parameters.Count == 0)
+ {
+ return null;
}
+ requestHeader.AdditionalHeader = new ExtensionObject(parameters);
return requestHeader;
}
@@ -4812,7 +4994,30 @@ protected virtual void ProcessResponseAdditionalHeader(
{
foreach (KeyValuePair ii in parameters.Parameters)
{
- if (ii.Key == "ECDHKey")
+ if (ii.Key == AdditionalParameterNames.Padding)
+ {
+ var padding = ii.Value.Value as byte[];
+
+ if (ii.Value.TypeInfo != TypeInfo.Scalars.ByteString || padding == null)
+ {
+ m_logger.LogWarning(
+ "Server returned invalid message padding. Ignored.");
+ }
+ else if (padding.Length > 128)
+ {
+ m_logger.LogWarning(
+ "Server returned a {Size}byte message padding that is too long. Ignored.",
+ padding.Length);
+ }
+ else
+ {
+ m_logger.LogWarning("Ignoring Padding with {Length} Bytes", padding.Length);
+ }
+
+ continue;
+ }
+
+ if (ii.Key == AdditionalParameterNames.ECDHKey)
{
if (ii.Value.TypeInfo == TypeInfo.Scalars.StatusCode)
{
@@ -4828,8 +5033,15 @@ protected virtual void ProcessResponseAdditionalHeader(
"Server did not provide a valid ECDHKey. User authentication not possible.");
}
- if (!EccUtils.Verify(
- new ArraySegment(key.PublicKey ?? []),
+ if (key.PublicKey == null || key.Signature == null)
+ {
+ throw new ServiceResultException(
+ StatusCodes.BadDecodingError,
+ "Server did not provide a valid ECDHKey. User authentication not possible.");
+ }
+
+ if (!CryptoUtils.Verify(
+ new ArraySegment(key.PublicKey),
key.Signature,
serverCertificate,
m_userTokenSecurityPolicyUri))
@@ -4840,8 +5052,10 @@ protected virtual void ProcessResponseAdditionalHeader(
}
m_eccServerEphemeralKey = Nonce.CreateNonce(
- m_userTokenSecurityPolicyUri,
+ SecurityPolicies.GetInfo(m_userTokenSecurityPolicyUri),
key.PublicKey);
+
+ m_logger.LogWarning("Updating ServerEphemeralKey: {Key} bytes", m_eccServerEphemeralKey.Data?.Length ?? 0);
}
}
}
@@ -4923,6 +5137,7 @@ protected virtual void ProcessResponseAdditionalHeader(
private readonly NodeCache m_nodeCache;
private readonly List m_identityHistory = [];
private byte[]? m_serverNonce;
+ private byte[]? m_clientNonce;
private byte[]? m_previousServerNonce;
private X509Certificate2? m_serverCertificate;
private uint m_publishCounter;
diff --git a/Libraries/Opc.Ua.Client/Session/SessionConfiguration.cs b/Libraries/Opc.Ua.Client/Session/SessionConfiguration.cs
index 69357e3708..bcf0fb779c 100644
--- a/Libraries/Opc.Ua.Client/Session/SessionConfiguration.cs
+++ b/Libraries/Opc.Ua.Client/Session/SessionConfiguration.cs
@@ -123,10 +123,18 @@ public SessionState(SessionOptions options)
public NodeId AuthenticationToken { get; init; }
///
- /// The last server nonce received.
+ /// The raw bytes of the last server nonce received.
+ /// Persisting bytes avoids object-serialization ambiguity for Nonce internals.
///
[DataMember(IsRequired = true, Order = 80)]
- public Nonce? ServerNonce { get; init; }
+ public byte[]? ServerNonce { get; init; }
+
+ ///
+ /// The raw bytes of the client nonce used when the session was created.
+ /// Required for enhanced-policy activate signatures during reconnect.
+ ///
+ [DataMember(IsRequired = false, Order = 85)]
+ public byte[]? ClientNonce { get; init; }
///
/// The user identity token policy which was used to create the session.
@@ -135,10 +143,10 @@ public SessionState(SessionOptions options)
public string? UserIdentityTokenPolicy { get; init; }
///
- /// The last server ecc ephemeral key received.
+ /// The raw bytes of the last server ECC ephemeral key received.
///
[DataMember(IsRequired = false, Order = 100)]
- public Nonce? ServerEccEphemeralKey { get; init; }
+ public byte[]? ServerEccEphemeralKey { get; init; }
///
/// Allows the list of subscriptions to be saved/restored
diff --git a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs
index 667c3073e2..2d3fef8d20 100644
--- a/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs
+++ b/Libraries/Opc.Ua.Configuration/ApplicationInstance.cs
@@ -868,8 +868,10 @@ await DeleteApplicationInstanceCertificateAsync(configuration, id, ct).Configure
else
{
ECCurve? curve =
- EccUtils.GetCurveFromCertificateTypeId(id.CertificateType)
- ?? throw ServiceResultException.ConfigurationError("The Ecc certificate type is not supported.");
+ CryptoUtils.GetCurveFromCertificateTypeId(id.CertificateType)
+ ?? throw new ServiceResultException(
+ StatusCodes.BadConfigurationError,
+ "The Ecc certificate type is not supported.");
id.Certificate = builder.SetECCurve(curve.Value).CreateForECDsa();
diff --git a/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs b/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs
index 761155fdd5..6d788f01e4 100644
--- a/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs
+++ b/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs
@@ -756,7 +756,7 @@ private static bool TryGetECCCurve(NodeId certificateType, out ECCurve curve)
return false;
}
curve =
- EccUtils.GetCurveFromCertificateTypeId(certificateType)
+ CryptoUtils.GetCurveFromCertificateTypeId(certificateType)
?? throw new ServiceResultException(
StatusCodes.BadNotSupported,
$"The certificate type {certificateType} is not supported.");
diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs
index 55d18f0490..25aff8fbba 100644
--- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs
+++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs
@@ -392,6 +392,7 @@ public void HasApplicationSecureAdminAccess(ISystemContext context)
}
///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1725:Parameter names should match base declaration", Justification = "")]
public void HasApplicationSecureAdminAccess(
ISystemContext context,
CertificateStoreIdentifier trustedStore)
@@ -950,7 +951,7 @@ private X509Certificate2 GenerateTemporaryApplicationCertificate(
else
{
ECCurve? curve =
- EccUtils.GetCurveFromCertificateTypeId(certificateTypeId)
+ CryptoUtils.GetCurveFromCertificateTypeId(certificateTypeId)
?? throw new ServiceResultException(
StatusCodes.BadNotSupported,
"The Ecc certificate type is not supported.");
diff --git a/Libraries/Opc.Ua.Server/Server/StandardServer.cs b/Libraries/Opc.Ua.Server/Server/StandardServer.cs
index 5c741d5b59..a6d172371e 100644
--- a/Libraries/Opc.Ua.Server/Server/StandardServer.cs
+++ b/Libraries/Opc.Ua.Server/Server/StandardServer.cs
@@ -462,14 +462,9 @@ X509Certificate2Collection clientCertificateChain
}
}
- var parameters =
- ExtensionObject.ToEncodeable(
- requestHeader.AdditionalHeader) as AdditionalParametersType;
-
- if (parameters != null)
- {
- parameters = CreateSessionProcessAdditionalParameters(session, parameters);
- }
+ AdditionalParametersType parameters = CreateSessionProcessAdditionalParameters(
+ session,
+ requestHeader.AdditionalHeader);
await m_semaphoreSlim.WaitAsync(ct).ConfigureAwait(false);
try
@@ -494,17 +489,12 @@ X509Certificate2Collection clientCertificateChain
serverEndpoints = GetEndpointDescriptions(endpointUrl, BaseAddresses, null);
// sign the nonce provided by the client.
- serverSignature = null;
-
- // sign the client nonce (if provided).
- if (parsedClientCertificate != null && clientNonce != null)
- {
- byte[] dataToSign = Utils.Append(parsedClientCertificate.RawData, clientNonce);
- serverSignature = SecurityPolicies.Sign(
- instanceCertificate,
- context.SecurityPolicyUri,
- dataToSign);
- }
+ serverSignature = CreateSessionServerSignature(
+ context,
+ instanceCertificate,
+ parsedClientCertificate,
+ clientNonce,
+ serverNonce);
}
finally
{
@@ -584,7 +574,47 @@ X509Certificate2Collection clientCertificateChain
}
///
- /// Process additional parameters during the ECC session creation and set the session's UserToken security policy
+ /// Creates the server signature returned during CreateSession.
+ ///
+ /// The request context.
+ /// The instance certificate used to sign data.
+ /// The client certificate supplied in the request.
+ /// The client nonce supplied in the request.
+ /// The server nonce generated for the response.
+ /// The server signature or null when signing is not required.
+ protected virtual SignatureData CreateSessionServerSignature(
+ OperationContext context,
+ X509Certificate2 instanceCertificate,
+ X509Certificate2 parsedClientCertificate,
+ byte[] clientNonce,
+ byte[] serverNonce)
+ {
+ return SessionSecurityPolicyHelper.CreateServerSignature(
+ context,
+ instanceCertificate,
+ parsedClientCertificate,
+ clientNonce,
+ serverNonce);
+ }
+
+ ///
+ /// Process additional header values during session creation for ephemeral-key user token policies.
+ ///
+ /// The session.
+ /// The additional request header.
+ /// An AdditionalParametersType object containing the processed parameters.
+ protected virtual AdditionalParametersType CreateSessionProcessAdditionalParameters(
+ ISession session,
+ ExtensionObject additionalHeader)
+ {
+ AdditionalParametersType parameters = SessionSecurityPolicyHelper
+ .DecodeAdditionalParameters(additionalHeader);
+
+ return CreateSessionProcessAdditionalParameters(session, parameters);
+ }
+
+ ///
+ /// Process additional parameters during session creation and set the session's user token security policy.
///
/// The session
/// The additional parameters for the session
@@ -593,47 +623,30 @@ protected virtual AdditionalParametersType CreateSessionProcessAdditionalParamet
ISession session,
AdditionalParametersType parameters)
{
- AdditionalParametersType response = null;
-
- if (parameters != null && parameters.Parameters != null)
- {
- response = new AdditionalParametersType();
-
- foreach (KeyValuePair ii in parameters.Parameters)
- {
- if (ii.Key == "ECDHPolicyUri")
- {
- string policyUri = ii.Value.ToString();
+ return SessionSecurityPolicyHelper.ProcessCreateSessionAdditionalParameters(
+ session,
+ parameters,
+ m_logger);
+ }
- if (EccUtils.IsEccPolicy(policyUri))
- {
- session.SetEccUserTokenSecurityPolicy(policyUri);
- EphemeralKeyType key = session.GetNewEccKey();
- response.Parameters.Add(
- new KeyValuePair
- {
- Key = QualifiedName.From("ECDHKey"),
- Value = new ExtensionObject(key)
- });
- }
- else
- {
- response.Parameters.Add(
- new KeyValuePair
- {
- Key = QualifiedName.From("ECDHKey"),
- Value = StatusCodes.BadSecurityPolicyRejected
- });
- }
- }
- }
- }
+ ///
+ /// Process additional header values during session activation for ephemeral-key user token policies.
+ ///
+ /// The session.
+ /// The additional request header.
+ /// An AdditionalParametersType object containing the processed parameters.
+ protected virtual AdditionalParametersType ActivateSessionProcessAdditionalParameters(
+ ISession session,
+ ExtensionObject additionalHeader)
+ {
+ AdditionalParametersType parameters = SessionSecurityPolicyHelper
+ .DecodeAdditionalParameters(additionalHeader);
- return response;
+ return ActivateSessionProcessAdditionalParameters(session, parameters);
}
///
- /// Process additional parameters during ECC session activation
+ /// Process additional parameters during session activation for ephemeral-key user token policies.
///
/// The session
/// The additional parameters for the session
@@ -642,21 +655,10 @@ protected virtual AdditionalParametersType ActivateSessionProcessAdditionalParam
ISession session,
AdditionalParametersType parameters)
{
- AdditionalParametersType response = null;
-
- EphemeralKeyType key = session.GetNewEccKey();
-
- if (key != null)
- {
- response = new AdditionalParametersType();
- response.Parameters.Add(new KeyValuePair
- {
- Key = QualifiedName.From("ECDHKey"),
- Value = new ExtensionObject(key)
- });
- }
-
- return response;
+ return SessionSecurityPolicyHelper.ProcessActivateSessionAdditionalParameters(
+ session,
+ parameters,
+ m_logger);
}
///
@@ -696,10 +698,10 @@ public override async ValueTask ActivateSessionAsync(
ISession session = ServerInternal.SessionManager
.GetSession(requestHeader.AuthenticationToken);
- var parameters =
- ExtensionObject.ToEncodeable(
- requestHeader.AdditionalHeader) as AdditionalParametersType;
- parameters = ActivateSessionProcessAdditionalParameters(session, parameters);
+
+ AdditionalParametersType parameters = ActivateSessionProcessAdditionalParameters(
+ session,
+ requestHeader.AdditionalHeader);
m_logger.LogInformation("Server - SESSION ACTIVATED.");
@@ -2119,109 +2121,109 @@ await configuration
if (m_registrationEndpoints != null)
{
foreach (ConfiguredEndpoint endpoint in m_registrationEndpoints.Endpoints)
- {
- RegistrationClient client = null;
- int i = 0;
-
- while (i++ < 2)
{
- try
- {
- // update from the server.
- bool updateRequired = true;
+ RegistrationClient client = null;
+ int i = 0;
- lock (m_registrationLock)
+ while (i++ < 2)
+ {
+ try
{
- updateRequired = endpoint.UpdateBeforeConnect;
- }
+ // update from the server.
+ bool updateRequired = true;
- if (updateRequired)
- {
- await endpoint.UpdateFromServerAsync(MessageContext.Telemetry, ct).ConfigureAwait(false);
- }
+ lock (m_registrationLock)
+ {
+ updateRequired = endpoint.UpdateBeforeConnect;
+ }
- lock (m_registrationLock)
- {
- endpoint.UpdateBeforeConnect = false;
- }
+ if (updateRequired)
+ {
+ await endpoint.UpdateFromServerAsync(MessageContext.Telemetry, ct).ConfigureAwait(false);
+ }
- var requestHeader = new RequestHeader
- {
- Timestamp = DateTime.UtcNow
- };
-
- // create the client.
- X509Certificate2 instanceCertificate =
- InstanceCertificateTypesProvider.GetInstanceCertificate(
- endpoint.Description?.SecurityPolicyUri ??
- SecurityPolicies.None);
- client = await RegistrationClient.CreateAsync(
- configuration,
- endpoint.Description,
- endpoint.Configuration,
- instanceCertificate,
- ct: ct).ConfigureAwait(false);
-
- client.OperationTimeout = 10000;
-
- // register the server.
- if (m_useRegisterServer2)
- {
- var discoveryConfiguration = new ExtensionObjectCollection();
- var mdnsDiscoveryConfig = new MdnsDiscoveryConfiguration
+ lock (m_registrationLock)
{
- ServerCapabilities = configuration.ServerConfiguration
- .ServerCapabilities,
- MdnsServerName = Utils.GetHostName()
+ endpoint.UpdateBeforeConnect = false;
+ }
+
+ var requestHeader = new RequestHeader
+ {
+ Timestamp = DateTime.UtcNow
};
- var extensionObject = new ExtensionObject(mdnsDiscoveryConfig);
- discoveryConfiguration.Add(extensionObject);
- await client.RegisterServer2Async(
- requestHeader,
- m_registrationInfo,
- discoveryConfiguration,
- ct).ConfigureAwait(false);
+
+ // create the client.
+ X509Certificate2 instanceCertificate =
+ InstanceCertificateTypesProvider.GetInstanceCertificate(
+ endpoint.Description?.SecurityPolicyUri ??
+ SecurityPolicies.None);
+ client = await RegistrationClient.CreateAsync(
+ configuration,
+ endpoint.Description,
+ endpoint.Configuration,
+ instanceCertificate,
+ ct: ct).ConfigureAwait(false);
+
+ client.OperationTimeout = 10000;
+
+ // register the server.
+ if (m_useRegisterServer2)
+ {
+ var discoveryConfiguration = new ExtensionObjectCollection();
+ var mdnsDiscoveryConfig = new MdnsDiscoveryConfiguration
+ {
+ ServerCapabilities = configuration.ServerConfiguration
+ .ServerCapabilities,
+ MdnsServerName = Utils.GetHostName()
+ };
+ var extensionObject = new ExtensionObject(mdnsDiscoveryConfig);
+ discoveryConfiguration.Add(extensionObject);
+ await client.RegisterServer2Async(
+ requestHeader,
+ m_registrationInfo,
+ discoveryConfiguration,
+ ct).ConfigureAwait(false);
+ }
+ else
+ {
+ await client.RegisterServerAsync(
+ requestHeader,
+ m_registrationInfo,
+ ct)
+ .ConfigureAwait(false);
+ }
+
+ m_registeredWithDiscoveryServer = m_registrationInfo.IsOnline;
+ return true;
}
- else
+ catch (Exception e)
{
- await client.RegisterServerAsync(
- requestHeader,
- m_registrationInfo,
- ct)
- .ConfigureAwait(false);
+ m_logger.LogWarning(
+ "RegisterServer{Api} failed for {EndpointUrl}. Exception={ErrorMessage}",
+ m_useRegisterServer2 ? "2" : string.Empty,
+ endpoint.EndpointUrl,
+ e.Message);
+ m_useRegisterServer2 = !m_useRegisterServer2;
}
-
- m_registeredWithDiscoveryServer = m_registrationInfo.IsOnline;
- return true;
- }
- catch (Exception e)
- {
- m_logger.LogWarning(
- "RegisterServer{Api} failed for {EndpointUrl}. Exception={ErrorMessage}",
- m_useRegisterServer2 ? "2" : string.Empty,
- endpoint.EndpointUrl,
- e.Message);
- m_useRegisterServer2 = !m_useRegisterServer2;
- }
- finally
- {
- if (client != null)
+ finally
{
- try
+ if (client != null)
{
- await client.CloseAsync(ct).ConfigureAwait(false);
- client = null;
- }
- catch (Exception e)
- {
- m_logger.LogWarning(
- "Could not cleanly close connection with LDS. Exception={ErrorMessage}",
- e.Message);
+ try
+ {
+ await client.CloseAsync(ct).ConfigureAwait(false);
+ client = null;
+ }
+ catch (Exception e)
+ {
+ m_logger.LogWarning(
+ "Could not cleanly close connection with LDS. Exception={ErrorMessage}",
+ e.Message);
+ }
}
}
}
}
- }
// retry to start with RegisterServer2 if both failed
m_useRegisterServer2 = true;
}
diff --git a/Libraries/Opc.Ua.Server/Session/ISession.cs b/Libraries/Opc.Ua.Server/Session/ISession.cs
index b8a0bd07f1..91d47cbd7d 100644
--- a/Libraries/Opc.Ua.Server/Session/ISession.cs
+++ b/Libraries/Opc.Ua.Server/Session/ISession.cs
@@ -42,6 +42,11 @@ public interface ISession : IDisposable
///
bool Activated { get; }
+ ///
+ /// The server application instance certificate used by this session.
+ ///
+ X509Certificate2 ServerCertificate { get; }
+
///
/// The application instance certificate associated with the client.
///
@@ -127,7 +132,7 @@ bool Activate(
/// Create new ECC ephemeral key
///
/// A new ephemeral key
- EphemeralKeyType GetNewEccKey();
+ EphemeralKeyType GetNewEphemeralKey();
///
/// Checks if the secure channel is currently valid.
@@ -171,7 +176,7 @@ bool Activate(
///
/// Set the ECC security policy URI
///
- void SetEccUserTokenSecurityPolicy(string securityPolicyUri);
+ void SetUserTokenSecurityPolicy(string securityPolicyUri);
///
/// Updates the requested locale ids.
diff --git a/Libraries/Opc.Ua.Server/Session/Session.cs b/Libraries/Opc.Ua.Server/Session/Session.cs
index 51e0e3576a..d7d48a5313 100644
--- a/Libraries/Opc.Ua.Server/Session/Session.cs
+++ b/Libraries/Opc.Ua.Server/Session/Session.cs
@@ -95,6 +95,7 @@ public Session(
m_clientIssuerCertificates = clientCertificateChain;
SecureChannelId = context.ChannelContext.SecureChannelId;
+ m_channelThumbprint = context.ChannelContext.ChannelThumbprint;
MaxBrowseContinuationPoints = maxBrowseContinuationPoints;
m_maxHistoryContinuationPoints = maxHistoryContinuationPoints;
EndpointDescription = context.ChannelContext.EndpointDescription;
@@ -241,6 +242,11 @@ protected virtual void Dispose(bool disposing)
///
public byte[] ClientNonce { get; }
+ ///
+ /// The server application instance certificate used by this session.
+ ///
+ public X509Certificate2 ServerCertificate => m_serverCertificate;
+
///
/// The application instance certificate associated with the client.
///
@@ -289,12 +295,12 @@ public DateTime ClientLastContactTime
///
/// Set the ECC security policy URI
///
- public virtual void SetEccUserTokenSecurityPolicy(string securityPolicyUri)
+ public virtual void SetUserTokenSecurityPolicy(string securityPolicyUri)
{
lock (m_lock)
{
- m_eccUserTokenSecurityPolicyUri = securityPolicyUri;
- m_eccUserTokenNonce = null;
+ m_userTokenSecurityPolicyUri = securityPolicyUri;
+ m_userTokenNonce = null;
}
}
@@ -302,23 +308,23 @@ public virtual void SetEccUserTokenSecurityPolicy(string securityPolicyUri)
/// Create new ECC ephemeral key
///
/// A new ephemeral key
- public virtual EphemeralKeyType GetNewEccKey()
+ public virtual EphemeralKeyType GetNewEphemeralKey()
{
lock (m_lock)
{
- if (m_eccUserTokenSecurityPolicyUri == null)
+ if (m_userTokenSecurityPolicyUri == null)
{
return null;
}
- m_eccUserTokenNonce = Nonce.CreateNonce(m_eccUserTokenSecurityPolicyUri);
+ m_userTokenNonce = Nonce.CreateNonce(m_userTokenSecurityPolicyUri);
- var key = new EphemeralKeyType { PublicKey = m_eccUserTokenNonce.Data };
+ var key = new EphemeralKeyType { PublicKey = m_userTokenNonce.Data };
- key.Signature = EccUtils.Sign(
+ key.Signature = CryptoUtils.Sign(
new ArraySegment(key.PublicKey),
m_serverCertificate,
- m_eccUserTokenSecurityPolicyUri);
+ m_userTokenSecurityPolicyUri);
return key;
}
@@ -474,15 +480,21 @@ public void ValidateBeforeActivate(
StatusCodes.BadApplicationSignatureInvalid);
}
- byte[] dataToSign = Utils.Append(
+ var securityPolicy = SecurityPolicies.GetInfo(EndpointDescription.SecurityPolicyUri);
+
+ byte[] dataToSign = securityPolicy.GetClientSignatureData(
+ context.ChannelContext.ChannelThumbprint,
+ m_serverNonce.Data,
m_serverCertificate.RawData,
- m_serverNonce.Data);
+ context.ChannelContext.ServerChannelCertificate,
+ context.ChannelContext.ClientChannelCertificate,
+ ClientNonce);
- if (!SecurityPolicies.Verify(
- ClientCertificate,
+ if (!SecurityPolicies.VerifySignatureData(
+ clientSignature,
EndpointDescription.SecurityPolicyUri,
- dataToSign,
- clientSignature))
+ ClientCertificate,
+ dataToSign))
{
// verify for certificate chain in endpoint.
// validate the signature with complete chain if the check with leaf certificate failed.
@@ -503,15 +515,19 @@ public void ValidateBeforeActivate(
byte[] serverCertificateChainData = [.. serverCertificateChainList];
- dataToSign = Utils.Append(
+ dataToSign = securityPolicy.GetClientSignatureData(
+ context.ChannelContext.ChannelThumbprint,
+ m_serverNonce.Data,
serverCertificateChainData,
- m_serverNonce.Data);
-
- if (!SecurityPolicies.Verify(
- ClientCertificate,
- EndpointDescription.SecurityPolicyUri,
- dataToSign,
- clientSignature))
+ context.ChannelContext.ServerChannelCertificate,
+ context.ChannelContext.ClientChannelCertificate,
+ ClientNonce);
+
+ if (!SecurityPolicies.VerifySignatureData(
+ clientSignature,
+ EndpointDescription.SecurityPolicyUri,
+ ClientCertificate,
+ dataToSign))
{
throw new ServiceResultException(
StatusCodes.BadApplicationSignatureInvalid);
@@ -536,12 +552,14 @@ public void ValidateBeforeActivate(
// validate the user identity token.
identityToken = ValidateUserIdentityToken(
+ context,
userIdentityToken,
userTokenSignature,
out userTokenPolicy);
TraceState("VALIDATED");
}
+
}
///
@@ -825,6 +843,7 @@ private ServiceResult OnUpdateSecurityDiagnostics(
///
///
private IUserIdentityTokenHandler ValidateUserIdentityToken(
+ OperationContext context,
ExtensionObject identityToken,
SignatureData userTokenSignature,
out UserTokenPolicy policy)
@@ -997,7 +1016,7 @@ private IUserIdentityTokenHandler ValidateUserIdentityToken(
m_serverNonce,
securityPolicyUri,
m_server.MessageContext,
- m_eccUserTokenNonce,
+ m_userTokenNonce,
ClientCertificate,
m_clientIssuerCertificates);
}
@@ -1012,9 +1031,16 @@ private IUserIdentityTokenHandler ValidateUserIdentityToken(
// verify the signature.
if (securityPolicyUri != SecurityPolicies.None)
{
- byte[] dataToSign = Utils.Append(
+ var securityPolicy = SecurityPolicies.GetInfo(securityPolicyUri);
+
+ byte[] dataToSign = securityPolicy.GetUserTokenSignatureData(
+ context.ChannelContext.ChannelThumbprint,
+ m_serverNonce.Data,
m_serverCertificate.RawData,
- m_serverNonce.Data);
+ context.ChannelContext.ServerChannelCertificate,
+ ClientCertificate?.RawData,
+ context.ChannelContext.ClientChannelCertificate,
+ ClientNonce ?? []);
if (!token.Verify(dataToSign, userTokenSignature, securityPolicyUri))
{
@@ -1256,8 +1282,9 @@ private void UpdateDiagnosticCounters(
private readonly string m_sessionName;
private X509Certificate2 m_serverCertificate;
private Nonce m_serverNonce;
- private string m_eccUserTokenSecurityPolicyUri;
- private Nonce m_eccUserTokenNonce;
+ private byte[] m_channelThumbprint;
+ private string m_userTokenSecurityPolicyUri;
+ private Nonce m_userTokenNonce;
private readonly X509Certificate2Collection m_clientIssuerCertificates;
private readonly int m_maxHistoryContinuationPoints;
private readonly SessionSecurityDiagnosticsDataType m_securityDiagnostics;
diff --git a/Libraries/Opc.Ua.Server/Session/SessionManager.cs b/Libraries/Opc.Ua.Server/Session/SessionManager.cs
index 2f89d780c2..030b294bbb 100644
--- a/Libraries/Opc.Ua.Server/Session/SessionManager.cs
+++ b/Libraries/Opc.Ua.Server/Session/SessionManager.cs
@@ -220,8 +220,7 @@ public virtual async ValueTask CreateSessionAsync(
}
// create server nonce.
- var serverNonceObject = Nonce.CreateNonce(
- context.ChannelContext.EndpointDescription.SecurityPolicyUri);
+ var serverNonceObject = Nonce.CreateNonce(0);
// assign client name.
if (string.IsNullOrEmpty(sessionName))
diff --git a/Libraries/Opc.Ua.Server/Session/SessionSecurityPolicyHelper.cs b/Libraries/Opc.Ua.Server/Session/SessionSecurityPolicyHelper.cs
new file mode 100644
index 0000000000..ab97e323aa
--- /dev/null
+++ b/Libraries/Opc.Ua.Server/Session/SessionSecurityPolicyHelper.cs
@@ -0,0 +1,161 @@
+/* ========================================================================
+ * 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.Security.Cryptography.X509Certificates;
+using Microsoft.Extensions.Logging;
+
+namespace Opc.Ua.Server
+{
+ ///
+ /// Encapsulates session service security-policy specific processing.
+ ///
+ internal static class SessionSecurityPolicyHelper
+ {
+ ///
+ /// Decodes additional request parameters from an additional header.
+ ///
+ public static AdditionalParametersType DecodeAdditionalParameters(
+ ExtensionObject additionalHeader)
+ {
+ return ExtensionObject.ToEncodeable(additionalHeader) as AdditionalParametersType;
+ }
+
+ ///
+ /// Creates the signature returned by CreateSession.
+ ///
+ public static SignatureData CreateServerSignature(
+ OperationContext context,
+ X509Certificate2 instanceCertificate,
+ X509Certificate2 parsedClientCertificate,
+ byte[] clientNonce,
+ byte[] serverNonce)
+ {
+ if (parsedClientCertificate == null || clientNonce == null)
+ {
+ return null;
+ }
+
+ SecurityPolicyInfo securityPolicy = SecurityPolicies.GetInfo(context.SecurityPolicyUri);
+
+ byte[] dataToSign = securityPolicy.GetServerSignatureData(
+ context.ChannelContext.ChannelThumbprint,
+ clientNonce,
+ context.ChannelContext.ServerChannelCertificate,
+ parsedClientCertificate.RawData,
+ context.ChannelContext.ClientChannelCertificate,
+ serverNonce);
+
+ return SecurityPolicies.CreateSignatureData(
+ context.SecurityPolicyUri,
+ instanceCertificate,
+ dataToSign);
+ }
+
+ ///
+ /// Processes additional request parameters during CreateSession.
+ ///
+ public static AdditionalParametersType ProcessCreateSessionAdditionalParameters(
+ ISession session,
+ AdditionalParametersType parameters,
+ ILogger logger)
+ {
+ AdditionalParametersType response = null;
+
+ if (parameters != null && parameters.Parameters != null)
+ {
+ response = new AdditionalParametersType();
+
+ foreach (KeyValuePair ii in parameters.Parameters)
+ {
+ if (ii.Key == AdditionalParameterNames.ECDHPolicyUri)
+ {
+ string policyUri = ii.Value.ToString();
+ logger.LogWarning("Received request for new EphmeralKey using {SecurityPolicyUri}.", policyUri);
+
+ SecurityPolicyInfo securityPolicy = SecurityPolicies.GetInfo(policyUri);
+
+ if (securityPolicy.EphemeralKeyAlgorithm != CertificateKeyAlgorithm.None)
+ {
+ session.SetUserTokenSecurityPolicy(policyUri);
+ EphemeralKeyType key = session.GetNewEphemeralKey();
+ response.Parameters.Add(
+ new KeyValuePair
+ {
+ Key = QualifiedName.From(AdditionalParameterNames.ECDHKey),
+ Value = new ExtensionObject(key)
+ });
+
+ logger.LogWarning("Returning new EphemeralKey: {PublicKey} bytes.", key.PublicKey?.Length ?? 0);
+ }
+ else
+ {
+ response.Parameters.Add(
+ new KeyValuePair
+ {
+ Key = QualifiedName.From(AdditionalParameterNames.ECDHKey),
+ Value = StatusCodes.BadSecurityPolicyRejected
+ });
+
+ logger.LogWarning("Rejecting request for new EphemeralKey using {SecurityPolicyUri}.", policyUri);
+ }
+ }
+ }
+ }
+
+ return response;
+ }
+
+ ///
+ /// Processes additional request parameters during ActivateSession.
+ ///
+ public static AdditionalParametersType ProcessActivateSessionAdditionalParameters(
+ ISession session,
+ AdditionalParametersType parameters,
+ ILogger logger)
+ {
+ AdditionalParametersType response = null;
+ EphemeralKeyType key = session.GetNewEphemeralKey();
+
+ if (key != null)
+ {
+ response = new AdditionalParametersType();
+ response.Parameters.Add(
+ new KeyValuePair
+ {
+ Key = QualifiedName.From(AdditionalParameterNames.ECDHKey),
+ Value = new ExtensionObject(key)
+ });
+
+ logger.LogWarning("Returning new EphemeralKey: {PublicKey} bytes.", key.PublicKey?.Length ?? 0);
+ }
+
+ return response;
+ }
+ }
+}
diff --git a/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs b/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs
index 253b809cca..5ad1116bbe 100644
--- a/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs
+++ b/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs
@@ -172,6 +172,8 @@ protected virtual void Dispose(bool disposing)
///
public string ListenerId { get; private set; }
+ internal byte[] ServerChannelCertificate { get; set; }
+
///
/// Opens the listener and starts accepting connection.
///
@@ -302,6 +304,8 @@ private void ConfigureWebHost(IWebHostBuilder webHostBuilder)
m_logger.LogTrace("Copy of the private key for https was denied: {Message}", ce.Message);
}
#endif
+ // save the server certificate so it can be used in the secure channel context.
+ ServerChannelCertificate = serverCertificate.RawData;
var httpsOptions = new HttpsConnectionAdapterOptions
{
@@ -484,10 +488,13 @@ await WriteServiceResponseAsync(context, serviceResponse, ct)
return;
}
}
+
var secureChannelContext = new SecureChannelContext(
- ListenerId,
- endpoint,
- RequestEncoding.Binary);
+ ListenerId,
+ endpoint,
+ RequestEncoding.Binary,
+ context.Connection.ClientCertificate?.RawData,
+ ServerChannelCertificate);
IServiceResponse output =
await m_callback.ProcessRequestAsync(
diff --git a/Stack/Opc.Ua.Core/Schema/SecuredApplicationHelpers.cs b/Stack/Opc.Ua.Core/Schema/SecuredApplicationHelpers.cs
index 28ef021c26..61a5fe3756 100644
--- a/Stack/Opc.Ua.Core/Schema/SecuredApplicationHelpers.cs
+++ b/Stack/Opc.Ua.Core/Schema/SecuredApplicationHelpers.cs
@@ -374,8 +374,17 @@ public static byte CalculateSecurityLevel(
result = 2;
break;
case SecurityPolicies.Basic256:
- logger.LogWarning(
- "Deprecated Security Policy Basic256 requested - Not rcommended.");
+ logger.LogWarning("Deprecated Security Policy Basic256 requested - Not recommended.");
+ result = 4;
+ break;
+ case SecurityPolicies.ECC_nistP256:
+ case SecurityPolicies.ECC_nistP384:
+ logger.LogWarning("Deprecated Security Policy {PolicyUri} requested - Use ECC_nistP[256/384]_AES.", policyUri);
+ result = 4;
+ break;
+ case SecurityPolicies.ECC_brainpoolP256r1:
+ case SecurityPolicies.ECC_brainpoolP384r1:
+ logger.LogWarning("Deprecated Security Policy {PolicyUri} requested - Use ECC_brainpoolP[256/384]r1_AES.", policyUri);
result = 4;
break;
case SecurityPolicies.Basic256Sha256:
@@ -387,16 +396,20 @@ public static byte CalculateSecurityLevel(
case SecurityPolicies.Aes256_Sha256_RsaPss:
result = 10;
break;
- case SecurityPolicies.ECC_brainpoolP256r1:
- result = 11;
- break;
- case SecurityPolicies.ECC_nistP256:
+ case SecurityPolicies.RSA_DH_AesGcm:
+ case SecurityPolicies.RSA_DH_ChaChaPoly:
result = 12;
break;
- case SecurityPolicies.ECC_brainpoolP384r1:
- result = 13;
+ case SecurityPolicies.ECC_brainpoolP256r1_AesGcm:
+ case SecurityPolicies.ECC_brainpoolP256r1_ChaChaPoly:
+ case SecurityPolicies.ECC_nistP256_AesGcm:
+ case SecurityPolicies.ECC_nistP256_ChaChaPoly:
+ result = 12;
break;
- case SecurityPolicies.ECC_nistP384:
+ case SecurityPolicies.ECC_nistP384_AesGcm:
+ case SecurityPolicies.ECC_nistP384_ChaChaPoly:
+ case SecurityPolicies.ECC_brainpoolP384r1_AesGcm:
+ case SecurityPolicies.ECC_brainpoolP384r1_ChaChaPoly:
result = 14;
break;
case SecurityPolicies.None:
diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs
index 9f45315556..096e64a794 100644
--- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs
+++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs
@@ -708,7 +708,7 @@ public static NodeId GetCertificateType(X509Certificate2 certificate)
case Oids.ECDsaWithSha384:
case Oids.ECDsaWithSha256:
case Oids.ECDsaWithSha512:
- return EccUtils.GetEccCertificateTypeId(certificate);
+ return CryptoUtils.GetEccCertificateTypeId(certificate);
case Oids.RsaPkcs1Sha256:
case Oids.RsaPkcs1Sha384:
case Oids.RsaPkcs1Sha512:
@@ -739,7 +739,7 @@ public static bool ValidateCertificateType(
case Oids.ECDsaWithSha384:
case Oids.ECDsaWithSha256:
case Oids.ECDsaWithSha512:
- NodeId certType = EccUtils.GetEccCertificateTypeId(certificate);
+ NodeId certType = CryptoUtils.GetEccCertificateTypeId(certificate);
if (certType.IsNull)
{
return false;
@@ -795,32 +795,45 @@ public static IList MapSecurityPolicyToCertificateTypes(string securityP
case SecurityPolicies.Basic256Sha256:
case SecurityPolicies.Aes128_Sha256_RsaOaep:
case SecurityPolicies.Aes256_Sha256_RsaPss:
+ case SecurityPolicies.RSA_DH_AesGcm:
+ case SecurityPolicies.RSA_DH_ChaChaPoly:
result.Add(ObjectTypeIds.RsaSha256ApplicationCertificateType);
- goto default;
+ break;
case SecurityPolicies.ECC_nistP256:
+ case SecurityPolicies.ECC_nistP256_AesGcm:
+ case SecurityPolicies.ECC_nistP256_ChaChaPoly:
result.Add(ObjectTypeIds.EccNistP256ApplicationCertificateType);
goto case SecurityPolicies.ECC_nistP384;
case SecurityPolicies.ECC_nistP384:
+ case SecurityPolicies.ECC_nistP384_AesGcm:
+ case SecurityPolicies.ECC_nistP384_ChaChaPoly:
result.Add(ObjectTypeIds.EccNistP384ApplicationCertificateType);
- goto default;
+ break;
case SecurityPolicies.ECC_brainpoolP256r1:
+ case SecurityPolicies.ECC_brainpoolP256r1_AesGcm:
+ case SecurityPolicies.ECC_brainpoolP256r1_ChaChaPoly:
result.Add(ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType);
goto case SecurityPolicies.ECC_brainpoolP384r1;
case SecurityPolicies.ECC_brainpoolP384r1:
+ case SecurityPolicies.ECC_brainpoolP384r1_AesGcm:
+ case SecurityPolicies.ECC_brainpoolP384r1_ChaChaPoly:
result.Add(ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType);
- goto default;
+ break;
case SecurityPolicies.ECC_curve25519:
+ case SecurityPolicies.ECC_curve25519_AesGcm:
+ case SecurityPolicies.ECC_curve25519_ChaChaPoly:
result.Add(ObjectTypeIds.EccCurve25519ApplicationCertificateType);
- goto default;
+ break;
case SecurityPolicies.ECC_curve448:
+ case SecurityPolicies.ECC_curve448_AesGcm:
+ case SecurityPolicies.ECC_curve448_ChaChaPoly:
result.Add(ObjectTypeIds.EccCurve448ApplicationCertificateType);
- goto default;
+ break;
case SecurityPolicies.Https:
result.Add(ObjectTypeIds.HttpsCertificateType);
- goto default;
- default:
- return result;
+ break;
}
+ return result;
}
///
diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CryptoUtils.cs b/Stack/Opc.Ua.Core/Security/Certificates/CryptoUtils.cs
new file mode 100644
index 0000000000..c247383166
--- /dev/null
+++ b/Stack/Opc.Ua.Core/Security/Certificates/CryptoUtils.cs
@@ -0,0 +1,1356 @@
+/* Copyright (c) 1996-2022 The OPC Foundation. All rights reserved.
+ The source code in this file is covered under a dual-license scenario:
+ - RCL: for OPC Foundation Corporate Members in good-standing
+ - GPL V2: everybody else
+ RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
+ GNU General Public License as published by the Free Software Foundation;
+ version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
+ This source code is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+using System;
+using System.Globalization;
+using System.Numerics;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using Opc.Ua.Bindings;
+using Opc.Ua.Security.Certificates;
+#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
+{
+ ///
+ /// Defines functions to implement ECC cryptography.
+ ///
+ public static class CryptoUtils
+ {
+ ///
+ /// The name of the NIST P-256 curve.
+ ///
+ public const string NistP256 = nameof(NistP256);
+
+ ///
+ /// The name of the NIST P-384 curve.
+ ///
+ public const string NistP384 = nameof(NistP384);
+
+ ///
+ /// The name of the BrainpoolP256r1 curve.
+ ///
+ public const string BrainpoolP256r1 = nameof(BrainpoolP256r1);
+
+ ///
+ /// The name of the BrainpoolP384r1 curve.
+ ///
+ public const string BrainpoolP384r1 = nameof(BrainpoolP384r1);
+
+ internal const string NistP256KeyParameters = "06-08-2A-86-48-CE-3D-03-01-07";
+ internal const string NistP384KeyParameters = "06-05-2B-81-04-00-22";
+ internal const string BrainpoolP256r1KeyParameters = "06-09-2B-24-03-03-02-08-01-01-07";
+ internal const string BrainpoolP384r1KeyParameters = "06-09-2B-24-03-03-02-08-01-01-0B";
+
+ ///
+ /// Returns true if the certificate is an ECC certificate.
+ ///
+ public static bool IsEccPolicy(string securityPolicyUri)
+ {
+ var info = SecurityPolicies.GetInfo(securityPolicyUri);
+
+ if (info != null)
+ {
+ return info.CertificateKeyFamily == CertificateKeyFamily.ECC;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Returns the NodeId for the certificate type for the specified certificate.
+ ///
+ public static NodeId GetEccCertificateTypeId(X509Certificate2 certificate)
+ {
+ string keyAlgorithm = certificate.GetKeyAlgorithm();
+ if (keyAlgorithm != Oids.ECPublicKey)
+ {
+ return NodeId.Null;
+ }
+
+ PublicKey encodedPublicKey = certificate.PublicKey;
+ switch (BitConverter.ToString(encodedPublicKey.EncodedParameters.RawData))
+ {
+ // nistP256
+ case NistP256KeyParameters:
+ return ObjectTypeIds.EccNistP256ApplicationCertificateType;
+ // nistP384
+ case NistP384KeyParameters:
+ return ObjectTypeIds.EccNistP384ApplicationCertificateType;
+ // brainpoolP256r1
+ case BrainpoolP256r1KeyParameters:
+ return ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType;
+ // brainpoolP384r1
+ case BrainpoolP384r1KeyParameters:
+ return ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType;
+ default:
+ return NodeId.Null;
+ }
+ }
+
+ ///
+ /// returns an ECCCurve if there is a matching supported curve for the provided
+ /// certificate type id. if no supported ECC curve is found null is returned.
+ ///
+ /// the application certificatate type node id
+ /// the ECCCurve, null if certificatate type id has no matching supported ECC curve
+ public static ECCurve? GetCurveFromCertificateTypeId(NodeId certificateType)
+ {
+ ECCurve? curve = null;
+
+ if (certificateType == ObjectTypeIds.EccApplicationCertificateType ||
+ certificateType == ObjectTypeIds.EccNistP256ApplicationCertificateType)
+ {
+ curve = ECCurve.NamedCurves.nistP256;
+ }
+ else if (certificateType == ObjectTypeIds.EccNistP384ApplicationCertificateType)
+ {
+ curve = ECCurve.NamedCurves.nistP384;
+ }
+ else if (certificateType == ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType)
+ {
+ curve = ECCurve.NamedCurves.brainpoolP256r1;
+ }
+ else if (certificateType == ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType)
+ {
+ curve = ECCurve.NamedCurves.brainpoolP384r1;
+ }
+#if CURVE25519
+ else if (certificateType == ObjectTypeIds.EccCurve25519ApplicationCertificateType)
+ {
+ curve = default(ECCurve);
+ }
+ else if (certificateType == ObjectTypeIds.EccCurve448ApplicationCertificateType)
+ {
+ curve = default(ECCurve);
+ }
+#endif
+ return curve;
+ }
+
+ ///
+ /// Returns the signature algorithm for the specified certificate.
+ ///
+ public static string GetECDsaQualifier(X509Certificate2 certificate)
+ {
+ if (X509Utils.IsECDsaSignature(certificate))
+ {
+ const string signatureQualifier = "ECDsa";
+ PublicKey encodedPublicKey = certificate.PublicKey;
+
+ // New values can be determined by running the dotted-decimal OID value
+ // through BitConverter.ToString(CryptoConfig.EncodeOID(dottedDecimal));
+
+ switch (BitConverter.ToString(encodedPublicKey.EncodedParameters.RawData))
+ {
+ case NistP256KeyParameters:
+ return NistP256;
+ case NistP384KeyParameters:
+ return NistP384;
+ case BrainpoolP256r1KeyParameters:
+ return BrainpoolP256r1;
+ case BrainpoolP384r1KeyParameters:
+ return BrainpoolP384r1;
+ default:
+ return signatureQualifier;
+ }
+ }
+ return string.Empty;
+ }
+
+ ///
+ /// Returns the public key for the specified certificate.
+ ///
+ public static ECDsa GetPublicKey(X509Certificate2 certificate)
+ {
+ return GetPublicKey(certificate, out string[] _);
+ }
+
+ ///
+ /// Returns the public key for the specified certificate and outputs the security policy uris.
+ ///
+ ///
+ ///
+ public static ECDsa GetPublicKey(
+ X509Certificate2 certificate,
+ out string[] securityPolicyUris)
+ {
+ securityPolicyUris = null;
+
+ if (certificate == null)
+ {
+ return null;
+ }
+
+ string keyAlgorithm = certificate.GetKeyAlgorithm();
+
+ if (keyAlgorithm != Oids.ECPublicKey)
+ {
+ return null;
+ }
+
+ const X509KeyUsageFlags kSufficientFlags =
+ X509KeyUsageFlags.KeyAgreement |
+ X509KeyUsageFlags.DigitalSignature |
+ X509KeyUsageFlags.NonRepudiation |
+ X509KeyUsageFlags.CrlSign |
+ X509KeyUsageFlags.KeyCertSign;
+
+ foreach (X509Extension extension in certificate.Extensions)
+ {
+ if (extension.Oid.Value == "2.5.29.15")
+ {
+ var kuExt = (X509KeyUsageExtension)extension;
+
+ if ((kuExt.KeyUsages & kSufficientFlags) == 0)
+ {
+ return null;
+ }
+ }
+ }
+
+ PublicKey encodedPublicKey = certificate.PublicKey;
+ string keyParameters = BitConverter.ToString(
+ encodedPublicKey.EncodedParameters.RawData);
+ byte[] keyValue = encodedPublicKey.EncodedKeyValue.RawData;
+
+ var ecParameters = default(ECParameters);
+
+ if (keyValue[0] != 0x04)
+ {
+ throw new InvalidOperationException("Only uncompressed points are supported");
+ }
+
+ byte[] x = new byte[(keyValue.Length - 1) / 2];
+ byte[] y = new byte[x.Length];
+
+ Buffer.BlockCopy(keyValue, 1, x, 0, x.Length);
+ Buffer.BlockCopy(keyValue, 1 + x.Length, y, 0, y.Length);
+
+ ecParameters.Q.X = x;
+ ecParameters.Q.Y = y;
+
+ // New values can be determined by running the dotted-decimal OID value
+ // through BitConverter.ToString(CryptoConfig.EncodeOID(dottedDecimal));
+
+ switch (keyParameters)
+ {
+ case NistP256KeyParameters:
+ ecParameters.Curve = ECCurve.NamedCurves.nistP256;
+ securityPolicyUris = [SecurityPolicies.ECC_nistP256];
+ break;
+ case NistP384KeyParameters:
+ ecParameters.Curve = ECCurve.NamedCurves.nistP384;
+ securityPolicyUris = [SecurityPolicies.ECC_nistP384, SecurityPolicies
+ .ECC_nistP256];
+ break;
+ case BrainpoolP256r1KeyParameters:
+ ecParameters.Curve = ECCurve.NamedCurves.brainpoolP256r1;
+ securityPolicyUris = [SecurityPolicies.ECC_brainpoolP256r1];
+ break;
+ case BrainpoolP384r1KeyParameters:
+ ecParameters.Curve = ECCurve.NamedCurves.brainpoolP384r1;
+ securityPolicyUris = [SecurityPolicies.ECC_brainpoolP384r1, SecurityPolicies
+ .ECC_brainpoolP256r1];
+ break;
+ default:
+ throw new NotImplementedException(keyParameters);
+ }
+
+ return ECDsa.Create(ecParameters);
+ }
+
+ ///
+ /// Returns the length of a ECDsa signature of a digest.
+ ///
+ ///
+ public static int GetSignatureLength(X509Certificate2 signingCertificate)
+ {
+ if (signingCertificate == null)
+ {
+ throw ServiceResultException.Create(
+ StatusCodes.BadSecurityChecksFailed,
+ "No public key for certificate.");
+ }
+
+ if (signingCertificate.GetRSAPublicKey() != null)
+ {
+ return RsaUtils.GetSignatureLength(signingCertificate);
+ }
+
+ using ECDsa publicKey =
+ GetPublicKey(signingCertificate)
+ ?? throw ServiceResultException.Create(
+ StatusCodes.BadSecurityChecksFailed,
+ "No public key for certificate.");
+
+ return publicKey.KeySize / 4;
+ }
+
+ ///
+ /// Computes a signature.
+ ///
+ public static byte[] Sign(
+ ArraySegment dataToSign,
+ X509Certificate2 signingCertificate,
+ string securityPolicyUri)
+ {
+ var info = SecurityPolicies.GetInfo(securityPolicyUri);
+ return Sign(dataToSign, signingCertificate, info.AsymmetricSignatureAlgorithm);
+ }
+
+ ///
+ /// Computes a signature.
+ ///
+ ///
+ public static byte[] Sign(
+ ArraySegment dataToSign,
+ X509Certificate2 signingCertificate,
+ AsymmetricSignatureAlgorithm algorithm)
+ {
+ switch (algorithm)
+ {
+ case AsymmetricSignatureAlgorithm.None:
+ return null;
+ case AsymmetricSignatureAlgorithm.RsaPkcs15Sha1:
+ return RsaUtils.Rsa_Sign(
+ dataToSign,
+ signingCertificate,
+ HashAlgorithmName.SHA1,
+ RSASignaturePadding.Pkcs1);
+ case AsymmetricSignatureAlgorithm.RsaPkcs15Sha256:
+ return RsaUtils.Rsa_Sign(
+ dataToSign,
+ signingCertificate,
+ HashAlgorithmName.SHA256,
+ RSASignaturePadding.Pkcs1);
+ case AsymmetricSignatureAlgorithm.RsaPssSha256:
+ return RsaUtils.Rsa_Sign(
+ dataToSign,
+ signingCertificate,
+ HashAlgorithmName.SHA256,
+ RSASignaturePadding.Pss);
+ case AsymmetricSignatureAlgorithm.EcdsaSha256:
+ case AsymmetricSignatureAlgorithm.EcdsaSha384:
+ break;
+ default:
+ throw new ServiceResultException(StatusCodes.BadSecurityPolicyRejected);
+ }
+
+ // get the algorithm used for the signature.
+ HashAlgorithmName hashAlgorithm;
+
+ switch (algorithm)
+ {
+ case AsymmetricSignatureAlgorithm.EcdsaSha384:
+ hashAlgorithm = HashAlgorithmName.SHA384;
+ break;
+ case AsymmetricSignatureAlgorithm.EcdsaSha256:
+ hashAlgorithm = HashAlgorithmName.SHA256;
+ break;
+ default:
+ throw new NotSupportedException($"AsymmetricSignatureAlgorithm not supported: {algorithm}");
+ }
+
+ ECDsa senderPrivateKey =
+ signingCertificate.GetECDsaPrivateKey()
+ ?? throw new ServiceResultException(
+ StatusCodes.BadCertificateInvalid,
+ "Missing private key needed for create a signature.");
+
+ using (senderPrivateKey)
+ {
+ byte[] signature = senderPrivateKey.SignData(
+ dataToSign.Array,
+ dataToSign.Offset,
+ dataToSign.Count,
+ hashAlgorithm);
+
+ return signature;
+ }
+ }
+
+ ///
+ /// Verifies a signature.
+ ///
+ public static bool Verify(
+ ArraySegment dataToVerify,
+ byte[] signature,
+ X509Certificate2 signingCertificate,
+ string securityPolicyUri)
+ {
+ var info = SecurityPolicies.GetInfo(securityPolicyUri);
+
+ if (info == null)
+ {
+ throw new ServiceResultException(
+ StatusCodes.BadSecurityChecksFailed,
+ $"Unknown security policy: {securityPolicyUri}");
+ }
+
+ return Verify(
+ dataToVerify,
+ signature,
+ signingCertificate,
+ info.AsymmetricSignatureAlgorithm);
+ }
+
+ ///
+ /// Verifies a signature.
+ ///
+ public static bool Verify(
+ ArraySegment dataToVerify,
+ byte[] signature,
+ X509Certificate2 signingCertificate,
+ AsymmetricSignatureAlgorithm algorithm)
+ {
+ switch (algorithm)
+ {
+ case AsymmetricSignatureAlgorithm.None:
+ return true;
+ case AsymmetricSignatureAlgorithm.RsaPkcs15Sha1:
+ return RsaUtils.Rsa_Verify(
+ dataToVerify,
+ signature,
+ signingCertificate,
+ HashAlgorithmName.SHA1,
+ RSASignaturePadding.Pkcs1);
+ case AsymmetricSignatureAlgorithm.RsaPkcs15Sha256:
+ return RsaUtils.Rsa_Verify(
+ dataToVerify,
+ signature,
+ signingCertificate,
+ HashAlgorithmName.SHA256,
+ RSASignaturePadding.Pkcs1);
+ case AsymmetricSignatureAlgorithm.RsaPssSha256:
+ return RsaUtils.Rsa_Verify(
+ dataToVerify,
+ signature,
+ signingCertificate,
+ HashAlgorithmName.SHA256,
+ RSASignaturePadding.Pss);
+ case AsymmetricSignatureAlgorithm.EcdsaSha256:
+ case AsymmetricSignatureAlgorithm.EcdsaSha384:
+ break;
+ default:
+ return false;
+ }
+
+ // get the algorithm used for the signature.
+ HashAlgorithmName hashAlgorithm;
+
+ switch (algorithm)
+ {
+ case AsymmetricSignatureAlgorithm.EcdsaSha384:
+ hashAlgorithm = HashAlgorithmName.SHA384;
+ break;
+ case AsymmetricSignatureAlgorithm.EcdsaSha256:
+ hashAlgorithm = HashAlgorithmName.SHA256;
+ break;
+ default:
+ throw new NotSupportedException($"AsymmetricSignatureAlgorithm not supported: {algorithm}.");
+ }
+
+ using ECDsa ecdsa = GetPublicKey(signingCertificate);
+
+ return ecdsa.VerifyData(
+ dataToVerify.Array,
+ dataToVerify.Offset,
+ dataToVerify.Count,
+ signature,
+ hashAlgorithm);
+ }
+
+ ///
+ /// Adds padding to a buffer. Input: buffer with unencrypted data starting at 0; plaintext data starting at offset; no padding.
+ ///
+ /// buffer with unencrypted data starting at 0; plaintext data starting at offset; no padding.
+ ///
+ /// Additional bytes that will be appended after padding (e.g., HMAC) and must be considered for block alignment.
+ /// Output: buffer with unencrypted data starting at 0; plaintext data starting at offset; padding added.
+ private static ArraySegment AddPadding(ArraySegment data, int blockSize, int trailingBytes = 0)
+ {
+ int paddingByteSize = blockSize > byte.MaxValue ? 2 : 1;
+ int paddingSize = blockSize - ((data.Count + paddingByteSize + trailingBytes) % blockSize);
+ paddingSize %= blockSize;
+
+ int endOfData = data.Offset + data.Count;
+ int endOfPaddedData = data.Offset + data.Count + paddingSize + paddingByteSize;
+
+ for (int ii = endOfData; ii < endOfPaddedData - paddingByteSize && ii < data.Array.Length; ii++)
+ {
+ data.Array[ii] = (byte)(paddingSize & 0xFF);
+ }
+
+ data.Array[endOfData + paddingSize] = (byte)(paddingSize & 0xFF);
+
+ if (blockSize > byte.MaxValue)
+ {
+ data.Array[endOfData + paddingSize + 1] = (byte)((paddingSize & 0xFF) >> 8);
+ }
+
+ return new ArraySegment(data.Array, data.Offset, data.Count + paddingSize + paddingByteSize);
+ }
+
+ ///
+ /// Removes padding from a buffer. Input: buffer with unencrypted data starting at 0; plaintext including padding starting at offset; signature removed.
+ ///
+ /// Input: buffer with unencrypted data starting at 0; plaintext including padding starting at offset; signature removed.
+ ///
+ /// Output: buffer with unencrypted data starting at 0; plaintext starting at offset; padding excluded.
+ ///
+ private static ArraySegment RemovePadding(ArraySegment data, int blockSize)
+ {
+ int paddingSize = data.Array[data.Offset + data.Count - 1];
+ int paddingByteSize = 1;
+
+ if (blockSize > byte.MaxValue)
+ {
+ paddingSize <<= 8;
+ paddingSize += data.Array[data.Offset + data.Count - 2];
+ paddingByteSize = 2;
+ }
+
+ int notvalid = paddingSize < data.Count ? 0 : 1;
+ int start = data.Offset + data.Count - paddingSize - paddingByteSize;
+
+ for (int ii = data.Offset; ii < data.Count - paddingByteSize && ii < paddingSize; ii++)
+ {
+ if (start < 0 || start + ii >= data.Count)
+ {
+ notvalid |= 1;
+ continue;
+ }
+
+ notvalid |= data.Array[start + ii] ^ (paddingSize & 0xFF);
+ }
+
+ if (notvalid != 0)
+ {
+ throw new CryptographicException("Invalid padding.");
+ }
+
+ return new ArraySegment(data.Array, 0, data.Offset + data.Count - paddingSize - paddingByteSize);
+ }
+
+ ///
+ /// Encrypts the buffer using the algorithm specified by the security policy.
+ ///
+ public static ArraySegment SymmetricEncryptAndSign(
+ ArraySegment data,
+ SecurityPolicyInfo securityPolicy,
+ byte[] encryptingKey,
+ byte[] iv,
+ byte[] signingKey = null,
+ HMAC hmac = null,
+ bool signOnly = false,
+ uint tokenId = 0,
+ uint lastSequenceNumber = 0)
+ {
+ SymmetricEncryptionAlgorithm algorithm = securityPolicy.SymmetricEncryptionAlgorithm;
+
+ if (algorithm == SymmetricEncryptionAlgorithm.None)
+ {
+ return data;
+ }
+
+ if (algorithm is SymmetricEncryptionAlgorithm.Aes128Gcm or SymmetricEncryptionAlgorithm.Aes256Gcm)
+ {
+#if NET8_0_OR_GREATER
+ return EncryptWithAesGcm(data, encryptingKey, iv, signOnly, tokenId, lastSequenceNumber);
+#else
+ throw new NotSupportedException("AES-GCM requires .NET 8 or greater.");
+#endif
+ }
+
+ if (algorithm == SymmetricEncryptionAlgorithm.ChaCha20Poly1305)
+ {
+#if NET8_0_OR_GREATER
+ return EncryptWithChaCha20Poly1305(
+ data,
+ encryptingKey,
+ iv,
+ signOnly,
+ tokenId,
+ lastSequenceNumber);
+#else
+ throw new NotSupportedException("ChaCha20Poly1305 requires .NET 8 or greater.");
+#endif
+ }
+
+ int hashLength = 0;
+
+ if (signingKey != null)
+ {
+ if (hmac == null)
+ {
+ throw new CryptographicException("Missing HMAC for symmetric signing.");
+ }
+
+ hashLength = hmac.HashSize / 8;
+ }
+
+ if (!signOnly)
+ {
+ data = AddPadding(data, iv.Length, hashLength);
+ }
+
+ if (signingKey != null)
+ {
+ byte[] hash = hmac.ComputeHash(data.Array, 0, data.Offset + data.Count);
+
+ Buffer.BlockCopy(
+ hash,
+ 0,
+ data.Array,
+ data.Offset + data.Count,
+ hash.Length);
+
+ data = new ArraySegment(
+ data.Array,
+ data.Offset,
+ data.Count + hash.Length);
+ }
+
+ if (!signOnly)
+ {
+#pragma warning disable CA5401 // Symmetric encryption uses non-default initialization vector
+ using var aes = Aes.Create();
+
+ aes.Mode = CipherMode.CBC;
+ aes.Padding = PaddingMode.None;
+ aes.Key = encryptingKey;
+ aes.IV = iv;
+
+ using ICryptoTransform encryptor = aes.CreateEncryptor();
+#pragma warning restore CA5401
+
+ encryptor.TransformBlock(
+ data.Array,
+ data.Offset,
+ data.Count,
+ data.Array,
+ data.Offset);
+ }
+
+ return new ArraySegment(data.Array, 0, data.Offset + data.Count);
+ }
+
+#if NET8_0_OR_GREATER
+ private static byte[] ApplyAeadMask(uint tokenId, uint lastSequenceNumber, byte[] iv)
+ {
+ var copy = new byte[iv.Length];
+ Buffer.BlockCopy(iv, 0, copy, 0, iv.Length);
+
+ copy[0] ^= (byte)((tokenId & 0x000000FF));
+ copy[1] ^= (byte)((tokenId & 0x0000FF00) >> 8);
+ copy[2] ^= (byte)((tokenId & 0x00FF0000) >> 16);
+ copy[3] ^= (byte)((tokenId & 0xFF000000) >> 24);
+ copy[4] ^= (byte)((lastSequenceNumber & 0x000000FF));
+ copy[5] ^= (byte)((lastSequenceNumber & 0x0000FF00) >> 8);
+ copy[6] ^= (byte)((lastSequenceNumber & 0x00FF0000) >> 16);
+ copy[7] ^= (byte)((lastSequenceNumber & 0xFF000000) >> 24);
+
+ return copy;
+ }
+
+ private const int kChaChaPolyIvLength = 12;
+ private const int kChaChaPolyTagLength = 16;
+
+ private static ArraySegment EncryptWithChaCha20Poly1305(
+ ArraySegment data,
+ byte[] encryptingKey,
+ byte[] iv,
+ bool signOnly,
+ uint tokenId,
+ uint lastSequenceNumber)
+ {
+ if (encryptingKey == null || encryptingKey.Length != 32)
+ {
+ throw new ArgumentException("ChaCha20-Poly1305 requires a 256-bit (32-byte) key.", nameof(encryptingKey));
+ }
+
+ if (iv == null || iv.Length != kChaChaPolyIvLength)
+ {
+ throw new ArgumentException("ChaCha20-Poly1305 requires a 96-bit (12-byte) nonce.", nameof(iv));
+ }
+
+ byte[] ciphertext = new byte[signOnly ? 0 : data.Count];
+ byte[] tag = new byte[kChaChaPolyTagLength]; // ChaCha20-Poly1305/AES-GCM uses 128-bit authentication tag
+
+ var extraData = new ReadOnlySpan(
+ data.Array,
+ 0,
+ signOnly ? data.Offset + data.Count : data.Offset);
+
+ using var chacha = new ChaCha20Poly1305(encryptingKey);
+
+ iv = ApplyAeadMask(tokenId, lastSequenceNumber, iv);
+
+ chacha.Encrypt(
+ iv,
+ signOnly ? Array.Empty() : data,
+ ciphertext,
+ tag,
+ extraData);
+
+ CryptoTrace.Start(ConsoleColor.DarkCyan, "EncryptWithChaCha20Poly1305");
+ CryptoTrace.WriteLine($"Data Offset/Count={data.Offset}/{data.Count}");
+ CryptoTrace.WriteLine($"TokenId/LastSequenceNumber={tokenId}/{lastSequenceNumber}");
+ CryptoTrace.WriteLine($"EncryptingKey={CryptoTrace.KeyToString(encryptingKey)}");
+ CryptoTrace.WriteLine($"IV={CryptoTrace.KeyToString(iv)}");
+ CryptoTrace.WriteLine($"EncryptedData={CryptoTrace.KeyToString(ciphertext)}");
+ CryptoTrace.WriteLine($"Tag={CryptoTrace.KeyToString(tag)}");
+ CryptoTrace.WriteLine($"ExtraData={CryptoTrace.KeyToString(extraData.ToArray())}");
+ CryptoTrace.Finish("EncryptWithChaCha20Poly1305");
+
+ // Return layout: [associated data | ciphertext | tag]
+ if (!signOnly)
+ {
+ Buffer.BlockCopy(ciphertext, 0, data.Array, data.Offset, ciphertext.Length);
+ }
+
+ Buffer.BlockCopy(tag, 0, data.Array, data.Offset + data.Count, tag.Length);
+
+ return new ArraySegment(
+ data.Array,
+ 0,
+ data.Offset + data.Count + kChaChaPolyTagLength);
+ }
+#endif
+
+#if NET8_0_OR_GREATER
+ private static ArraySegment DecryptWithChaCha20Poly1305(
+ ArraySegment data,
+ byte[] encryptingKey,
+ byte[] iv,
+ bool signOnly,
+ uint tokenId,
+ uint lastSequenceNumber)
+ {
+ if (encryptingKey == null || encryptingKey.Length != 32)
+ {
+ throw new ArgumentException("ChaCha20-Poly1305 requires a 256-bit (32-byte) key.", nameof(encryptingKey));
+ }
+
+ if (iv == null || iv.Length != kChaChaPolyIvLength)
+ {
+ throw new ArgumentException("ChaCha20-Poly1305 requires a 96-bit (12-byte) nonce.", nameof(iv));
+ }
+
+ if (data.Count < kChaChaPolyTagLength) // Must at least contain tag
+ {
+ throw new ArgumentException("Ciphertext too short.", nameof(data));
+ }
+
+ byte[] plaintext = new byte[data.Count - kChaChaPolyTagLength];
+
+ var encryptedData = new ArraySegment(
+ data.Array,
+ data.Offset,
+ signOnly ? 0 : data.Count - kChaChaPolyTagLength);
+
+ var tag = new ArraySegment(
+ data.Array,
+ data.Offset + data.Count - kChaChaPolyTagLength,
+ kChaChaPolyTagLength);
+
+ var extraData = new ReadOnlySpan(
+ data.Array,
+ 0,
+ signOnly ? data.Offset + data.Count - kChaChaPolyTagLength : data.Offset);
+
+ using var chacha = new ChaCha20Poly1305(encryptingKey);
+
+ iv = ApplyAeadMask(tokenId, lastSequenceNumber, iv);
+
+ chacha.Decrypt(
+ iv,
+ encryptedData,
+ tag,
+ signOnly ? [] : plaintext,
+ extraData);
+
+ CryptoTrace.Start(ConsoleColor.DarkCyan, "DecryptWithChaCha20Poly1305");
+ CryptoTrace.WriteLine($"Data Offset/Count={data.Offset}/{data.Count - kChaChaPolyTagLength}");
+ CryptoTrace.WriteLine($"TokenId/LastSequenceNumber={tokenId}/{lastSequenceNumber}");
+ CryptoTrace.WriteLine($"EncryptingKey={CryptoTrace.KeyToString(encryptingKey)}");
+ CryptoTrace.WriteLine($"IV={CryptoTrace.KeyToString(iv)}");
+ CryptoTrace.WriteLine($"EncryptedData={CryptoTrace.KeyToString(encryptedData)}");
+ CryptoTrace.WriteLine($"Tag={CryptoTrace.KeyToString(tag)}");
+ CryptoTrace.WriteLine($"ExtraData={CryptoTrace.KeyToString(extraData.ToArray())}");
+ CryptoTrace.Finish("DecryptWithChaCha20Poly1305");
+
+ // Return layout: [associated data | plaintext]
+ if (!signOnly)
+ {
+ Buffer.BlockCopy(plaintext, 0, data.Array, data.Offset, encryptedData.Count);
+ }
+
+ return new ArraySegment(data.Array, 0, data.Offset + data.Count - kChaChaPolyTagLength);
+ }
+#endif
+
+#if NET8_0_OR_GREATER
+ private const int kAesGcmIvLength = 12;
+ private const int kAesGcmTagLength = 16;
+
+ private static ArraySegment EncryptWithAesGcm(
+ ArraySegment data,
+ byte[] encryptingKey,
+ byte[] iv,
+ bool signOnly,
+ uint tokenId,
+ uint lastSequenceNumber)
+ {
+ if (encryptingKey == null)
+ {
+ throw new ArgumentNullException(nameof(encryptingKey));
+ }
+
+ if (iv == null || iv.Length != kAesGcmIvLength)
+ {
+ throw new ArgumentException("AES-GCM requires a 96-bit (12-byte) IV/nonce.", nameof(iv));
+ }
+
+ byte[] ciphertext = new byte[signOnly ? 0 : data.Count];
+ byte[] tag = new byte[kAesGcmTagLength]; // AES-GCM uses 128-bit authentication tag
+
+ var extraData = new ReadOnlySpan(
+ data.Array,
+ 0,
+ signOnly ? data.Offset + data.Count : data.Offset);
+
+ using var aesGcm = new AesGcm(encryptingKey, kAesGcmTagLength);
+
+ iv = ApplyAeadMask(tokenId, lastSequenceNumber, iv);
+
+ aesGcm.Encrypt(
+ iv,
+ signOnly ? Array.Empty() : data,
+ ciphertext,
+ tag,
+ extraData);
+
+ CryptoTrace.Start(ConsoleColor.DarkCyan, "EncryptWithAesGcm");
+ CryptoTrace.WriteLine($"Data Offset/Count={data.Offset}/{data.Count}");
+ CryptoTrace.WriteLine($"TokenId/LastSequenceNumber={tokenId}/{lastSequenceNumber}");
+ CryptoTrace.WriteLine($"EncryptingKey={CryptoTrace.KeyToString(encryptingKey)}");
+ CryptoTrace.WriteLine($"IV={CryptoTrace.KeyToString(iv)}");
+ CryptoTrace.WriteLine($"EncryptedData={CryptoTrace.KeyToString(ciphertext)}");
+ CryptoTrace.WriteLine($"Tag={CryptoTrace.KeyToString(tag)}");
+ CryptoTrace.WriteLine($"ExtraData={CryptoTrace.KeyToString(extraData.ToArray())}");
+ CryptoTrace.Finish("DecryptWithAesGcm");
+
+ // Return layout: [associated data | ciphertext | tag]
+ if (!signOnly)
+ {
+ Buffer.BlockCopy(ciphertext, 0, data.Array, data.Offset, ciphertext.Length);
+ }
+
+ Buffer.BlockCopy(tag, 0, data.Array, data.Offset + data.Count, tag.Length);
+
+ return new ArraySegment(
+ data.Array,
+ 0,
+ data.Offset + data.Count + kAesGcmTagLength);
+ }
+#endif
+
+#if NET8_0_OR_GREATER
+ private static ArraySegment DecryptWithAesGcm(
+ ArraySegment data,
+ byte[] encryptingKey,
+ byte[] iv,
+ bool signOnly,
+ uint tokenId,
+ uint lastSequenceNumber)
+ {
+ if (encryptingKey == null)
+ {
+ throw new ArgumentNullException(nameof(encryptingKey));
+ }
+
+ if (iv == null || iv.Length != kAesGcmIvLength)
+ {
+ throw new ArgumentException("AES-GCM requires a 96-bit (12-byte) IV/nonce.", nameof(iv));
+ }
+
+ if (data.Count < kAesGcmTagLength) // Must at least contain tag
+ {
+ throw new ArgumentException("Ciphertext too short.", nameof(data));
+ }
+
+ byte[] plaintext = new byte[data.Count - kAesGcmTagLength];
+
+ var encryptedData = new ArraySegment(
+ data.Array,
+ data.Offset,
+ signOnly ? 0 : data.Count - kAesGcmTagLength);
+
+ var tag = new ArraySegment(
+ data.Array,
+ data.Offset + data.Count - kAesGcmTagLength,
+ kAesGcmTagLength);
+
+ var extraData = new ReadOnlySpan(
+ data.Array,
+ 0,
+ signOnly ? data.Offset + data.Count - kAesGcmTagLength : data.Offset);
+
+ using var aesGcm = new AesGcm(encryptingKey, kAesGcmTagLength);
+
+ iv = ApplyAeadMask(tokenId, lastSequenceNumber, iv);
+
+ CryptoTrace.Start(ConsoleColor.DarkCyan, "DecryptWithAesGcm");
+ CryptoTrace.WriteLine($"Data Offset/Count={data.Offset}/{data.Count - kAesGcmTagLength}");
+ CryptoTrace.WriteLine($"TokenId/LastSequenceNumber={tokenId}/{lastSequenceNumber}");
+ CryptoTrace.WriteLine($"EncryptingKey={CryptoTrace.KeyToString(encryptingKey)}");
+ CryptoTrace.WriteLine($"IV={CryptoTrace.KeyToString(iv)}");
+ CryptoTrace.WriteLine($"EncryptedData={CryptoTrace.KeyToString(encryptedData)}");
+ CryptoTrace.WriteLine($"Tag={CryptoTrace.KeyToString(tag)}");
+ CryptoTrace.WriteLine($"ExtraData={CryptoTrace.KeyToString(extraData.ToArray())}");
+ CryptoTrace.Finish("DecryptWithAesGcm");
+
+ aesGcm.Decrypt(
+ iv,
+ encryptedData,
+ tag,
+ signOnly ? [] : plaintext,
+ extraData);
+
+ // Return layout: [associated data | plaintext]
+ if (!signOnly)
+ {
+ Buffer.BlockCopy(plaintext, 0, data.Array, data.Offset, encryptedData.Count);
+ }
+
+ return new ArraySegment(data.Array, 0, data.Offset + data.Count - kAesGcmTagLength);
+ }
+#endif
+
+ ///
+ /// Decrypts the buffer using the algorithm specified by the security policy.
+ ///
+ ///
+ ///
+ public static ArraySegment SymmetricDecryptAndVerify(
+ ArraySegment data,
+ SecurityPolicyInfo securityPolicy,
+ byte[] encryptingKey,
+ byte[] iv,
+ byte[] signingKey = null,
+ bool signOnly = false,
+ uint tokenId = 0,
+ uint lastSequenceNumber = 0)
+ {
+ SymmetricEncryptionAlgorithm algorithm = securityPolicy.SymmetricEncryptionAlgorithm;
+
+ if (algorithm == SymmetricEncryptionAlgorithm.None)
+ {
+ return data;
+ }
+
+ if (algorithm is SymmetricEncryptionAlgorithm.Aes128Gcm or SymmetricEncryptionAlgorithm.Aes256Gcm)
+ {
+#if NET8_0_OR_GREATER
+ return DecryptWithAesGcm(data, encryptingKey, iv, signOnly, tokenId, lastSequenceNumber);
+#else
+ throw new NotSupportedException("AES-GCM requires .NET 8 or greater.");
+#endif
+ }
+
+ if (algorithm == SymmetricEncryptionAlgorithm.ChaCha20Poly1305)
+ {
+#if NET8_0_OR_GREATER
+ return DecryptWithChaCha20Poly1305(
+ data,
+ encryptingKey,
+ iv,
+ signOnly,
+ tokenId,
+ lastSequenceNumber);
+#else
+ throw new NotSupportedException("ChaCha20Poly1305 requires .NET 8 or greater.");
+#endif
+ }
+
+ if (!signOnly)
+ {
+ using var aes = Aes.Create();
+
+ aes.Mode = CipherMode.CBC;
+ aes.Padding = PaddingMode.None;
+ aes.Key = encryptingKey;
+ aes.IV = iv;
+
+ using ICryptoTransform decryptor = aes.CreateDecryptor();
+
+ decryptor.TransformBlock(
+ data.Array,
+ data.Offset,
+ data.Count,
+ data.Array,
+ data.Offset);
+ }
+
+ int isNotValid = 0;
+
+ if (signingKey != null)
+ {
+ using HMAC hmac = securityPolicy.CreateSignatureHmac(signingKey);
+ byte[] hash = hmac.ComputeHash(data.Array, 0, data.Offset + data.Count - (hmac.HashSize / 8));
+ for (int ii = 0; ii < hash.Length; ii++)
+ {
+ int index = data.Offset + data.Count - hash.Length + ii;
+ isNotValid |= data.Array[index] != hash[ii] ? 1 : 0;
+ }
+
+ data = new ArraySegment(
+ data.Array,
+ data.Offset,
+ data.Count - hash.Length);
+ }
+
+ if (!signOnly)
+ {
+ data = RemovePadding(data, iv.Length);
+ }
+
+ if (isNotValid != 0)
+ {
+ throw new CryptographicException("Invalid signature.");
+ }
+
+ return new ArraySegment(data.Array, 0, data.Offset + data.Count);
+ }
+ }
+
+#if X
+ class FfdheDhWithRsaAuth
+ {
+ // ffdhe2048 prime from RFC 7919 (hex, without whitespace).
+ // (RFC 7919 Appendix A.3 — use this canonical modulus in production.)
+ 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";
+
+ // Generator for FFDHE groups is 2
+ static readonly BigInteger G = new BigInteger(2);
+
+ static void Main()
+ {
+ // Parse the RFC hex prime into a positive BigInteger
+ BigInteger p = ParseHexBigInteger(FFDHE2048_HEX);
+
+ // Recommended: use a short ephemeral exponent (e.g. 256-bit) for performance
+ // while still achieving ~128-bit security for typical scenarios.
+ // (RFC7919 and implementation guidance discuss exponent sizing; choose per your threat model.)
+ int privateBitLength = 256;
+
+ Console.WriteLine("Simulating DH exchange (ffdhe2048) with RSA signing...");
+
+ // Simulate Alice and Bob
+ var alice = CreateDhParticipant(p, privateBitLength);
+ var bob = CreateDhParticipant(p, privateBitLength);
+
+ // Each signs their public value with their own RSA key (DHE-RSA style authentication)
+ byte[] alicePub = ToBigEndian(alice.PublicValue);
+ byte[] bobPub = ToBigEndian(bob.PublicValue);
+
+ byte[] aliceSig, bobSig;
+ using (RSA rsaAlice = RSA.Create(2048))
+ using (RSA rsaBob = RSA.Create(2048))
+ {
+ // In real use the RSA keys are persistent (server cert) and public keys/certificates exchanged
+ aliceSig = rsaAlice.SignData(alicePub, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
+ bobSig = rsaBob.SignData(bobPub, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
+
+ // Each verifies the other's signature (in a real protocol they'd have the peer's cert/public key)
+ bool verifyAlice = rsaBob.VerifyData(bobPub, bobSig, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
+ bool verifyBob = rsaAlice.VerifyData(alicePub, aliceSig, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
+
+ Console.WriteLine($"Alice verifies Bob's signature: {verifyAlice}");
+ Console.WriteLine($"Bob verifies Alice's signature: {verifyBob}");
+ }
+
+ // Compute shared secrets
+ BigInteger aliceShared = BigInteger.ModPow(bob.PublicValue, alice.PrivateValue, p);
+ BigInteger bobShared = BigInteger.ModPow(alice.PublicValue, bob.PrivateValue, p);
+
+ if (aliceShared != bobShared)
+ {
+ Console.WriteLine("Shared secrets do not match! Aborting.");
+ return;
+ }
+
+ byte[] sharedBytes = ToBigEndian(aliceShared); // same as bobShared
+
+ // Derive 32-byte key with HKDF-SHA256 (RFC 5869)
+ byte[] salt = RandomNumberGenerator.GetBytes(32); // optional but recommended
+ byte[] info = Encoding.UTF8.GetBytes("ffdhe2048-dhe-rsa-derived-key");
+ byte[] aesKey = HkdfSha256(sharedBytes, salt, info, 32);
+ }
+
+ // Creates a participant with ephemeral private/public values
+ static (BigInteger PrivateValue, BigInteger PublicValue) CreateDhParticipant(BigInteger p, int privateBitLen)
+ {
+ BigInteger priv = GenerateRandomBigInteger(privateBitLen);
+ BigInteger pub = BigInteger.ModPow(G, priv, p);
+ return (priv, pub);
+ }
+
+ // Generate an unsigned positive BigInteger of bitLength bits (big-endian)
+ static BigInteger GenerateRandomBigInteger(int bitLength)
+ {
+ int byteCount = (bitLength + 7) / 8;
+ byte[] be = RandomNumberGenerator.GetBytes(byteCount);
+
+ // Ensure top bit set so the value has the requested bit length (avoid tiny exponents)
+ int topBitIndex = (bitLength - 1) % 8;
+ be[0] |= (byte)(1 << topBitIndex);
+
+ // Convert big-endian to little-endian + sign byte for BigInteger ctor
+ byte[] le = new byte[be.Length + 1]; // extra zero to force positive
+ for (int i = 0; i < be.Length; i++)
+ le[i] = be[be.Length - 1 - i];
+ le[le.Length - 1] = 0;
+ return new BigInteger(le);
+ }
+
+ // Convert BigInteger to big-endian unsigned byte[] (no leading zero)
+ static byte[] ToBigEndian(BigInteger value)
+ {
+ if (value.Sign < 0)
+ throw new ArgumentException("value must be non-negative");
+ byte[] le = value.ToByteArray(); // little-endian two's complement
+ // Trim any trailing zero that indicates sign if present
+ int last = le.Length - 1;
+ if (le[last] == 0)
+ {
+ Array.Resize(ref le, last);
+ last--;
+ }
+ byte[] be = new byte[le.Length];
+ for (int i = 0; i < le.Length; i++)
+ be[i] = le[le.Length - 1 - i];
+ return be;
+ }
+
+ // Parse hex into a positive BigInteger (handles odd-length and ensures positive)
+ static BigInteger ParseHexBigInteger(string hex)
+ {
+ hex = hex.Trim().Replace(" ", "").Replace("\n", "").Replace("\r", "");
+ // Ensure even length
+ if ((hex.Length & 1) == 1)
+ hex = "0" + hex;
+ // Use Convert.FromHexString (available in .NET 5+) or implement equivalent
+ byte[] be = Convert.FromHexString(hex);
+ // If highest bit of first byte is set, BigInteger.Parse with HexNumber can treat it as negative;
+ // we therefore construct positive BigInteger from big-endian bytes:
+ byte[] le = new byte[be.Length + 1];
+ for (int i = 0; i < be.Length; i++)
+ le[i] = be[be.Length - 1 - i];
+ le[le.Length - 1] = 0;
+ return new BigInteger(le);
+ }
+
+ // Simple HKDF-SHA256 implementation (RFC 5869)
+ static byte[] HkdfSha256(byte[] ikm, byte[] salt, byte[] info, int outLen)
+ {
+ // HKDF-Extract
+ byte[] prk;
+ using (var hmac = new HMACSHA256(salt ?? new byte[0]))
+ {
+ prk = hmac.ComputeHash(ikm);
+ }
+
+ // HKDF-Expand
+ int hashLen = 32;
+ int n = (outLen + hashLen - 1) / hashLen;
+ byte[] okm = new byte[outLen];
+ byte[] previous = Array.Empty();
+ using (var hmac = new HMACSHA256(prk))
+ {
+ int offset = 0;
+ for (byte i = 1; i <= n; i++)
+ {
+ // T(i) = HMAC-PRK( T(i-1) | info | i )
+ hmac.Initialize();
+ hmac.TransformBlock(previous, 0, previous.Length, null, 0);
+ if (info != null && info.Length > 0)
+ hmac.TransformBlock(info, 0, info.Length, null, 0);
+ byte[] counter = new byte[] { i };
+ hmac.TransformFinalBlock(counter, 0, 1);
+ previous = hmac.Hash!;
+ int toCopy = Math.Min(hashLen, outLen - offset);
+ Array.Copy(previous, 0, okm, offset, toCopy);
+ offset += toCopy;
+ }
+ }
+ return okm;
+ }
+ }
+#endif
+
+ ///
+ /// A class to assist with tracing crypto operations.
+ ///
+ public static class CryptoTrace
+ {
+ ///
+ /// Enabled crypto traces to stdout.
+ ///
+ public static bool Enabled { get; set; } = true;
+
+ ///
+ /// Starts a trace block.
+ ///
+ public static void Start(ConsoleColor color, string format, params object[] args)
+ {
+#if DEBUG
+ if (Enabled)
+ {
+ Console.ForegroundColor = color;
+ Console.Write("============ ");
+ Console.Write(format, args);
+ Console.WriteLine(" ============");
+ }
+#endif
+ }
+
+ ///
+ /// Finishes a trace block.
+ ///
+ public static void Finish(string format, params object[] args)
+ {
+#if DEBUG
+ if (Enabled)
+ {
+ Console.Write("============ ");
+ Console.Write(format, args);
+ Console.WriteLine(" Finished ============");
+ Console.ForegroundColor = ConsoleColor.White;
+ }
+#endif
+ }
+
+ ///
+ /// Writes a trace message.
+ ///
+ public static void Write(string format, params object[] args)
+ {
+#if DEBUG
+ if (Enabled)
+ {
+ Console.Write(format, args);
+ }
+#endif
+ }
+
+ ///
+ /// Writes a trace message.
+ ///
+ public static void WriteLine(string format, params object[] args)
+ {
+#if DEBUG
+ if (Enabled)
+ {
+ Console.WriteLine(format, args);
+ }
+#endif
+ }
+
+ ///
+ /// Returns a debug string for a key.
+ ///
+ public static string KeyToString(ArraySegment key)
+ {
+#if DEBUG
+ if (Enabled)
+ {
+ byte[] bytes = new byte[key.Count];
+ Buffer.BlockCopy(key.Array ?? [], key.Offset, bytes, 0, key.Count);
+ return KeyToString(bytes);
+ }
+ else
+ {
+ return String.Empty;
+ }
+#else
+ return String.Empty;
+#endif
+ }
+
+ ///
+ /// Returns a debug string for a key.
+ ///
+ public static string KeyToString(byte[] key)
+ {
+#if DEBUG
+ if (Enabled)
+ {
+ if (key == null || key.Length == 0)
+ {
+ return "Len=0:---";
+ }
+
+ byte checksum = 0;
+
+ foreach (var item in key)
+ {
+ checksum ^= item;
+ }
+
+ if (key.Length <= 16)
+ {
+ return "Len=" + key.Length.ToString(CultureInfo.InvariantCulture) +
+ ":" +
+ Utils.ToHexString(key) +
+ "=>XOR=" +
+ checksum.ToString(CultureInfo.InvariantCulture);
+ }
+
+ var text = Utils.ToHexString(key);
+ return $"Len={key.Length}:{text.Substring(0, 8)}...{text.Substring(text.Length - 8, 8)}=>XOR={checksum}";
+ }
+ else
+ {
+ return String.Empty;
+ }
+#else
+ return String.Empty;
+#endif
+ }
+ }
+}
diff --git a/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs b/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs
deleted file mode 100644
index 1aacf695c8..0000000000
--- a/Stack/Opc.Ua.Core/Security/Certificates/EccUtils.cs
+++ /dev/null
@@ -1,1280 +0,0 @@
-/* ========================================================================
- * 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.Security.Cryptography;
-using System.Security.Cryptography.X509Certificates;
-using Opc.Ua.Security.Certificates;
-#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
-{
- ///
- /// Defines functions to implement ECC cryptography.
- ///
- public static class EccUtils
- {
- ///
- /// The name of the NIST P-256 curve.
- ///
- public const string NistP256 = nameof(NistP256);
-
- ///
- /// The name of the NIST P-384 curve.
- ///
- public const string NistP384 = nameof(NistP384);
-
- ///
- /// The name of the BrainpoolP256r1 curve.
- ///
- public const string BrainpoolP256r1 = nameof(BrainpoolP256r1);
-
- ///
- /// The name of the BrainpoolP384r1 curve.
- ///
- public const string BrainpoolP384r1 = nameof(BrainpoolP384r1);
-
- internal const string NistP256KeyParameters = "06-08-2A-86-48-CE-3D-03-01-07";
- internal const string NistP384KeyParameters = "06-05-2B-81-04-00-22";
- internal const string BrainpoolP256r1KeyParameters = "06-09-2B-24-03-03-02-08-01-01-07";
- internal const string BrainpoolP384r1KeyParameters = "06-09-2B-24-03-03-02-08-01-01-0B";
-
- ///
- /// Returns true if the certificate is an ECC certificate.
- ///
- public static bool IsEccPolicy(string securityPolicyUri)
- {
- if (securityPolicyUri != null)
- {
- switch (securityPolicyUri)
- {
- case SecurityPolicies.ECC_nistP256:
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP256r1:
- case SecurityPolicies.ECC_brainpoolP384r1:
- case SecurityPolicies.ECC_curve25519:
- case SecurityPolicies.ECC_curve448:
- return true;
- default:
- return false;
- }
- }
-
- return false;
- }
-
- ///
- /// Returns the NodeId for the certificate type for the specified certificate.
- ///
- public static NodeId GetEccCertificateTypeId(X509Certificate2 certificate)
- {
- string keyAlgorithm = certificate.GetKeyAlgorithm();
- if (keyAlgorithm != Oids.ECPublicKey)
- {
- return NodeId.Null;
- }
-
- PublicKey encodedPublicKey = certificate.PublicKey;
- switch (BitConverter.ToString(encodedPublicKey.EncodedParameters.RawData))
- {
- // nistP256
- case NistP256KeyParameters:
- return ObjectTypeIds.EccNistP256ApplicationCertificateType;
- // nistP384
- case NistP384KeyParameters:
- return ObjectTypeIds.EccNistP384ApplicationCertificateType;
- // brainpoolP256r1
- case BrainpoolP256r1KeyParameters:
- return ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType;
- // brainpoolP384r1
- case BrainpoolP384r1KeyParameters:
- return ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType;
- default:
- return NodeId.Null;
- }
- }
-
- ///
- /// returns an ECCCurve if there is a matching supported curve for the provided
- /// certificate type id. if no supported ECC curve is found null is returned.
- ///
- /// the application certificatate type node id
- /// the ECCCurve, null if certificatate type id has no matching supported ECC curve
- public static ECCurve? GetCurveFromCertificateTypeId(NodeId certificateType)
- {
- ECCurve? curve = null;
-
- if (certificateType == ObjectTypeIds.EccApplicationCertificateType ||
- certificateType == ObjectTypeIds.EccNistP256ApplicationCertificateType)
- {
- curve = ECCurve.NamedCurves.nistP256;
- }
- else if (certificateType == ObjectTypeIds.EccNistP384ApplicationCertificateType)
- {
- curve = ECCurve.NamedCurves.nistP384;
- }
- else if (certificateType == ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType)
- {
- curve = ECCurve.NamedCurves.brainpoolP256r1;
- }
- else if (certificateType == ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType)
- {
- curve = ECCurve.NamedCurves.brainpoolP384r1;
- }
-#if CURVE25519
- else if (certificateType == ObjectTypeIds.EccCurve25519ApplicationCertificateType)
- {
- curve = default(ECCurve);
- }
- else if (certificateType == ObjectTypeIds.EccCurve448ApplicationCertificateType)
- {
- curve = default(ECCurve);
- }
-#endif
- return curve;
- }
-
- ///
- /// Returns the signature algorithm for the specified certificate.
- ///
- public static string GetECDsaQualifier(X509Certificate2 certificate)
- {
- if (X509Utils.IsECDsaSignature(certificate))
- {
- const string signatureQualifier = "ECDsa";
- PublicKey encodedPublicKey = certificate.PublicKey;
-
- // New values can be determined by running the dotted-decimal OID value
- // through BitConverter.ToString(CryptoConfig.EncodeOID(dottedDecimal));
-
- switch (BitConverter.ToString(encodedPublicKey.EncodedParameters.RawData))
- {
- case NistP256KeyParameters:
- return NistP256;
- case NistP384KeyParameters:
- return NistP384;
- case BrainpoolP256r1KeyParameters:
- return BrainpoolP256r1;
- case BrainpoolP384r1KeyParameters:
- return BrainpoolP384r1;
- default:
- return signatureQualifier;
- }
- }
- return string.Empty;
- }
-
- ///
- /// Returns the public key for the specified certificate.
- ///
- public static ECDsa GetPublicKey(X509Certificate2 certificate)
- {
- return GetPublicKey(certificate, out string[] _);
- }
-
- ///
- /// Returns the public key for the specified certificate and outputs the security policy uris.
- ///
- ///
- ///
- public static ECDsa GetPublicKey(
- X509Certificate2 certificate,
- out string[] securityPolicyUris)
- {
- securityPolicyUris = null;
-
- if (certificate == null)
- {
- return null;
- }
-
- string keyAlgorithm = certificate.GetKeyAlgorithm();
-
- if (keyAlgorithm != Oids.ECPublicKey)
- {
- return null;
- }
-
- const X509KeyUsageFlags kSufficientFlags =
- X509KeyUsageFlags.KeyAgreement |
- X509KeyUsageFlags.DigitalSignature |
- X509KeyUsageFlags.NonRepudiation |
- X509KeyUsageFlags.CrlSign |
- X509KeyUsageFlags.KeyCertSign;
-
- foreach (X509Extension extension in certificate.Extensions)
- {
- if (extension.Oid.Value == "2.5.29.15")
- {
- var kuExt = (X509KeyUsageExtension)extension;
-
- if ((kuExt.KeyUsages & kSufficientFlags) == 0)
- {
- return null;
- }
- }
- }
-
- PublicKey encodedPublicKey = certificate.PublicKey;
- string keyParameters = BitConverter.ToString(
- encodedPublicKey.EncodedParameters.RawData);
- byte[] keyValue = encodedPublicKey.EncodedKeyValue.RawData;
-
- var ecParameters = default(ECParameters);
-
- if (keyValue[0] != 0x04)
- {
- throw new InvalidOperationException("Only uncompressed points are supported");
- }
-
- byte[] x = new byte[(keyValue.Length - 1) / 2];
- byte[] y = new byte[x.Length];
-
- Buffer.BlockCopy(keyValue, 1, x, 0, x.Length);
- Buffer.BlockCopy(keyValue, 1 + x.Length, y, 0, y.Length);
-
- ecParameters.Q.X = x;
- ecParameters.Q.Y = y;
-
- // New values can be determined by running the dotted-decimal OID value
- // through BitConverter.ToString(CryptoConfig.EncodeOID(dottedDecimal));
-
- switch (keyParameters)
- {
- case NistP256KeyParameters:
- ecParameters.Curve = ECCurve.NamedCurves.nistP256;
- securityPolicyUris = [SecurityPolicies.ECC_nistP256];
- break;
- case NistP384KeyParameters:
- ecParameters.Curve = ECCurve.NamedCurves.nistP384;
- securityPolicyUris = [SecurityPolicies.ECC_nistP384, SecurityPolicies
- .ECC_nistP256];
- break;
- case BrainpoolP256r1KeyParameters:
- ecParameters.Curve = ECCurve.NamedCurves.brainpoolP256r1;
- securityPolicyUris = [SecurityPolicies.ECC_brainpoolP256r1];
- break;
- case BrainpoolP384r1KeyParameters:
- ecParameters.Curve = ECCurve.NamedCurves.brainpoolP384r1;
- securityPolicyUris = [SecurityPolicies.ECC_brainpoolP384r1, SecurityPolicies
- .ECC_brainpoolP256r1];
- break;
- default:
- throw new NotImplementedException(keyParameters);
- }
-
- return ECDsa.Create(ecParameters);
- }
-
- ///
- /// Returns the length of a ECDsa signature of a digest.
- ///
- ///
- public static int GetSignatureLength(X509Certificate2 signingCertificate)
- {
- if (signingCertificate == null)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "No public key for certificate.");
- }
- using ECDsa publicKey =
- GetPublicKey(signingCertificate)
- ?? throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "No public key for certificate.");
-
- return publicKey.KeySize / 4;
- }
-
- ///
- /// Returns the hash algorithm for the specified security policy.
- ///
- /// is null.
- ///
- public static HashAlgorithmName GetSignatureAlgorithmName(string securityPolicyUri)
- {
- if (securityPolicyUri == null)
- {
- throw new ArgumentNullException(nameof(securityPolicyUri));
- }
-
- switch (securityPolicyUri)
- {
- case SecurityPolicies.ECC_nistP256:
- case SecurityPolicies.ECC_brainpoolP256r1:
- case SecurityPolicies.ECC_curve25519:
- case SecurityPolicies.ECC_curve448:
- return HashAlgorithmName.SHA256;
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP384r1:
- return HashAlgorithmName.SHA384;
- default:
- throw ServiceResultException.Unexpected(
- "Unexpected security policy URI for ECC: {0}", securityPolicyUri);
- }
- }
-
- ///
- /// Computes an ECDSA signature.
- ///
- public static byte[] Sign(
- ArraySegment dataToSign,
- X509Certificate2 signingCertificate,
- string securityPolicyUri)
- {
- HashAlgorithmName algorithm = GetSignatureAlgorithmName(securityPolicyUri);
- return Sign(dataToSign, signingCertificate, algorithm);
- }
-
- ///
- /// Computes an ECDSA signature.
- ///
- ///
- public static byte[] Sign(
- ArraySegment dataToSign,
- X509Certificate2 signingCertificate,
- HashAlgorithmName algorithm)
- {
-#if CURVE25519
- var publicKey = signingCertificate.BcCertificate.GetPublicKey();
-
- if (publicKey is Ed25519PublicKeyParameters)
- {
- var signer = new Ed25519Signer();
-
- signer.Init(true, signingCertificate.BcPrivateKey);
- signer.BlockUpdate(dataToSign.Array, dataToSign.Offset, dataToSign.Count);
- byte[] signature = signer.GenerateSignature();
-#if DEBUG
- var verifier = new Ed25519Signer();
-
- verifier.Init(false, signingCertificate.BcCertificate.GetPublicKey());
- verifier.BlockUpdate(dataToSign.Array, dataToSign.Offset, dataToSign.Count);
-
- if (!verifier.VerifySignature(signature))
- {
- throw new ServiceResultException(StatusCodes.BadSecurityChecksFailed, "Could not verify signature.");
- }
-#endif
- return signature;
- }
-
- if (publicKey is Ed448PublicKeyParameters)
- {
- var signer = new Ed448Signer(new byte[32]);
-
- signer.Init(true, signingCertificate.BcPrivateKey);
- signer.BlockUpdate(dataToSign.Array, dataToSign.Offset, dataToSign.Count);
- byte[] signature = signer.GenerateSignature();
-#if DEBUG
- var verifier = new Ed448Signer(new byte[32]);
-
- verifier.Init(false, signingCertificate.BcCertificate.GetPublicKey());
- verifier.BlockUpdate(dataToSign.Array, dataToSign.Offset, dataToSign.Count);
-
- if (!verifier.VerifySignature(signature))
- {
- throw new ServiceResultException(StatusCodes.BadSecurityChecksFailed, "Could not verify signature.");
- }
-#endif
- return signature;
- }
-#endif
- ECDsa senderPrivateKey =
- signingCertificate.GetECDsaPrivateKey()
- ?? throw new ServiceResultException(
- StatusCodes.BadCertificateInvalid,
- "Missing private key needed for create a signature.");
-
- using (senderPrivateKey)
- {
- byte[] signature = senderPrivateKey.SignData(
- dataToSign.Array,
- dataToSign.Offset,
- dataToSign.Count,
- algorithm);
-
-#if DEBUGxxx
- using (ECDsa ecdsa = EccUtils.GetPublicKey(new X509Certificate2(signingCertificate.RawData)))
- {
- if (!ecdsa.VerifyData(dataToSign.Array, dataToSign.Offset, dataToSign.Count, signature, algorithm))
- {
- throw new ServiceResultException(
- StatusCodes.BadSecurityChecksFailed,
- "Could not verify signature.");
- }
- }
-#endif
-
- return signature;
- }
- }
-
- ///
- /// Verifies a ECDsa signature.
- ///
- public static bool Verify(
- ArraySegment dataToVerify,
- byte[] signature,
- X509Certificate2 signingCertificate,
- string securityPolicyUri)
- {
- return Verify(
- dataToVerify,
- signature,
- signingCertificate,
- GetSignatureAlgorithmName(securityPolicyUri));
- }
-
- ///
- /// Verifies a ECDsa signature.
- ///
- public static bool Verify(
- ArraySegment dataToVerify,
- byte[] signature,
- X509Certificate2 signingCertificate,
- HashAlgorithmName algorithm)
- {
-#if CURVE25519
- var publicKey = signingCertificate.BcCertificate.GetPublicKey();
-
- if (publicKey is Ed25519PublicKeyParameters)
- {
- var verifier = new Ed25519Signer();
-
- verifier.Init(false, signingCertificate.BcCertificate.GetPublicKey());
- verifier.BlockUpdate(dataToVerify.Array, dataToVerify.Offset, dataToVerify.Count);
-
- if (!verifier.VerifySignature(signature))
- {
- return false;
- }
-
- return true;
- }
-
- if (publicKey is Ed448PublicKeyParameters)
- {
- var verifier = new Ed448Signer(new byte[32]);
-
- verifier.Init(false, signingCertificate.BcCertificate.GetPublicKey());
- verifier.BlockUpdate(dataToVerify.Array, dataToVerify.Offset, dataToVerify.Count);
-
- if (!verifier.VerifySignature(signature))
- {
- return false;
- }
-
- return true;
- }
-#endif
- using ECDsa ecdsa = GetPublicKey(signingCertificate);
- return ecdsa.VerifyData(
- dataToVerify.Array,
- dataToVerify.Offset,
- dataToVerify.Count,
- signature,
- algorithm);
- }
- }
-
- ///
- /// Utility class for encrypting and decrypting secrets using Elliptic Curve Cryptography (ECC).
- ///
- public class EncryptedSecret
- {
- ///
- /// Create secret
- ///
- public EncryptedSecret(
- IServiceMessageContext context,
- string securityPolicyUri,
- X509Certificate2Collection senderIssuerCertificates,
- X509Certificate2 receiverCertificate,
- Nonce receiverNonce,
- X509Certificate2 senderCertificate,
- Nonce senderNonce,
- CertificateValidator validator = null,
- bool doNotEncodeSenderCertificate = false)
- {
- SenderCertificate = senderCertificate;
- SenderIssuerCertificates = senderIssuerCertificates;
- DoNotEncodeSenderCertificate = doNotEncodeSenderCertificate;
- SenderNonce = senderNonce;
- ReceiverNonce = receiverNonce;
- ReceiverCertificate = receiverCertificate;
- Validator = validator;
- SecurityPolicyUri = securityPolicyUri;
- Context = context;
- }
-
- ///
- /// Gets or sets the X.509 certificate of the sender.
- ///
- public X509Certificate2 SenderCertificate { get; private set; }
-
- ///
- /// Gets or sets the collection of X.509 certificates of the sender's issuer.
- ///
- public X509Certificate2Collection SenderIssuerCertificates { get; private set; }
-
- ///
- /// Gets or sets a value indicating whether the sender's certificate should not be encoded.
- ///
- public bool DoNotEncodeSenderCertificate { get; }
-
- ///
- /// Gets or sets the nonce of the sender.
- ///
- public Nonce SenderNonce { get; private set; }
-
- ///
- /// Gets or sets the nonce of the receiver.
- ///
- public Nonce ReceiverNonce { get; }
-
- ///
- /// Gets or sets the X.509 certificate of the receiver.
- ///
- public X509Certificate2 ReceiverCertificate { get; }
-
- ///
- /// Gets or sets the certificate validator.
- ///
- public CertificateValidator Validator { get; }
-
- ///
- /// Gets or sets the security policy URI.
- ///
- public string SecurityPolicyUri { get; private set; }
-
- ///
- /// Service message context to use
- ///
- public IServiceMessageContext Context { get; }
-
- ///
- /// Encrypts a secret using the specified nonce, encrypting key, and initialization vector (IV).
- ///
- /// The secret to encrypt.
- /// The nonce to use for encryption.
- /// The key to use for encryption.
- /// The initialization vector to use for encryption.
- /// The encrypted secret.
- ///
- private byte[] EncryptSecret(
- byte[] secret,
- byte[] nonce,
- byte[] encryptingKey,
- byte[] iv)
- {
-#if CURVE25519
- bool useAuthenticatedEncryption = false;
- if (SenderCertificate.BcCertificate.GetPublicKey() is Ed25519PublicKeyParameters
- || SenderCertificate.BcCertificate.GetPublicKey() is Ed448PublicKeyParameters)
- {
- useAuthenticatedEncryption = true;
- }
-#endif
- byte[] dataToEncrypt = null;
-
- using (var encoder = new BinaryEncoder(Context))
- {
- encoder.WriteByteString(null, nonce);
- encoder.WriteByteString(null, secret);
-
- // add padding.
- int paddingSize = iv.Length - ((encoder.Position + 2) % iv.Length);
- paddingSize %= iv.Length;
-
- if (secret.Length + paddingSize < iv.Length)
- {
- paddingSize += iv.Length;
- }
-
- for (int ii = 0; ii < paddingSize; ii++)
- {
- encoder.WriteByte(null, (byte)(paddingSize & 0xFF));
- }
-
- encoder.WriteUInt16(null, (ushort)paddingSize);
-
- dataToEncrypt = encoder.CloseAndReturnBuffer();
- }
-#if CURVE25519
- if (useAuthenticatedEncryption)
- {
- return EncryptWithChaCha20Poly1305(encryptingKey, iv, dataToEncrypt);
- }
-#endif
- using (var aes = Aes.Create())
- {
- aes.Mode = CipherMode.CBC;
- aes.Padding = PaddingMode.None;
- aes.Key = encryptingKey;
- aes.IV = iv;
-
-#pragma warning disable CA5401 // Symmetric encryption uses non-default initialization vector, which could be potentially repeatable
- using ICryptoTransform encryptor = aes.CreateEncryptor();
-#pragma warning restore CA5401 // Symmetric encryption uses non-default initialization vector, which could be potentially repeatable
- if (dataToEncrypt.Length % encryptor.InputBlockSize != 0)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Input data is not an even number of encryption blocks.");
- }
-
- encryptor.TransformBlock(dataToEncrypt, 0, dataToEncrypt.Length, dataToEncrypt, 0);
- }
-
- return dataToEncrypt;
- }
-
-#if CURVE25519
- ///
- /// Encrypts the given data using the ChaCha20Poly1305 algorithm with the provided key and initialization vector (IV).
- ///
- /// The key used for encryption.
- /// The initialization vector used for encryption.
- /// The data to be encrypted.
- /// The encrypted data.
- private static byte[] EncryptWithChaCha20Poly1305(byte[] encryptingKey, byte[] iv, byte[] dataToEncrypt)
- {
- Utils.Trace($"EncryptKey={Utils.ToHexString(encryptingKey)}");
- Utils.Trace($"EncryptIV={Utils.ToHexString(iv)}");
-
- int signatureLength = 16;
-
- AeadParameters parameters = new AeadParameters(
- new KeyParameter(encryptingKey),
- signatureLength * 8,
- iv,
- null);
-
- ChaCha20Poly1305 encryptor = new ChaCha20Poly1305();
- encryptor.Init(true, parameters);
-
- byte[] ciphertext = new byte[encryptor.GetOutputSize(dataToEncrypt.Length)];
- int length = encryptor.ProcessBytes(dataToEncrypt, 0, dataToEncrypt.Length, ciphertext, 0);
- length += encryptor.DoFinal(ciphertext, length);
-
- if (ciphertext.Length != length)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- $"CipherText not the expected size. [{ciphertext.Length} != {length}]");
- }
-
- return ciphertext;
- }
-
- ///
- /// Decrypts the given data using the ChaCha20Poly1305 algorithm with the provided key and initialization vector (IV).
- ///
- /// The key used for encryption.
- /// The initialization vector used for encryption.
- /// The data to be decrypted.
- /// The offset in the data to start decrypting from.
- /// The number of bytes to decrypt.
- /// An containing the decrypted data.
- /// Thrown if the plaintext is not the expected size or too short, or if the nonce is invalid.
- private ArraySegment DecryptWithChaCha20Poly1305(
- byte[] encryptingKey,
- byte[] iv,
- byte[] dataToDecrypt,
- int offset,
- int count)
- {
- Utils.Trace($"EncryptKey={Utils.ToHexString(encryptingKey)}");
- Utils.Trace($"EncryptIV={Utils.ToHexString(iv)}");
-
- int signatureLength = 16;
-
- AeadParameters parameters = new AeadParameters(
- new KeyParameter(encryptingKey),
- signatureLength * 8,
- iv,
- null);
-
- ChaCha20Poly1305 decryptor = new ChaCha20Poly1305();
- decryptor.Init(false, parameters);
-
- byte[] plaintext = new byte[decryptor.GetOutputSize(count)];
- int length = decryptor.ProcessBytes(dataToDecrypt, offset, count, plaintext, 0);
- length += decryptor.DoFinal(plaintext, length);
-
- if (plaintext.Length != length || plaintext.Length < iv.Length)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- $"PlainText not the expected size or too short. [{count} != {length}]");
- }
-
- ushort paddingSize = plaintext[length - 1];
- paddingSize <<= 8;
- paddingSize += plaintext[length - 2];
-
- int notvalid = (paddingSize < length) ? 0 : 1;
- int start = length - paddingSize - 2;
-
- for (int ii = 0; ii < length - 2 && ii < paddingSize; ii++)
- {
- if (start < 0 || start + ii >= plaintext.Length)
- {
- notvalid |= 1;
- continue;
- }
-
- notvalid |= plaintext[start + ii] ^ (paddingSize & 0xFF);
- }
-
- if (notvalid != 0)
- {
- throw new ServiceResultException(StatusCodes.BadNonceInvalid);
- }
-
- return new ArraySegment(plaintext, 0, start);
- }
-#endif
-
- ///
- /// Decrypts the specified data using the provided encrypting key and initialization vector (IV).
- ///
- /// The data to decrypt.
- /// The offset in the data to start decrypting from.
- /// The number of bytes to decrypt.
- /// The key to use for decryption.
- /// The initialization vector to use for decryption.
- /// The decrypted data.
- /// Thrown if the input data is not an even number of encryption blocks or if the nonce is invalid.
- private static ArraySegment DecryptSecret(
- byte[] dataToDecrypt,
- int offset,
- int count,
- byte[] encryptingKey,
- byte[] iv)
- {
-#if CURVE25519
- bool useAuthenticatedEncryption = false;
- if (SenderCertificate.BcCertificate.GetPublicKey() is Ed25519PublicKeyParameters
- || SenderCertificate.BcCertificate.GetPublicKey() is Ed448PublicKeyParameters)
- {
- useAuthenticatedEncryption = true;
- }
- if (useAuthenticatedEncryption)
- {
- return DecryptWithChaCha20Poly1305(encryptingKey, iv, dataToDecrypt, offset, count);
- }
-#endif
- using (var aes = Aes.Create())
- {
- aes.Mode = CipherMode.CBC;
- aes.Padding = PaddingMode.None;
- aes.Key = encryptingKey;
- aes.IV = iv;
-
- using ICryptoTransform decryptor = aes.CreateDecryptor();
- if (count % decryptor.InputBlockSize != 0)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Input data is not an even number of encryption blocks.");
- }
-
- decryptor.TransformBlock(dataToDecrypt, offset, count, dataToDecrypt, offset);
- }
-
- ushort paddingSize = dataToDecrypt[offset + count - 1];
- paddingSize <<= 8;
- paddingSize += dataToDecrypt[offset + count - 2];
-
- int notvalid = paddingSize < count ? 0 : 1;
- int start = offset + count - paddingSize - 2;
-
- for (int ii = 0; ii < count - 2 && ii < paddingSize; ii++)
- {
- if (start < 0 || start + ii >= dataToDecrypt.Length)
- {
- notvalid |= 1;
- continue;
- }
-
- notvalid |= dataToDecrypt[start + ii] ^ (paddingSize & 0xFF);
- }
-
- if (notvalid != 0)
- {
- throw new ServiceResultException(StatusCodes.BadNonceInvalid);
- }
-
- return new ArraySegment(dataToDecrypt, offset, count - paddingSize);
- }
-
- private static readonly byte[] s_label = System.Text.Encoding.UTF8.GetBytes("opcua-secret");
-
- ///
- /// Creates the encrypting key and initialization vector (IV) for Elliptic Curve Cryptography (ECC) encryption or decryption.
- ///
- /// The security policy URI.
- /// The sender nonce.
- /// The receiver nonce.
- /// if set to true, creates the keys for decryption; otherwise, creates the keys for encryption.
- /// The encrypting key.
- /// The initialization vector (IV).
- private static void CreateKeysForEcc(
- string securityPolicyUri,
- Nonce senderNonce,
- Nonce receiverNonce,
- bool forDecryption,
- out byte[] encryptingKey,
- out byte[] iv)
- {
- int encryptingKeySize;
- int blockSize;
- HashAlgorithmName algorithmName;
-
- switch (securityPolicyUri)
- {
- case SecurityPolicies.ECC_nistP256:
- case SecurityPolicies.ECC_brainpoolP256r1:
- blockSize = 16;
- encryptingKeySize = 16;
- algorithmName = HashAlgorithmName.SHA256;
- break;
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP384r1:
- encryptingKeySize = 32;
- blockSize = 16;
- algorithmName = HashAlgorithmName.SHA384;
- break;
- case SecurityPolicies.ECC_curve25519:
- case SecurityPolicies.ECC_curve448:
- encryptingKeySize = 32;
- blockSize = 12;
- algorithmName = HashAlgorithmName.SHA256;
- break;
- default:
- encryptingKeySize = 32;
- blockSize = 16;
- algorithmName = HashAlgorithmName.SHA256;
- break;
- }
-
- encryptingKey = new byte[encryptingKeySize];
- iv = new byte[blockSize];
-
- byte[] keyLength = BitConverter.GetBytes((ushort)(encryptingKeySize + blockSize));
- byte[] salt = Utils.Append(keyLength, s_label, senderNonce.Data, receiverNonce.Data);
-
- byte[] keyData;
- if (forDecryption)
- {
- keyData = receiverNonce.DeriveKey(
- senderNonce,
- salt,
- algorithmName,
- encryptingKeySize + blockSize);
- }
- else
- {
- keyData = senderNonce.DeriveKey(
- receiverNonce,
- salt,
- algorithmName,
- encryptingKeySize + blockSize);
- }
-
- Buffer.BlockCopy(keyData, 0, encryptingKey, 0, encryptingKey.Length);
- Buffer.BlockCopy(keyData, encryptingKeySize, iv, 0, iv.Length);
- }
-
- ///
- /// Encrypts a secret using the specified nonce.
- ///
- /// The secret to encrypt.
- /// The nonce to use for encryption.
- /// The encrypted secret.
- public byte[] Encrypt(byte[] secret, byte[] nonce)
- {
- byte[] encryptingKey = null;
- byte[] iv = null;
- byte[] message = null;
- int lengthPosition = 0;
-
- int signatureLength = EccUtils.GetSignatureLength(SenderCertificate);
-
- using (var encoder = new BinaryEncoder(Context))
- {
- // write header.
- encoder.WriteNodeId(null, DataTypeIds.EccEncryptedSecret);
- encoder.WriteByte(null, (byte)ExtensionObjectEncoding.Binary);
-
- lengthPosition = encoder.Position;
- encoder.WriteUInt32(null, 0);
-
- encoder.WriteString(null, SecurityPolicyUri);
-
- byte[] senderCertificate = null;
-
- if (!DoNotEncodeSenderCertificate)
- {
- senderCertificate = SenderCertificate.RawData;
-
- if (SenderIssuerCertificates != null && SenderIssuerCertificates.Count > 0)
- {
- int blobSize = senderCertificate.Length;
-
- foreach (X509Certificate2 issuer in SenderIssuerCertificates)
- {
- blobSize += issuer.RawData.Length;
- }
-
- byte[] blob = new byte[blobSize];
- Buffer.BlockCopy(senderCertificate, 0, blob, 0, senderCertificate.Length);
-
- int pos = senderCertificate.Length;
-
- foreach (X509Certificate2 issuer in SenderIssuerCertificates)
- {
- byte[] data = issuer.RawData;
- Buffer.BlockCopy(data, 0, blob, pos, data.Length);
- pos += data.Length;
- }
-
- senderCertificate = blob;
- }
- }
-
- encoder.WriteByteString(null, senderCertificate);
- encoder.WriteDateTime(null, DateTime.UtcNow);
-
- byte[] senderNonce = SenderNonce.Data;
- byte[] receiverNonce = ReceiverNonce.Data;
-
- encoder.WriteUInt16(null, (ushort)(senderNonce.Length + receiverNonce.Length + 8));
- encoder.WriteByteString(null, senderNonce);
- encoder.WriteByteString(null, receiverNonce);
-
- // create keys.
- if (EccUtils.IsEccPolicy(SecurityPolicyUri))
- {
- CreateKeysForEcc(
- SecurityPolicyUri,
- SenderNonce,
- ReceiverNonce,
- false,
- out encryptingKey,
- out iv);
- }
-
- // encrypt secret,
- byte[] encryptedData = EncryptSecret(secret, nonce, encryptingKey, iv);
-
- // append encrypted secret.
- for (int ii = 0; ii < encryptedData.Length; ii++)
- {
- encoder.WriteByte(null, encryptedData[ii]);
- }
-
- // save space for signature.
- for (int ii = 0; ii < signatureLength; ii++)
- {
- encoder.WriteByte(null, 0);
- }
-
- message = encoder.CloseAndReturnBuffer();
- }
-
- int length = message.Length - lengthPosition - 4;
-
- message[lengthPosition++] = (byte)(length & 0xFF);
- message[lengthPosition++] = (byte)((length & 0xFF00) >> 8);
- message[lengthPosition++] = (byte)((length & 0xFF0000) >> 16);
- message[lengthPosition++] = (byte)((length & 0xFF000000) >> 24);
-
- // get the algorithm used for the signature.
- HashAlgorithmName signatureAlgorithm;
- switch (SecurityPolicyUri)
- {
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP384r1:
- signatureAlgorithm = HashAlgorithmName.SHA384;
- break;
- default:
- signatureAlgorithm = HashAlgorithmName.SHA256;
- break;
- }
-
- var dataToSign = new ArraySegment(message, 0, message.Length - signatureLength);
- byte[] signature = EccUtils.Sign(dataToSign, SenderCertificate, signatureAlgorithm);
- Buffer.BlockCopy(
- signature,
- 0,
- message,
- message.Length - signatureLength,
- signatureLength);
- return message;
- }
-
- ///
- /// Verifies the header for an ECC encrypted message and returns the encrypted data.
- ///
- /// The data to decrypt.
- /// The earliest time allowed for the message signing time.
- /// The telemetry context to use to create obvservability instruments
- /// The encrypted data.
- ///
- private ArraySegment VerifyHeaderForEcc(
- ArraySegment dataToDecrypt,
- DateTime earliestTime,
- ITelemetryContext telemetry)
- {
- using var decoder = new BinaryDecoder(
- dataToDecrypt.Array,
- dataToDecrypt.Offset,
- dataToDecrypt.Count,
- Context);
- NodeId typeId = decoder.ReadNodeId(null);
-
- if (typeId != DataTypeIds.EccEncryptedSecret)
- {
- throw new ServiceResultException(StatusCodes.BadDataTypeIdUnknown);
- }
-
- var encoding = (ExtensionObjectEncoding)decoder.ReadByte(null);
-
- if (encoding != ExtensionObjectEncoding.Binary)
- {
- throw new ServiceResultException(StatusCodes.BadDataEncodingUnsupported);
- }
-
- uint length = decoder.ReadUInt32(null);
-
- // get the start of data.
- int startOfData = decoder.Position + dataToDecrypt.Offset;
-
- SecurityPolicyUri = decoder.ReadString(null);
-
- if (!EccUtils.IsEccPolicy(SecurityPolicyUri))
- {
- throw new ServiceResultException(StatusCodes.BadSecurityPolicyRejected);
- }
-
- // get the algorithm used for the signature.
- HashAlgorithmName signatureAlgorithm;
-
- switch (SecurityPolicyUri)
- {
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP384r1:
- signatureAlgorithm = HashAlgorithmName.SHA384;
- break;
- default:
- signatureAlgorithm = HashAlgorithmName.SHA256;
- break;
- }
-
- // extract the send certificate and any chain.
- byte[] senderCertificate = decoder.ReadByteString(null);
-
- if (senderCertificate == null || senderCertificate.Length == 0)
- {
- if (SenderCertificate == null)
- {
- throw new ServiceResultException(StatusCodes.BadCertificateInvalid);
- }
- }
- else
- {
- X509Certificate2Collection senderCertificateChain = Utils.ParseCertificateChainBlob(
- senderCertificate,
- telemetry);
-
- SenderCertificate = senderCertificateChain[0];
- SenderIssuerCertificates = [];
-
- for (int ii = 1; ii < senderCertificateChain.Count; ii++)
- {
- SenderIssuerCertificates.Add(senderCertificateChain[ii]);
- }
-
- // validate the sender.
- Validator?.ValidateAsync(senderCertificateChain, default).GetAwaiter().GetResult();
- }
-
- // extract the send certificate and any chain.
- DateTime signingTime = decoder.ReadDateTime(null);
-
- if (signingTime < earliestTime)
- {
- throw new ServiceResultException(StatusCodes.BadInvalidTimestamp);
- }
-
- // extract the policy header.
- ushort headerLength = decoder.ReadUInt16(null);
-
- if (headerLength == 0 || headerLength > length)
- {
- throw new ServiceResultException(StatusCodes.BadDecodingError);
- }
-
- // read the policy header.
- byte[] senderPublicKey = decoder.ReadByteString(null);
- byte[] receiverPublicKey = decoder.ReadByteString(null);
-
- if (headerLength != senderPublicKey.Length + receiverPublicKey.Length + 8)
- {
- throw new ServiceResultException(
- StatusCodes.BadDecodingError,
- "Unexpected policy header length");
- }
-
- int startOfEncryption = decoder.Position;
-
- SenderNonce = Nonce.CreateNonce(SecurityPolicyUri, senderPublicKey);
-
- if (!Utils.IsEqual(receiverPublicKey, ReceiverNonce.Data))
- {
- throw new ServiceResultException(
- StatusCodes.BadDecodingError,
- "Unexpected receiver nonce.");
- }
-
- // check the signature.
- int signatureLength = EccUtils.GetSignatureLength(SenderCertificate);
-
- if (signatureLength >= length)
- {
- throw new ServiceResultException(StatusCodes.BadDecodingError);
- }
-
- byte[] signature = new byte[signatureLength];
- Buffer.BlockCopy(
- dataToDecrypt.Array,
- startOfData + (int)length - signatureLength,
- signature,
- 0,
- signatureLength);
-
- var dataToSign = new ArraySegment(
- dataToDecrypt.Array,
- 0,
- startOfData + (int)length - signatureLength);
-
- if (!EccUtils.Verify(dataToSign, signature, SenderCertificate, signatureAlgorithm))
- {
- throw new ServiceResultException(
- StatusCodes.BadSecurityChecksFailed,
- "Could not verify signature.");
- }
-
- // extract the encrypted data.
- return new ArraySegment(
- dataToDecrypt.Array,
- startOfEncryption,
- (int)length - (startOfEncryption - startOfData + signatureLength));
- }
-
- ///
- /// Decrypts the specified data using the ECC algorithm.
- ///
- /// The earliest time allowed for the message.
- /// The expected nonce value.
- /// The data to decrypt.
- /// The offset of the data to decrypt.
- /// The number of bytes to decrypt.
- /// The telemetry context to use to create obvservability instruments
- /// The decrypted data.
- ///
- public byte[] Decrypt(
- DateTime earliestTime,
- byte[] expectedNonce,
- byte[] data,
- int offset,
- int count,
- ITelemetryContext telemetry)
- {
- ArraySegment dataToDecrypt = VerifyHeaderForEcc(
- new ArraySegment(data, offset, count),
- earliestTime,
- telemetry);
-
- CreateKeysForEcc(
- SecurityPolicyUri,
- SenderNonce,
- ReceiverNonce,
- true,
- out byte[] encryptingKey,
- out byte[] iv);
-
- ArraySegment plainText = DecryptSecret(
- dataToDecrypt.Array,
- dataToDecrypt.Offset,
- dataToDecrypt.Count,
- encryptingKey,
- iv);
-
- using var decoder = new BinaryDecoder(
- plainText.Array,
- plainText.Offset,
- plainText.Count,
- Context);
- byte[] actualNonce = decoder.ReadByteString(null);
-
- if (expectedNonce != null && expectedNonce.Length > 0)
- {
- int notvalid = expectedNonce.Length == actualNonce.Length ? 0 : 1;
-
- for (int ii = 0; ii < expectedNonce.Length && ii < actualNonce.Length; ii++)
- {
- notvalid |= expectedNonce[ii] ^ actualNonce[ii];
- }
-
- if (notvalid != 0)
- {
- throw new ServiceResultException(StatusCodes.BadNonceInvalid);
- }
- }
-
- return decoder.ReadByteString(null);
- }
- }
-}
diff --git a/Stack/Opc.Ua.Core/Security/Certificates/EncryptedSecret.cs b/Stack/Opc.Ua.Core/Security/Certificates/EncryptedSecret.cs
new file mode 100644
index 0000000000..fbda7398d5
--- /dev/null
+++ b/Stack/Opc.Ua.Core/Security/Certificates/EncryptedSecret.cs
@@ -0,0 +1,577 @@
+/* Copyright (c) 1996-2022 The OPC Foundation. All rights reserved.
+ The source code in this file is covered under a dual-license scenario:
+ - RCL: for OPC Foundation Corporate Members in good-standing
+ - GPL V2: everybody else
+ RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
+ GNU General Public License as published by the Free Software Foundation;
+ version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
+ This source code is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+using System;
+using System.IO;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using Opc.Ua.Bindings;
+#if CURVE25519
+using Org.BouncyCastle.Crypto.Modes;
+using Org.BouncyCastle.Crypto.Parameters;
+#endif
+
+namespace Opc.Ua
+{
+ ///
+ /// Utility class for encrypting and decrypting secrets using Elliptic Curve Cryptography (ECC).
+ ///
+ public class EncryptedSecret
+ {
+ ///
+ /// Create secret
+ ///
+ public EncryptedSecret(
+ IServiceMessageContext context,
+ string securityPolicyUri,
+ X509Certificate2Collection senderIssuerCertificates,
+ X509Certificate2 receiverCertificate,
+ Nonce receiverNonce,
+ X509Certificate2 senderCertificate,
+ Nonce senderNonce,
+ CertificateValidator validator = null,
+ bool doNotEncodeSenderCertificate = false)
+ {
+ SenderCertificate = senderCertificate;
+ SenderIssuerCertificates = senderIssuerCertificates;
+ DoNotEncodeSenderCertificate = doNotEncodeSenderCertificate;
+ SenderNonce = senderNonce;
+ ReceiverNonce = receiverNonce;
+ ReceiverCertificate = receiverCertificate;
+ Validator = validator;
+ SecurityPolicy = SecurityPolicies.GetInfo(securityPolicyUri);
+ Context = context;
+
+ if (SecurityPolicy == null)
+ {
+ throw new ArgumentException($"Cannot resolve SecurityPolicy '{securityPolicyUri}'.", nameof(securityPolicyUri));
+ }
+ }
+
+ ///
+ /// Gets or sets the X.509 certificate of the sender.
+ ///
+ public X509Certificate2 SenderCertificate { get; private set; }
+
+ ///
+ /// Gets or sets the collection of X.509 certificates of the sender's issuer.
+ ///
+ public X509Certificate2Collection SenderIssuerCertificates { get; private set; }
+
+ ///
+ /// Gets or sets a value indicating whether the sender's certificate should not be encoded.
+ ///
+ public bool DoNotEncodeSenderCertificate { get; }
+
+ ///
+ /// Gets or sets the nonce of the sender.
+ ///
+ public Nonce SenderNonce { get; private set; }
+
+ ///
+ /// Gets or sets the nonce of the receiver.
+ ///
+ public Nonce ReceiverNonce { get; }
+
+ ///
+ /// Gets or sets the X.509 certificate of the receiver.
+ ///
+ public X509Certificate2 ReceiverCertificate { get; }
+
+ ///
+ /// Gets or sets the certificate validator.
+ ///
+ public CertificateValidator Validator { get; }
+
+ ///
+ /// Gets or sets the security policy.
+ ///
+ public SecurityPolicyInfo SecurityPolicy { get; private set; }
+
+ ///
+ /// Service message context to use
+ ///
+ public IServiceMessageContext Context { get; }
+
+ private static readonly byte[] s_secretLabel = System.Text.Encoding.UTF8.GetBytes("opcua-secret");
+
+ ///
+ /// Creates the encrypting key and initialization vector (IV) for Elliptic Curve Cryptography (ECC) encryption or decryption.
+ ///
+ private static void CreateKeysForEcc(
+ SecurityPolicyInfo securityPolicy,
+ Nonce localNonce,
+ Nonce remoteNonce,
+ bool forDecryption,
+ out byte[] encryptingKey,
+ out byte[] iv)
+ {
+ CryptoTrace.Start(ConsoleColor.Blue, $"EncryptedSecret {((forDecryption) ? "DECRYPT" : "ENCRYPT")}");
+ CryptoTrace.WriteLine($"SecurityPolicy={securityPolicy.Name}");
+ CryptoTrace.WriteLine($"LocalNonce={CryptoTrace.KeyToString(localNonce?.Data)}");
+ CryptoTrace.WriteLine($"RemoteNonce={CryptoTrace.KeyToString(remoteNonce?.Data)}");
+
+ int encryptingKeySize = securityPolicy.SymmetricEncryptionKeyLength;
+ int blockSize = securityPolicy.InitializationVectorLength;
+
+ encryptingKey = new byte[encryptingKeySize];
+ iv = new byte[blockSize];
+
+ byte[] secret = localNonce.GenerateSecret(remoteNonce, null);
+ byte[] keyLength = BitConverter.GetBytes((ushort)(encryptingKeySize + blockSize));
+
+ byte[] salt = Utils.Append(
+ keyLength,
+ s_secretLabel,
+ forDecryption ? remoteNonce.Data : localNonce.Data,
+ forDecryption ? localNonce.Data : remoteNonce.Data);
+
+ byte[] keyData = localNonce.DeriveKeyData(
+ secret,
+ salt,
+ securityPolicy.KeyDerivationAlgorithm,
+ encryptingKeySize + blockSize);
+
+ Buffer.BlockCopy(keyData, 0, encryptingKey, 0, encryptingKey.Length);
+ Buffer.BlockCopy(keyData, encryptingKeySize, iv, 0, iv.Length);
+
+ Console.ForegroundColor = ConsoleColor.Blue;
+ CryptoTrace.WriteLine($"EncryptingKey={CryptoTrace.KeyToString(encryptingKey)}");
+ CryptoTrace.WriteLine($"IV={CryptoTrace.KeyToString(iv)}");
+ CryptoTrace.Finish("EncryptedSecret");
+ }
+
+ ///
+ /// Encrypts a secret using the specified nonce.
+ ///
+ /// The secret to encrypt.
+ /// The nonce to use for encryption.
+ /// The encrypted secret.
+ public byte[] Encrypt(byte[] secret, byte[] nonce)
+ {
+ byte[] encryptingKey = null;
+ byte[] iv = null;
+ byte[] message = null;
+ int lengthPosition = 0;
+
+ int signatureLength = CryptoUtils.GetSignatureLength(SenderCertificate);
+
+ using var encoder = new BinaryEncoder(Context);
+
+ // write header.
+ encoder.WriteNodeId(null, DataTypeIds.EccEncryptedSecret);
+ encoder.WriteByte(null, (byte)ExtensionObjectEncoding.Binary);
+
+ lengthPosition = encoder.Position;
+ encoder.WriteUInt32(null, 0);
+
+ encoder.WriteString(null, SecurityPolicy.Uri);
+
+ byte[] senderCertificate = null;
+
+ if (!DoNotEncodeSenderCertificate)
+ {
+ senderCertificate = SenderCertificate.RawData;
+
+ if (SenderIssuerCertificates != null && SenderIssuerCertificates.Count > 0)
+ {
+ int blobSize = senderCertificate.Length;
+
+ foreach (X509Certificate2 issuer in SenderIssuerCertificates)
+ {
+ blobSize += issuer.RawData.Length;
+ }
+
+ byte[] blob = new byte[blobSize];
+ Buffer.BlockCopy(senderCertificate, 0, blob, 0, senderCertificate.Length);
+
+ int pos = senderCertificate.Length;
+
+ foreach (X509Certificate2 issuer in SenderIssuerCertificates)
+ {
+ byte[] data = issuer.RawData;
+ Buffer.BlockCopy(data, 0, blob, pos, data.Length);
+ pos += data.Length;
+ }
+
+ senderCertificate = blob;
+ }
+ }
+
+ encoder.WriteByteString(null, senderCertificate);
+ encoder.WriteDateTime(null, DateTime.UtcNow);
+
+ if (ReceiverNonce?.Data == null)
+ {
+ throw new ServiceResultException(
+ StatusCodes.BadArgumentsMissing,
+ $"The receiver did not provide an ephemeral key.");
+ }
+
+ byte[] senderNonce = SenderNonce.Data;
+ byte[] receiverNonce = ReceiverNonce.Data;
+
+ encoder.WriteUInt16(null, (ushort)(senderNonce.Length + receiverNonce.Length + 8));
+ int senderNonceStart = encoder.Position;
+ encoder.WriteByteString(null, senderNonce);
+ int senderNonceEnd = encoder.Position;
+ encoder.WriteByteString(null, receiverNonce);
+ int receiverNonceEnd = encoder.Position;
+
+ // create keys.
+ CreateKeysForEcc(
+ SecurityPolicy,
+ SenderNonce,
+ ReceiverNonce,
+ false,
+ out encryptingKey,
+ out iv);
+
+ // reserves space for padding and tag that is added by SymmetricEncryptAndSign.
+ int startOfSecret = encoder.Position;
+ encoder.WriteByteString(null, nonce);
+ encoder.WriteByteString(null, secret);
+
+ int paddingCount = 0;
+ int tagLength = 0;
+
+ switch (SecurityPolicy.SymmetricEncryptionAlgorithm)
+ {
+ case SymmetricEncryptionAlgorithm.Aes128Cbc:
+ case SymmetricEncryptionAlgorithm.Aes256Cbc:
+ paddingCount = GetPaddingCount(SecurityPolicy.InitializationVectorLength, secret.Length, encoder.Position - startOfSecret);
+ tagLength = 0;
+ break;
+ case SymmetricEncryptionAlgorithm.Aes128Gcm:
+ case SymmetricEncryptionAlgorithm.Aes256Gcm:
+ case SymmetricEncryptionAlgorithm.ChaCha20Poly1305:
+ paddingCount = GetPaddingCount(16, secret.Length, encoder.Position - startOfSecret);
+ tagLength = SecurityPolicy.SymmetricSignatureLength;
+ break;
+ }
+
+ for (int ii = 0; ii < paddingCount; ii++)
+ {
+ encoder.WriteByte(null, (byte)paddingCount);
+ }
+
+ encoder.WriteByte(null, (byte)paddingCount);
+ encoder.WriteByte(null, 0);
+
+ int endOfSecret = encoder.Position;
+
+ // reserve space for the outer padding that SymmetricEncryptAndSign will add (CBC only).
+ int outerPaddingSize = 0;
+ if (SecurityPolicy.SymmetricEncryptionAlgorithm is SymmetricEncryptionAlgorithm.Aes128Cbc or SymmetricEncryptionAlgorithm.Aes256Cbc)
+ {
+ int blockSize = SecurityPolicy.InitializationVectorLength;
+ int paddingByteSize = blockSize > byte.MaxValue ? 2 : 1;
+ int paddingSize = blockSize - ((endOfSecret - startOfSecret + paddingByteSize) % blockSize);
+ paddingSize %= blockSize;
+ outerPaddingSize = paddingSize + paddingByteSize;
+
+ for (int ii = 0; ii < outerPaddingSize; ii++)
+ {
+ encoder.WriteByte(null, 0xCD);
+ }
+ }
+
+ // save space for tag.
+ for (int ii = 0; ii < tagLength; ii++)
+ {
+ encoder.WriteByte(null, 0xAB);
+ }
+
+ // save space for signature.
+ for (int ii = 0; ii < signatureLength; ii++)
+ {
+ encoder.WriteByte(null, 0xDE);
+ }
+
+ message = encoder.CloseAndReturnBuffer();
+
+ int length = message.Length - lengthPosition - 4;
+
+ message[lengthPosition++] = (byte)(length & 0xFF);
+ message[lengthPosition++] = (byte)((length & 0xFF00) >> 8);
+ message[lengthPosition++] = (byte)((length & 0xFF0000) >> 16);
+ message[lengthPosition++] = (byte)((length & 0xFF000000) >> 24);
+
+ _ = CryptoUtils.SymmetricEncryptAndSign(
+ new ArraySegment(message, startOfSecret, endOfSecret - startOfSecret),
+ SecurityPolicy,
+ encryptingKey,
+ iv);
+
+ var dataToSign = new ArraySegment(message, 0, message.Length - signatureLength);
+
+ byte[] signature = CryptoUtils.Sign(
+ dataToSign,
+ SenderCertificate,
+ SecurityPolicy.AsymmetricSignatureAlgorithm);
+
+ Buffer.BlockCopy(
+ signature,
+ 0,
+ message,
+ endOfSecret + outerPaddingSize + tagLength,
+ signatureLength);
+
+ return message;
+ }
+
+ private int GetPaddingCount(int blockSize, int secretLength, int dataLength)
+ {
+ dataLength += 2; // add padding size
+
+ int paddingCount =
+ dataLength % blockSize == 0
+ ? 0
+ : blockSize - dataLength % blockSize;
+
+ if (paddingCount + secretLength < blockSize)
+ {
+ paddingCount += blockSize;
+ }
+
+ return paddingCount;
+ }
+
+ ///
+ /// Verifies the header for an ECC encrypted message and returns the encrypted data.
+ ///
+ /// The data to decrypt.
+ /// The earliest time allowed for the message signing time.
+ /// The telemetry context to use to create obvservability instruments
+ /// The encrypted data.
+ ///
+ private ArraySegment VerifyHeaderForEcc(
+ ArraySegment dataToDecrypt,
+ DateTime earliestTime,
+ ITelemetryContext telemetry)
+ {
+ using var decoder = new BinaryDecoder(
+ dataToDecrypt.Array,
+ dataToDecrypt.Offset,
+ dataToDecrypt.Count,
+ Context);
+ NodeId typeId = decoder.ReadNodeId(null);
+
+ if (typeId != DataTypeIds.EccEncryptedSecret)
+ {
+ throw new ServiceResultException(StatusCodes.BadDataTypeIdUnknown);
+ }
+
+ var encoding = (ExtensionObjectEncoding)decoder.ReadByte(null);
+
+ if (encoding != ExtensionObjectEncoding.Binary)
+ {
+ throw new ServiceResultException(StatusCodes.BadDataEncodingUnsupported);
+ }
+
+ int length = (int)decoder.ReadUInt32(null) + decoder.Position;
+
+ SecurityPolicy = SecurityPolicies.GetInfo(decoder.ReadString(null));
+
+ if (SecurityPolicy.EphemeralKeyAlgorithm == CertificateKeyAlgorithm.None)
+ {
+ throw new ServiceResultException(StatusCodes.BadSecurityPolicyRejected);
+ }
+
+ // extract the send certificate and any chain.
+ byte[] senderCertificate = decoder.ReadByteString(null);
+
+ if (senderCertificate == null || senderCertificate.Length == 0)
+ {
+ if (SenderCertificate == null)
+ {
+ throw new ServiceResultException(StatusCodes.BadCertificateInvalid);
+ }
+ }
+ else
+ {
+ X509Certificate2Collection senderCertificateChain = Utils.ParseCertificateChainBlob(
+ senderCertificate,
+ telemetry);
+
+ SenderCertificate = senderCertificateChain[0];
+ SenderIssuerCertificates = [];
+
+ for (int ii = 1; ii < senderCertificateChain.Count; ii++)
+ {
+ SenderIssuerCertificates.Add(senderCertificateChain[ii]);
+ }
+
+ // validate the sender.
+ Validator?.ValidateAsync(senderCertificateChain, default).GetAwaiter().GetResult();
+ }
+
+ // extract the send certificate and any chain.
+ DateTime signingTime = decoder.ReadDateTime(null);
+
+ if (signingTime < earliestTime)
+ {
+ throw new ServiceResultException(StatusCodes.BadInvalidTimestamp);
+ }
+
+ // extract the key data length.
+ ushort headerLength = decoder.ReadUInt16(null);
+
+ if (headerLength == 0 || headerLength > length)
+ {
+ throw new ServiceResultException(StatusCodes.BadDecodingError);
+ }
+
+ // read the key data.
+ int senderNonceStart = decoder.Position;
+ byte[] senderPublicKey = decoder.ReadByteString(null);
+ int senderNonceEnd = decoder.Position;
+ byte[] receiverPublicKey = decoder.ReadByteString(null);
+ int receiverNonceEnd = decoder.Position;
+
+ if (headerLength != senderPublicKey.Length + receiverPublicKey.Length + 8)
+ {
+ throw new ServiceResultException(
+ StatusCodes.BadDecodingError,
+ "Unexpected key data length");
+ }
+
+ int startOfEncryption = decoder.Position;
+
+ SenderNonce = Nonce.CreateNonce(SecurityPolicy, senderPublicKey);
+
+ if (!Utils.IsEqual(receiverPublicKey, ReceiverNonce.Data))
+ {
+ throw new ServiceResultException(
+ StatusCodes.BadDecodingError,
+ "Unexpected receiver nonce.");
+ }
+
+ // check the signature.
+ int signatureLength = CryptoUtils.GetSignatureLength(SenderCertificate);
+
+ if (signatureLength >= length)
+ {
+ throw new ServiceResultException(StatusCodes.BadDecodingError);
+ }
+
+ byte[] signature = new byte[signatureLength];
+
+ Buffer.BlockCopy(
+ dataToDecrypt.Array,
+ dataToDecrypt.Offset + dataToDecrypt.Count - signatureLength,
+ signature,
+ 0,
+ signatureLength);
+
+ var dataToSign = new ArraySegment(
+ dataToDecrypt.Array,
+ dataToDecrypt.Offset,
+ dataToDecrypt.Count - signatureLength);
+
+ if (!CryptoUtils.Verify(dataToSign, signature, SenderCertificate, SecurityPolicy.AsymmetricSignatureAlgorithm))
+ {
+ throw new ServiceResultException(
+ StatusCodes.BadSecurityChecksFailed,
+ "Could not verify signature.");
+ }
+
+ // extract the encrypted data.
+ return new ArraySegment(
+ dataToDecrypt.Array,
+ dataToDecrypt.Offset + startOfEncryption,
+ dataToDecrypt.Count - startOfEncryption - signatureLength);
+ }
+
+ ///
+ /// Decrypts the specified data using the ECC algorithm.
+ ///
+ /// The earliest time allowed for the message.
+ /// The expected nonce value.
+ /// The data to decrypt.
+ /// The offset of the data to decrypt.
+ /// The number of bytes to decrypt.
+ /// The telemetry context to use to create obvservability instruments
+ /// The decrypted data.
+ ///
+ public byte[] Decrypt(
+ DateTime earliestTime,
+ byte[] expectedNonce,
+ byte[] data,
+ int offset,
+ int count,
+ ITelemetryContext telemetry)
+ {
+ ArraySegment dataToDecrypt = VerifyHeaderForEcc(
+ new ArraySegment(data, offset, count),
+ earliestTime,
+ telemetry);
+
+ CreateKeysForEcc(
+ SecurityPolicy,
+ ReceiverNonce,
+ SenderNonce,
+ true,
+ out byte[] encryptingKey,
+ out byte[] iv);
+
+ ArraySegment plainText = CryptoUtils.SymmetricDecryptAndVerify(
+ dataToDecrypt,
+ SecurityPolicy,
+ encryptingKey,
+ iv);
+
+ using var decoder = new BinaryDecoder(
+ plainText.Array,
+ plainText.Offset + dataToDecrypt.Offset,
+ plainText.Count - dataToDecrypt.Offset,
+ Context);
+
+ byte[] actualNonce = decoder.ReadByteString(null);
+
+ if (expectedNonce != null && expectedNonce.Length > 0)
+ {
+ int notvalid = expectedNonce.Length == actualNonce.Length ? 0 : 1;
+
+ for (int ii = 0; ii < expectedNonce.Length && ii < actualNonce.Length; ii++)
+ {
+ notvalid |= expectedNonce[ii] ^ actualNonce[ii];
+ }
+
+ if (notvalid != 0)
+ {
+ throw new ServiceResultException(StatusCodes.BadNonceInvalid);
+ }
+ }
+
+ var key = decoder.ReadByteString(null);
+ var paddingCount = decoder.ReadByte(null);
+
+ int error = 0;
+
+ for (int ii = 0; ii < paddingCount; ii++)
+ {
+ var padding = decoder.ReadByte(null);
+ error |= (padding & ~paddingCount);
+ }
+
+ var highByte = decoder.ReadByte(null);
+
+ if (error != 0 || highByte != 0)
+ {
+ throw new ServiceResultException(StatusCodes.BadDecodingError);
+ }
+
+ return key;
+ }
+ }
+}
diff --git a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs
index 37bad43a6f..53e4b220d3 100644
--- a/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs
+++ b/Stack/Opc.Ua.Core/Security/Certificates/Nonce.cs
@@ -28,8 +28,11 @@
* ======================================================================*/
using System;
+using System.Collections.Generic;
+using System.Numerics;
using System.Runtime.Serialization;
using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
#if CURVE25519
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509;
@@ -48,18 +51,20 @@ namespace Opc.Ua
///
/// Represents a cryptographic nonce used for secure communication.
///
- [Serializable]
- public class Nonce : IDisposable, ISerializable
+ 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;
+
///
/// Constructor
///
private Nonce()
{
m_ecdh = null;
-#if CURVE25519
- m_bcKeyPair = null;
-#endif
+ m_rsadh = null;
}
///
@@ -67,162 +72,212 @@ private Nonce()
///
public byte[] Data { get; private set; }
+ internal byte[] GenerateSecret(
+ Nonce remoteNonce,
+ byte[] previousSecret)
+ {
+ byte[] ikm = null;
+ CryptoTrace.Start(ConsoleColor.Cyan, $"GenerateSecret");
+
+#if NET8_0_OR_GREATER
+#if xDEBUG
+ Span privateKey = stackalloc char[2048];
+
+ if (m_ecdh != null && m_ecdh.TryExportECPrivateKeyPem(privateKey, out int charsWritten))
+ {
+ CryptoTrace.WriteLine($"Private Key PEM ({charsWritten} chars):");
+ }
+#endif
+
+ 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
+ CryptoTrace.WriteLine($"IKM-Raw={CryptoTrace.KeyToString(ikm)}");
+ CryptoTrace.WriteLine($"Previous-IKM={CryptoTrace.KeyToString(previousSecret)}");
+
+ if (ikm != null && previousSecret != null)
+ {
+ for (int ii = 0; ii < ikm.Length && ii < previousSecret.Length; ii++)
+ {
+ ikm[ii] ^= previousSecret[ii];
+ }
+ }
+ CryptoTrace.WriteLine($"IKM-XOR={CryptoTrace.KeyToString(ikm)}");
+ CryptoTrace.Finish("GenerateSecret");
+
+ return ikm;
+ }
+
///
/// Derives a key from the remote nonce, using the specified salt, hash algorithm, and length.
///
- /// The remote nonce to use in key derivation.
+ /// The secret to use in key derivation.
/// The salt to use in key derivation.
/// The hash algorithm to use in key derivation.
/// The length of the derived key.
/// The derived key.
- public byte[] DeriveKey(
- Nonce remoteNonce,
+ public byte[] DeriveKeyData(
+ byte[] secret,
byte[] salt,
- HashAlgorithmName algorithm,
+ KeyDerivationAlgorithm algorithm,
int length)
{
-#if CURVE25519
- if (m_bcKeyPair != null)
- {
- var localPublicKey = m_bcKeyPair.Public;
+ CryptoTrace.Start(ConsoleColor.DarkCyan, $"DeriveKeyData");
+ CryptoTrace.WriteLine($"Secret={CryptoTrace.KeyToString(secret)}");
+ CryptoTrace.WriteLine($"Salt={CryptoTrace.KeyToString(salt)}");
- if (localPublicKey is X25519PublicKeyParameters)
- {
- X25519Agreement agreement = new X25519Agreement();
- agreement.Init(m_bcKeyPair.Private);
-
- var key = new X25519PublicKeyParameters(remoteNonce.Data, 0);
- byte[] secret = new byte[agreement.AgreementSize];
- agreement.CalculateAgreement(key, secret, 0);
+ using HMAC extract = algorithm switch
+ {
+ KeyDerivationAlgorithm.HKDFSha256 => new HMACSHA256(salt),
+ KeyDerivationAlgorithm.HKDFSha384 => new HMACSHA384(salt),
+ _ => new HMACSHA256(salt)
+ };
- HkdfBytesGenerator generator = new HkdfBytesGenerator(new Sha256Digest());
- generator.Init(new HkdfParameters(secret, salt, salt));
+ byte[] prk = extract.ComputeHash(secret);
+ CryptoTrace.WriteLine($"PRK={CryptoTrace.KeyToString(prk)}");
- byte[] output = new byte[length];
- generator.GenerateBytes(output, 0, output.Length);
- return output;
- }
+ using HMAC expand = algorithm switch
+ {
+ KeyDerivationAlgorithm.HKDFSha256 => new HMACSHA256(prk),
+ KeyDerivationAlgorithm.HKDFSha384 => new HMACSHA384(prk),
+ _ => new HMACSHA256(prk)
+ };
- if (localPublicKey is X448PublicKeyParameters)
- {
- X448Agreement agreement = new X448Agreement();
- agreement.Init(m_bcKeyPair.Private);
+ byte[] output = new byte[length];
+ byte counter = 1;
- var key = new X448PublicKeyParameters(remoteNonce.Data, 0);
- byte[] secret = new byte[agreement.AgreementSize];
- agreement.CalculateAgreement(key, secret, 0);
+ byte[] info = new byte[(expand.HashSize / 8) + salt.Length + 1];
+ Buffer.BlockCopy(salt, 0, info, 0, salt.Length);
+ info[salt.Length] = counter++;
- HkdfBytesGenerator generator = new HkdfBytesGenerator(new Sha256Digest());
- generator.Init(new HkdfParameters(secret, salt, salt));
+ // computer T(1)
+ byte[] hash = expand.ComputeHash(info, 0, salt.Length + 1);
+ CryptoTrace.WriteLine($"T(1)={CryptoTrace.KeyToString(hash)}");
- byte[] output = new byte[length];
- generator.GenerateBytes(output, 0, output.Length);
- return output;
- }
+ int pos = 0;
- throw new NotSupportedException();
- }
-#endif
- if (m_ecdh != null)
+ for (int ii = 0; ii < hash.Length && pos < length; ii++)
{
- byte[] secret = m_ecdh.DeriveKeyFromHmac(
- remoteNonce.m_ecdh.PublicKey,
- algorithm,
- salt,
- null,
- null);
-
- byte[] output = new byte[length];
-
- HMAC hmac = algorithm.Name switch
- {
- "SHA256" => new HMACSHA256(secret),
- "SHA384" => new HMACSHA384(secret),
- _ => new HMACSHA256(secret)
- };
-
- byte counter = 1;
-
- byte[] info = new byte[(hmac.HashSize / 8) + salt.Length + 1];
- Buffer.BlockCopy(salt, 0, info, 0, salt.Length);
- info[salt.Length] = counter++;
+ output[pos++] = hash[ii];
+ }
- byte[] hash = hmac.ComputeHash(info, 0, salt.Length + 1);
+ while (pos < length)
+ {
+ Buffer.BlockCopy(hash, 0, info, 0, hash.Length);
+ Buffer.BlockCopy(salt, 0, info, hash.Length, salt.Length);
+ info[^1] = counter++;
- int pos = 0;
+ hash = expand.ComputeHash(info, 0, info.Length);
+ CryptoTrace.WriteLine($"T({counter - 1})={CryptoTrace.KeyToString(hash)}");
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 = hmac.ComputeHash(info, 0, info.Length);
-
- for (int ii = 0; ii < hash.Length && pos < length; ii++)
- {
- output[pos++] = hash[ii];
- }
- }
+ CryptoTrace.WriteLine($"KeyData={CryptoTrace.KeyToString(output)}");
+ CryptoTrace.Finish("DeriveKeyData");
- return output;
- }
+ return output;
+ }
- return Data;
+ ///
+ /// Generates a Nonce for cryptographic functions of a given length.
+ ///
+ /// The requested Nonce as a
+ public static Nonce CreateNonce(int length)
+ {
+ return new Nonce { Data = CreateRandomNonceData(length) };
}
///
/// Creates a nonce for the specified security policy URI and nonce length.
///
- /// The security policy URI.
- /// A object containing the generated nonce.
- /// is null.
public static Nonce CreateNonce(string securityPolicyUri)
{
- if (securityPolicyUri == null)
+ var info = SecurityPolicies.GetInfo(securityPolicyUri);
+ return CreateNonce(info);
+ }
+
+ ///
+ /// Creates a nonce for the specified security policy and nonce length.
+ ///
+ public static Nonce CreateNonce(SecurityPolicyInfo securityPolicy)
+ {
+ if (securityPolicy == null)
{
- throw new ArgumentNullException(nameof(securityPolicyUri));
+ throw new ArgumentNullException(nameof(securityPolicy));
}
- switch (securityPolicyUri)
+ switch (securityPolicy.EphemeralKeyAlgorithm)
{
- case SecurityPolicies.ECC_nistP256:
+ 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 SecurityPolicies.ECC_nistP384:
+ case CertificateKeyAlgorithm.NistP384:
return CreateNonce(ECCurve.NamedCurves.nistP384);
- case SecurityPolicies.ECC_brainpoolP256r1:
+ case CertificateKeyAlgorithm.BrainpoolP256r1:
return CreateNonce(ECCurve.NamedCurves.brainpoolP256r1);
- case SecurityPolicies.ECC_brainpoolP384r1:
+ case CertificateKeyAlgorithm.BrainpoolP384r1:
return CreateNonce(ECCurve.NamedCurves.brainpoolP384r1);
-#if CURVE25519
- case SecurityPolicies.ECC_curve25519:
- return CreateNonceForCurve25519();
- case SecurityPolicies.ECC_curve448:
- return CreateNonceForCurve448();
-#endif
default:
- uint rsaNonceLength = GetNonceLength(securityPolicyUri);
- return new Nonce { Data = CreateRandomNonceData(rsaNonceLength) };
+ // 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)
+ };
}
}
+ ///
+ /// Creates a new Nonce object for the specified RSA DiffieHellman group.
+ ///
+ public static Nonce CreateNonce(RSADiffieHellmanGroup group)
+ {
+ var nonce = new Nonce();
+ nonce.m_rsadh = RSADiffieHellman.Create(group);
+ nonce.Data = nonce.m_rsadh.GetNonce();
+ return nonce;
+ }
+
///
/// Creates a new Nonce object for the specified security policy URI and nonce data.
///
- /// The security policy URI.
- /// The nonce data.
- /// A new Nonce object.
- /// is null.
- public static Nonce CreateNonce(string securityPolicyUri, byte[] nonceData)
+ public static Nonce CreateNonce(SecurityPolicyInfo securityPolicy, byte[] nonceData)
{
- if (securityPolicyUri == null)
+ if (securityPolicy == null)
{
- throw new ArgumentNullException(nameof(securityPolicyUri));
+ throw new ArgumentNullException(nameof(securityPolicy));
}
if (nonceData == null)
@@ -230,24 +285,30 @@ public static Nonce CreateNonce(string securityPolicyUri, byte[] nonceData)
throw new ArgumentNullException(nameof(nonceData));
}
- var nonce = new Nonce { Data = nonceData };
+ if (securityPolicy.EphemeralKeyAlgorithm == CertificateKeyAlgorithm.RSADH)
+ {
+ var nonce = new Nonce();
+ nonce.m_rsadh = RSADiffieHellman.Create(nonceData);
+ nonce.Data = nonceData;
+ return nonce;
+ }
- switch (securityPolicyUri)
+ switch (securityPolicy.EphemeralKeyAlgorithm)
{
- case SecurityPolicies.ECC_nistP256:
+ case CertificateKeyAlgorithm.NistP256:
return CreateNonce(ECCurve.NamedCurves.nistP256, nonceData);
- case SecurityPolicies.ECC_nistP384:
+ case CertificateKeyAlgorithm.NistP384:
return CreateNonce(ECCurve.NamedCurves.nistP384, nonceData);
- case SecurityPolicies.ECC_brainpoolP256r1:
+ case CertificateKeyAlgorithm.BrainpoolP256r1:
return CreateNonce(ECCurve.NamedCurves.brainpoolP256r1, nonceData);
- case SecurityPolicies.ECC_brainpoolP384r1:
+ case CertificateKeyAlgorithm.BrainpoolP384r1:
return CreateNonce(ECCurve.NamedCurves.brainpoolP384r1, nonceData);
- case SecurityPolicies.ECC_curve25519:
+ case CertificateKeyAlgorithm.Curve25519:
return CreateNonceForCurve25519(nonceData);
- case SecurityPolicies.ECC_curve448:
+ case CertificateKeyAlgorithm.Curve448:
return CreateNonceForCurve448(nonceData);
default:
- return nonce;
+ return new Nonce { Data = nonceData };
}
}
@@ -255,8 +316,21 @@ public static Nonce CreateNonce(string securityPolicyUri, byte[] nonceData)
/// Generates a Nonce for cryptographic functions of a given length.
///
/// The requested Nonce as a
- public static byte[] CreateRandomNonceData(uint length)
+ public static byte[] CreateRandomNonceData(int length)
+ {
+ return CreateRandomNonceData(length, true);
+ }
+
+ ///
+ /// Generates nonce data with optional minimum length enforcement.
+ ///
+ 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;
@@ -268,9 +342,9 @@ public static byte[] CreateRandomNonceData(uint length)
public static bool ValidateNonce(
byte[] nonce,
MessageSecurityMode securityMode,
- string securityPolicyUri)
+ SecurityPolicyInfo securityPolicy)
{
- return ValidateNonce(nonce, securityMode, GetNonceLength(securityPolicyUri));
+ return ValidateNonce(nonce, securityMode, securityPolicy.SecureChannelNonceLength);
}
///
@@ -279,7 +353,7 @@ public static bool ValidateNonce(
public static bool ValidateNonce(
byte[] nonce,
MessageSecurityMode securityMode,
- uint minNonceLength)
+ int minNonceLength)
{
// no nonce needed for no security.
if (securityMode == MessageSecurityMode.None)
@@ -305,38 +379,6 @@ public static bool ValidateNonce(
return false;
}
- ///
- /// Returns the length of the symmetric encryption key for a security policy.
- ///
- public static uint GetNonceLength(string securityPolicyUri)
- {
- switch (securityPolicyUri)
- {
- case SecurityPolicies.Basic128Rsa15:
- return 16;
- case SecurityPolicies.Basic256:
- case SecurityPolicies.Basic256Sha256:
- case SecurityPolicies.Aes128_Sha256_RsaOaep:
- case SecurityPolicies.Aes256_Sha256_RsaPss:
- case SecurityPolicies.ECC_curve25519:
- return 32;
- case SecurityPolicies.ECC_nistP256:
- case SecurityPolicies.ECC_brainpoolP256r1:
- // Q.X + Q.Y = 32 + 32 = 64
- return 64;
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP384r1:
- // Q.X + Q.Y = 48 + 48 = 96
- return 96;
- case SecurityPolicies.ECC_curve448:
- // Q.X
- return 56;
- default:
- // Minimum nonce length by default
- return s_minNonceLength;
- }
- }
-
///
/// Compare Nonce for equality.
///
@@ -446,119 +488,298 @@ private static Nonce CreateNonce(ECCurve curve)
return new Nonce { Data = senderNonce, m_ecdh = ecdh };
}
-#if CURVE25519
///
- /// Creates a new Nonce object to be used in Curve25519 cryptography.
+ /// Frees any unmanaged resources.
///
- /// A new Nonce object.
- private static Nonce CreateNonceForCurve25519()
+ public void Dispose()
{
- SecureRandom random = new SecureRandom();
- IAsymmetricCipherKeyPairGenerator generator = new X25519KeyPairGenerator();
- generator.Init(new X25519KeyGenerationParameters(random));
-
- var keyPair = generator.GenerateKeyPair();
-
- byte[] senderNonce = new byte[X25519PublicKeyParameters.KeySize];
- ((X25519PublicKeyParameters)(keyPair.Public)).Encode(senderNonce, 0);
-
- var nonce = new Nonce() { Data = senderNonce, m_bcKeyPair = keyPair };
-
- return nonce;
+ Dispose(true);
+ GC.SuppressFinalize(this);
}
///
- /// Creates a Nonce object using the X448 elliptic curve algorithm.
+ /// An overrideable version of the Dispose.
///
- /// A Nonce object containing the generated nonce data and key pair.
- private static Nonce CreateNonceForCurve448()
+ protected virtual void Dispose(bool disposing)
{
- SecureRandom random = new SecureRandom();
- IAsymmetricCipherKeyPairGenerator generator = new X448KeyPairGenerator();
- generator.Init(new X448KeyGenerationParameters(random));
+ if (disposing)
+ {
+ if (m_ecdh != null)
+ {
+ m_ecdh.Dispose();
+ m_ecdh = null;
+ }
+ }
+ }
+ }
- var keyPair = generator.GenerateKeyPair();
+ ///
+ /// The known RSA Diffie-Hellman groups.
+ ///
+ public enum RSADiffieHellmanGroup
+ {
+ ///
+ /// The 2048-bit finite field Diffie-Hellman ephemeral group defined in RFC 7919.
+ ///
+ FFDHE2048,
- byte[] senderNonce = new byte[X448PublicKeyParameters.KeySize];
- ((X448PublicKeyParameters)(keyPair.Public)).Encode(senderNonce, 0);
+ ///
+ /// The 3072-bit finite field Diffie-Hellman ephemeral group defined in RFC 7919.
+ ///
+ FFDHE3072,
- var nonce = new Nonce() { Data = senderNonce, m_bcKeyPair = keyPair };
+ ///
+ /// The 4096-bit finite field Diffie-Hellman ephemeral group defined in RFC 7919.
+ ///
+ FFDHE4096
+ }
- return nonce;
- }
-#endif
+ ///
+ /// A RSA Diffie-Hellman key exchange implementation.
+ ///
+ public class RSADiffieHellman
+ {
+ private BigInteger m_privateKey;
+ private BigInteger m_publicKey;
+ private int m_nonceLength;
+
+ // ffdhe2048 prime from RFC 7919 (hex, without whitespace).
+ // (RFC 7919 Appendix A.3 — use this canonical modulus in production.)
+ 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";
+
+ static readonly Lazy s_P2048 = new(() => RfcTextToBytes(FFDHE2048_HEX));
+
+ const int k_FFDHE2048_MinExponent = 224;
+ const int k_FFDHE2048_MaxExponent = 255;
+
+ 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";
+
+ static readonly Lazy s_P3072 = new(() => RfcTextToBytes(FFDHE3072_HEX));
+
+ const int k_FFDHE3072_MinExponent = 275;
+ const int k_FFDHE3072_MaxExponent = 383;
+
+ 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";
+
+ static readonly Lazy s_P4096 = new(() => RfcTextToBytes(FFDHE4096_HEX));
+
+ const int k_FFDHE4096_MinExponent = 325;
+ const int k_FFDHE4096_MaxExponent = 511;
+
+ private static readonly Lazy s_rng = new(() => RandomNumberGenerator.Create());
+
+ // Generator for FFDHE groups is 2
+ static readonly BigInteger s_G = new BigInteger(2);
///
- /// Custom deserialization
+ /// Creates a new RSADiffieHellman instance for the specified group.
///
- protected Nonce(SerializationInfo info, StreamingContext context)
+ public static RSADiffieHellman Create(RSADiffieHellmanGroup group)
{
- string curveName = info.GetString("CurveName");
+ int min = 0;
+ int max = 0;
+ BigInteger p;
- if (curveName != null)
+ switch (group)
{
- var ecParams = new ECParameters
- {
- Curve = ECCurve.CreateFromFriendlyName(curveName),
- Q = new ECPoint
- {
- X = (byte[])info.GetValue("QX", typeof(byte[])),
- Y = (byte[])info.GetValue("QY", typeof(byte[]))
- }
- };
- m_ecdh = ECDiffieHellman.Create(ecParams);
+ 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.");
}
- Data = (byte[])info.GetValue("Data", typeof(byte[]));
- }
- private ECDiffieHellman m_ecdh;
-#if CURVE25519
- private AsymmetricCipherKeyPair m_bcKeyPair;
-#endif
+ var dh = new RSADiffieHellman();
- private static readonly RandomNumberGenerator s_rng = RandomNumberGenerator.Create();
- private static uint s_minNonceLength = 32;
+ 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[key.Length - 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;
+ }
///
- /// Frees any unmanaged resources.
+ /// Creates a new RSADiffieHellman instance from the nonce.
///
- public void Dispose()
+ public static RSADiffieHellman Create(byte[] nonce)
{
- Dispose(true);
- GC.SuppressFinalize(this);
+ var dh = new RSADiffieHellman();
+
+ var 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;
}
///
- /// An overrideable version of the Dispose.
+ /// Returns the nonce representing the public key.
///
- protected virtual void Dispose(bool disposing)
+ public byte[] GetNonce()
{
- if (disposing && m_ecdh != null)
+ var nonce = new byte[m_nonceLength];
+ var publicKey = m_publicKey.ToByteArray();
+
+ for (int ii = 0; ii < publicKey.Length && ii < nonce.Length; ii++)
{
- m_ecdh.Dispose();
- m_ecdh = null;
+ nonce[nonce.Length - 1 - ii] = publicKey[ii];
}
+
+ return nonce;
}
///
- /// Custom serialization
+ /// Derives the raw secret agreement from the remote key.
///
- public void GetObjectData(SerializationInfo info, StreamingContext context)
+ public byte[] DeriveRawSecretAgreement(RSADiffieHellman remoteKey)
{
- if (m_ecdh != null)
+ 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);
+
+ var bytes = shared.ToByteArray();
+
+ if (bytes.Length < m_nonceLength)
+ {
+ var padded = new byte[m_nonceLength];
+ Array.Copy(bytes, 0, padded, 0, bytes.Length);
+ bytes = padded;
+ }
+ else if (bytes.Length > m_nonceLength)
{
- ECParameters ecParams = m_ecdh.ExportParameters(false);
- info.AddValue("CurveName", ecParams.Curve.Oid.FriendlyName);
- info.AddValue("QX", ecParams.Q.X);
- info.AddValue("QY", ecParams.Q.Y);
+ var trucated = new byte[m_nonceLength];
+ Array.Copy(bytes, 0, trucated, 0, m_nonceLength);
+ bytes = trucated;
}
- else
+
+ // make sure bytes are in big-endian order.
+ Array.Reverse(bytes);
+
+ return bytes;
+ }
+
+ private static BigInteger RfcTextToBytes(string rfcText)
+ {
+ var bytes = new List();
+ var digit = new char[2];
+ int pos = 0;
+
+ bytes.Add(0);
+
+ for (int ii = 0; ii < rfcText.Length; ii++)
{
- info.AddValue("CurveName", null);
- info.AddValue("QX", null);
- info.AddValue("QY", null);
+ if (char.IsWhiteSpace(rfcText[ii]))
+ {
+ continue;
+ }
+
+ digit[pos++] = rfcText[ii];
+
+ if (pos == 2)
+ {
+ bytes.Add(Convert.ToByte(new string(digit), 16));
+ pos = 0;
+ }
}
- info.AddValue("Data", Data);
+
+ bytes.Reverse();
+ var integer = new BigInteger(bytes.ToArray());
+ return integer;
}
}
}
diff --git a/Stack/Opc.Ua.Core/Security/Certificates/SecurityConfiguration.cs b/Stack/Opc.Ua.Core/Security/Certificates/SecurityConfiguration.cs
index ae1b90a2a7..4fa506ff06 100644
--- a/Stack/Opc.Ua.Core/Security/Certificates/SecurityConfiguration.cs
+++ b/Stack/Opc.Ua.Core/Security/Certificates/SecurityConfiguration.cs
@@ -207,6 +207,8 @@ private StringCollection BuildSupportedSecurityPolicies()
securityPolicies.Add(SecurityPolicies.Basic256Sha256);
securityPolicies.Add(SecurityPolicies.Aes128_Sha256_RsaOaep);
securityPolicies.Add(SecurityPolicies.Aes256_Sha256_RsaPss);
+ securityPolicies.Add(SecurityPolicies.RSA_DH_AesGcm);
+ securityPolicies.Add(SecurityPolicies.RSA_DH_ChaChaPoly);
continue;
}
if (applicationCertificate.CertificateType.TryGetIdentifier(out uint identifier))
@@ -215,23 +217,39 @@ private StringCollection BuildSupportedSecurityPolicies()
{
case ObjectTypes.EccNistP256ApplicationCertificateType:
securityPolicies.Add(SecurityPolicies.ECC_nistP256);
+ securityPolicies.Add(SecurityPolicies.ECC_nistP256_AesGcm);
+ securityPolicies.Add(SecurityPolicies.ECC_nistP256_ChaChaPoly);
break;
case ObjectTypes.EccNistP384ApplicationCertificateType:
securityPolicies.Add(SecurityPolicies.ECC_nistP256);
+ securityPolicies.Add(SecurityPolicies.ECC_nistP256_AesGcm);
+ securityPolicies.Add(SecurityPolicies.ECC_nistP256_ChaChaPoly);
securityPolicies.Add(SecurityPolicies.ECC_nistP384);
+ securityPolicies.Add(SecurityPolicies.ECC_nistP384_AesGcm);
+ securityPolicies.Add(SecurityPolicies.ECC_nistP384_ChaChaPoly);
break;
case ObjectTypes.EccBrainpoolP256r1ApplicationCertificateType:
securityPolicies.Add(SecurityPolicies.ECC_brainpoolP256r1);
+ securityPolicies.Add(SecurityPolicies.ECC_brainpoolP256r1_AesGcm);
+ securityPolicies.Add(SecurityPolicies.ECC_brainpoolP256r1_ChaChaPoly);
break;
case ObjectTypes.EccBrainpoolP384r1ApplicationCertificateType:
securityPolicies.Add(SecurityPolicies.ECC_brainpoolP256r1);
+ securityPolicies.Add(SecurityPolicies.ECC_brainpoolP256r1_AesGcm);
+ securityPolicies.Add(SecurityPolicies.ECC_brainpoolP256r1_ChaChaPoly);
securityPolicies.Add(SecurityPolicies.ECC_brainpoolP384r1);
+ securityPolicies.Add(SecurityPolicies.ECC_brainpoolP384r1_AesGcm);
+ securityPolicies.Add(SecurityPolicies.ECC_brainpoolP384r1_ChaChaPoly);
break;
case ObjectTypes.EccCurve25519ApplicationCertificateType:
securityPolicies.Add(SecurityPolicies.ECC_curve25519);
+ securityPolicies.Add(SecurityPolicies.ECC_curve25519_AesGcm);
+ securityPolicies.Add(SecurityPolicies.ECC_curve25519_ChaChaPoly);
break;
case ObjectTypes.EccCurve448ApplicationCertificateType:
securityPolicies.Add(SecurityPolicies.ECC_curve448);
+ securityPolicies.Add(SecurityPolicies.ECC_curve448_AesGcm);
+ securityPolicies.Add(SecurityPolicies.ECC_curve448_ChaChaPoly);
break;
case ObjectTypes.RsaMinApplicationCertificateType:
securityPolicies.Add(SecurityPolicies.Basic128Rsa15);
@@ -242,6 +260,8 @@ private StringCollection BuildSupportedSecurityPolicies()
securityPolicies.Add(SecurityPolicies.Basic256Sha256);
securityPolicies.Add(SecurityPolicies.Aes128_Sha256_RsaOaep);
securityPolicies.Add(SecurityPolicies.Aes256_Sha256_RsaPss);
+ securityPolicies.Add(SecurityPolicies.RSA_DH_AesGcm);
+ securityPolicies.Add(SecurityPolicies.RSA_DH_ChaChaPoly);
goto case ObjectTypes.RsaMinApplicationCertificateType;
}
}
diff --git a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs
index 71a3e70f42..afc35a9ce9 100644
--- a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs
+++ b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs
@@ -651,7 +651,7 @@ public static bool IsECDsaSignature(X509Certificate2 cert)
/// The certificate.
public static string GetECDsaQualifier(X509Certificate2 certificate)
{
- return EccUtils.GetECDsaQualifier(certificate);
+ return CryptoUtils.GetECDsaQualifier(certificate);
}
///
diff --git a/Stack/Opc.Ua.Core/Security/Constants/AdditionalParameterNames.cs b/Stack/Opc.Ua.Core/Security/Constants/AdditionalParameterNames.cs
new file mode 100644
index 0000000000..880b3d0d64
--- /dev/null
+++ b/Stack/Opc.Ua.Core/Security/Constants/AdditionalParameterNames.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Opc.Ua
+{
+ ///
+ /// The names of additional parameters used in security-related operations.
+ ///
+ public static class AdditionalParameterNames
+ {
+ ///
+ /// The algorith to use for the ephemeral key used to encrypt user identity tokens.
+ ///
+ public const string ECDHPolicyUri = "ECDHPolicyUri";
+
+ ///
+ /// An ephemeral key used to encrypt user identity tokens.
+ ///
+ public const string ECDHKey = "ECDHKey";
+
+ ///
+ /// Padding bytes added to randomize the length of messages.
+ ///
+ public const string Padding = "Padding";
+ }
+}
diff --git a/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicies.cs b/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicies.cs
index ee3bea6ca3..867a17ae28 100644
--- a/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicies.cs
+++ b/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicies.cs
@@ -83,43 +83,137 @@ public static class SecurityPolicies
///
public const string Aes256_Sha256_RsaPss = BaseUri + "Aes256_Sha256_RsaPss";
+ ///
+ /// The URI for the RSA_DH_AES_GCM security policy.
+ ///
+ public const string RSA_DH_AesGcm = BaseUri + "RSA_DH_AesGcm";
+
+ ///
+ /// The URI for the RSA_DH_ChaChaPoly security policy.
+ ///
+ public const string RSA_DH_ChaChaPoly = BaseUri + "RSA_DH_ChaChaPoly";
+
///
/// The URI for the ECC_nistP256 security policy.
///
public const string ECC_nistP256 = BaseUri + "ECC_nistP256";
+ ///
+ /// The URI for the ECC_nistP256 security policy with AES-GCM.
+ ///
+ public const string ECC_nistP256_AesGcm = ECC_nistP256 + "_AesGcm";
+
+ ///
+ /// The URI for the ECC_nistP256 security policy with ChaCha20Poly1305.
+ ///
+ public const string ECC_nistP256_ChaChaPoly = ECC_nistP256 + "_ChaChaPoly";
+
///
/// The URI for the ECC_nistP384 security policy.
///
public const string ECC_nistP384 = BaseUri + "ECC_nistP384";
+ ///
+ /// The URI for the ECC_nistP384 security policy with AES-GCM.
+ ///
+ public const string ECC_nistP384_AesGcm = ECC_nistP384 + "_AesGcm";
+
+ ///
+ /// The URI for the ECC_nistP384 security policy with ChaCha20Poly1305.
+ ///
+ public const string ECC_nistP384_ChaChaPoly = ECC_nistP384 + "_ChaChaPoly";
+
///
/// The URI for the ECC_brainpoolP256r1 security policy.
///
public const string ECC_brainpoolP256r1 = BaseUri + "ECC_brainpoolP256r1";
+ ///
+ /// The URI for the ECC_brainpoolP256r1 security policy with AES-GCM.
+ ///
+ public const string ECC_brainpoolP256r1_AesGcm = ECC_brainpoolP256r1 + "_AesGcm";
+
+ ///
+ /// The URI for the ECC_brainpoolP256r1 security policy with ChaCha20Poly1305.
+ ///
+ public const string ECC_brainpoolP256r1_ChaChaPoly = ECC_brainpoolP256r1 + "_ChaChaPoly";
+
///
/// The URI for the ECC_brainpoolP384r1 security policy.
///
public const string ECC_brainpoolP384r1 = BaseUri + "ECC_brainpoolP384r1";
///
- /// The URI for the ECC_curve25519 security policy.
+ /// The URI for the ECC_brainpoolP384r1 security policy with AES-GCM.
+ ///
+ public const string ECC_brainpoolP384r1_AesGcm = ECC_brainpoolP384r1 + "_AesGcm";
+
+ ///
+ /// The URI for the ECC_brainpoolP384r1 security policy with ChaCha20Poly1305.
+ ///
+ public const string ECC_brainpoolP384r1_ChaChaPoly = ECC_brainpoolP384r1 + "_ChaChaPoly";
+
+ ///
+ /// The URI for the ECC_curve25519 security policy.brainpoolP384r1_AesGcm
///
public const string ECC_curve25519 = BaseUri + "ECC_curve25519";
///
- /// The URI for the ECC_curve448 security policy.
+ /// The URI for the ECC_curve25519 security policy with AES-GCM.
+ ///
+ public const string ECC_curve25519_AesGcm = ECC_curve25519 + "_AesGcm";
+
+ ///
+ /// The URI for the ECC_curve25519 security policy with ChaCha20Poly1305.
+ ///
+ public const string ECC_curve25519_ChaChaPoly = ECC_curve25519 + "_ChaChaPoly";
+
+ ///
+ /// The URI for the ECC_curve448 deprecated security policy.
///
public const string ECC_curve448 = BaseUri + "ECC_curve448";
+ ///
+ /// The URI for the ECC_curve448 security policy with AES-GCM.
+ ///
+ public const string ECC_curve448_AesGcm = ECC_curve448 + "_AesGcm";
+
+ ///
+ /// The URI for the ECC_curve448 security policy with ChaCha20Poly1305.
+ ///
+ public const string ECC_curve448_ChaChaPoly = ECC_curve448 + "_ChaChaPoly";
+
///
/// The URI for the Https security policy.
///
public const string Https = BaseUri + "Https";
+ private static bool SupportsAesGcmPolicy()
+ {
+#if NET8_0_OR_GREATER
+ return AesGcm.IsSupported;
+#else
+ return false;
+#endif
+ }
+
+ private static bool SupportsChaCha20Poly1305Policy()
+ {
+#if NET8_0_OR_GREATER
+ return ChaCha20Poly1305.IsSupported;
+#else
+ return false;
+#endif
+ }
+
private static bool IsPlatformSupportedName(string name)
{
+ // If name contains BaseUri trim the BaseUri part
+ if (name.StartsWith(BaseUri, StringComparison.Ordinal))
+ {
+ name = name.Substring(BaseUri.Length);
+ }
+
// all RSA
if (name.Equals(nameof(None), StringComparison.Ordinal) ||
name.Equals(nameof(Basic256), StringComparison.Ordinal) ||
@@ -130,6 +224,16 @@ private static bool IsPlatformSupportedName(string name)
return true;
}
+ if (name.Equals(nameof(RSA_DH_AesGcm), StringComparison.Ordinal))
+ {
+ return SupportsAesGcmPolicy();
+ }
+
+ if (name.Equals(nameof(RSA_DH_ChaChaPoly), StringComparison.Ordinal))
+ {
+ return SupportsChaCha20Poly1305Policy();
+ }
+
if (name.Equals(nameof(Aes256_Sha256_RsaPss), StringComparison.Ordinal) &&
RsaUtils.IsSupportingRSAPssSign.Value)
{
@@ -140,32 +244,114 @@ private static bool IsPlatformSupportedName(string name)
return Utils.IsSupportedCertificateType(
ObjectTypeIds.EccNistP256ApplicationCertificateType);
}
+ if (name.Equals(nameof(ECC_nistP256_AesGcm), StringComparison.Ordinal))
+ {
+ return SupportsAesGcmPolicy() &&
+ Utils.IsSupportedCertificateType(ObjectTypeIds.EccNistP256ApplicationCertificateType);
+ }
+ if (name.Equals(nameof(ECC_nistP256_ChaChaPoly), StringComparison.Ordinal))
+ {
+ return SupportsChaCha20Poly1305Policy() &&
+ Utils.IsSupportedCertificateType(ObjectTypeIds.EccNistP256ApplicationCertificateType);
+ }
if (name.Equals(nameof(ECC_nistP384), StringComparison.Ordinal))
{
return Utils.IsSupportedCertificateType(
ObjectTypeIds.EccNistP384ApplicationCertificateType);
}
+ if (name.Equals(nameof(ECC_nistP384_AesGcm), StringComparison.Ordinal))
+ {
+ return SupportsAesGcmPolicy() &&
+ Utils.IsSupportedCertificateType(ObjectTypeIds.EccNistP384ApplicationCertificateType);
+ }
+ if (name.Equals(nameof(ECC_nistP384_ChaChaPoly), StringComparison.Ordinal))
+ {
+ return SupportsChaCha20Poly1305Policy() &&
+ Utils.IsSupportedCertificateType(ObjectTypeIds.EccNistP384ApplicationCertificateType);
+ }
if (name.Equals(nameof(ECC_brainpoolP256r1), StringComparison.Ordinal))
{
return Utils.IsSupportedCertificateType(
ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType);
}
+ if (name.Equals(nameof(ECC_brainpoolP256r1_AesGcm), StringComparison.Ordinal))
+ {
+ return SupportsAesGcmPolicy() &&
+ Utils.IsSupportedCertificateType(ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType);
+ }
+ if (name.Equals(nameof(ECC_brainpoolP256r1_ChaChaPoly), StringComparison.Ordinal))
+ {
+ return SupportsChaCha20Poly1305Policy() &&
+ Utils.IsSupportedCertificateType(ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType);
+ }
if (name.Equals(nameof(ECC_brainpoolP384r1), StringComparison.Ordinal))
{
return Utils.IsSupportedCertificateType(
ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType);
}
+ if (name.Equals(nameof(ECC_brainpoolP384r1_AesGcm), StringComparison.Ordinal))
+ {
+ return SupportsAesGcmPolicy() &&
+ Utils.IsSupportedCertificateType(ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType);
+ }
+ if (name.Equals(nameof(ECC_brainpoolP384r1_ChaChaPoly), StringComparison.Ordinal))
+ {
+ return SupportsChaCha20Poly1305Policy() &&
+ Utils.IsSupportedCertificateType(ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType);
+ }
if (name.Equals(nameof(ECC_curve25519), StringComparison.Ordinal) ||
name.Equals(nameof(ECC_curve448), StringComparison.Ordinal))
{
#if CURVE25519
return true;
+#endif
+ }
+ if (name.Equals(nameof(ECC_curve25519_AesGcm), StringComparison.Ordinal) ||
+ name.Equals(nameof(ECC_curve448_AesGcm), StringComparison.Ordinal))
+ {
+#if CURVE25519
+ return SupportsAesGcmPolicy();
+#endif
+ }
+ if (name.Equals(nameof(ECC_curve25519_ChaChaPoly), StringComparison.Ordinal) ||
+ name.Equals(nameof(ECC_curve448_ChaChaPoly), StringComparison.Ordinal))
+ {
+#if CURVE25519
+ return SupportsChaCha20Poly1305Policy();
#endif
}
return false;
}
+ ///
+ /// Returns the info object associated with the SecurityPolicyUri.
+ /// Supports both full URI and short name (without BaseUri prefix).
+ ///
+ public static SecurityPolicyInfo GetInfo(string securityPolicyUri)
+ {
+ if (String.IsNullOrEmpty(securityPolicyUri))
+ {
+ return SecurityPolicyInfo.None;
+ }
+
+ // Try full URI lookup first (e.g., "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256")
+ if (s_securityPolicyUriToInfo.Value.TryGetValue(securityPolicyUri, out SecurityPolicyInfo info) &&
+ IsPlatformSupportedName(info.Name))
+ {
+ return info;
+ }
+
+ // Try short name lookup (e.g., "Basic256Sha256")
+ if (s_securityPolicyNameToInfo.Value.TryGetValue(securityPolicyUri, out info) &&
+ IsPlatformSupportedName(info.Name))
+ {
+ return info;
+ }
+
+ return null;
+ }
+
///
/// Returns the uri associated with the display name. This includes http and all
/// other supported platform security policies.
@@ -303,65 +489,70 @@ public static EncryptedData Encrypt(
ReadOnlySpan plainText,
ILogger logger)
{
- var encryptedData = new EncryptedData
- {
- Algorithm = null,
- Data = plainText.IsEmpty ? null : plainText.ToArray()
- };
+ var encryptedData = new EncryptedData { Algorithm = null };
// check if nothing to do.
- if (plainText.IsEmpty)
+ if (plainText.Length == 0 || String.IsNullOrEmpty(securityPolicyUri))
{
+ encryptedData.Data = plainText.ToArray();
return encryptedData;
}
- // nothing more to do if no encryption.
- if (string.IsNullOrEmpty(securityPolicyUri))
+ // get the info object.
+ var info = GetInfo(securityPolicyUri);
+
+ // unsupported policy.
+ if (info == null)
{
- return encryptedData;
+ throw ServiceResultException.Create(
+ StatusCodes.BadSecurityPolicyRejected,
+ "Unsupported security policy: {0}",
+ securityPolicyUri);
}
- // encrypt data.
- switch (securityPolicyUri)
+ // check if asymmetric encryption is possible.
+ if (info.AsymmetricEncryptionAlgorithm != AsymmetricEncryptionAlgorithm.None)
{
- case Basic256:
- case Basic256Sha256:
- case Aes128_Sha256_RsaOaep:
- encryptedData.Algorithm = SecurityAlgorithms.RsaOaep;
- encryptedData.Data = RsaUtils.Encrypt(
- plainText,
- certificate,
- RsaUtils.Padding.OaepSHA1,
- logger);
- break;
- case Basic128Rsa15:
- encryptedData.Algorithm = SecurityAlgorithms.Rsa15;
- encryptedData.Data = RsaUtils.Encrypt(
- plainText,
- certificate,
- RsaUtils.Padding.Pkcs1,
- logger);
- break;
- case Aes256_Sha256_RsaPss:
- encryptedData.Algorithm = SecurityAlgorithms.RsaOaepSha256;
- encryptedData.Data = RsaUtils.Encrypt(
- plainText,
- certificate,
- RsaUtils.Padding.OaepSHA256,
- logger);
- break;
- case ECC_nistP256:
- case ECC_nistP384:
- case ECC_brainpoolP256r1:
- case ECC_brainpoolP384r1:
- return encryptedData;
- case None:
- break;
- default:
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityPolicyRejected,
- "Unsupported security policy: {0}",
- securityPolicyUri);
+ switch (info.AsymmetricEncryptionAlgorithm)
+ {
+ case AsymmetricEncryptionAlgorithm.RsaOaepSha1:
+ {
+ encryptedData.Algorithm = SecurityAlgorithms.RsaOaep;
+ encryptedData.Data = RsaUtils.Encrypt(
+ plainText,
+ certificate,
+ RsaUtils.Padding.OaepSHA1,
+ logger);
+ break;
+ }
+
+ case AsymmetricEncryptionAlgorithm.RsaPkcs15Sha1:
+ {
+ encryptedData.Algorithm = SecurityAlgorithms.Rsa15;
+ encryptedData.Data = RsaUtils.Encrypt(
+ plainText,
+ certificate,
+ RsaUtils.Padding.Pkcs1,
+ logger);
+ break;
+ }
+
+ case AsymmetricEncryptionAlgorithm.RsaOaepSha256:
+ {
+ encryptedData.Algorithm = SecurityAlgorithms.RsaOaepSha256;
+ encryptedData.Data = RsaUtils.Encrypt(
+ plainText,
+ certificate,
+ RsaUtils.Padding.OaepSHA256,
+ logger);
+ break;
+ }
+ }
+ }
+ else
+ {
+ // No asymmetric encryption is defined for this policy – return the plaintext.
+ encryptedData.Data = plainText.ToArray();
}
return encryptedData;
@@ -389,56 +580,68 @@ public static byte[] Decrypt(
return dataToDecrypt.Data;
}
- // decrypt data.
- switch (securityPolicyUri)
+ // get the info object.
+ var info = GetInfo(securityPolicyUri);
+
+ // unsupported policy.
+ if (info == null)
{
- case Basic256:
- case Basic256Sha256:
- case Aes128_Sha256_RsaOaep:
- if (dataToDecrypt.Algorithm == SecurityAlgorithms.RsaOaep)
- {
- return RsaUtils.Decrypt(
- new ArraySegment(dataToDecrypt.Data),
- certificate,
- RsaUtils.Padding.OaepSHA1,
- logger);
- }
- break;
- case Basic128Rsa15:
- if (dataToDecrypt.Algorithm == SecurityAlgorithms.Rsa15)
+ throw ServiceResultException.Create(
+ StatusCodes.BadSecurityPolicyRejected,
+ "Unsupported security policy: {0}",
+ securityPolicyUri);
+ }
+
+ // check if asymmetric encryption is possible.
+ if (info.AsymmetricEncryptionAlgorithm != AsymmetricEncryptionAlgorithm.None)
+ {
+ switch (info.AsymmetricEncryptionAlgorithm)
+ {
+ case AsymmetricEncryptionAlgorithm.RsaOaepSha1:
{
- return RsaUtils.Decrypt(
- new ArraySegment(dataToDecrypt.Data),
- certificate,
- RsaUtils.Padding.Pkcs1,
- logger);
+ if (dataToDecrypt.Algorithm == SecurityAlgorithms.RsaOaep)
+ {
+ return RsaUtils.Decrypt(
+ new ArraySegment(dataToDecrypt.Data),
+ certificate,
+ RsaUtils.Padding.OaepSHA1,
+ logger);
+ }
+ break;
}
- break;
- case Aes256_Sha256_RsaPss:
- if (dataToDecrypt.Algorithm == SecurityAlgorithms.RsaOaepSha256)
+
+ case AsymmetricEncryptionAlgorithm.RsaPkcs15Sha1:
{
- return RsaUtils.Decrypt(
- new ArraySegment(dataToDecrypt.Data),
- certificate,
- RsaUtils.Padding.OaepSHA256,
- logger);
+ if (dataToDecrypt.Algorithm == SecurityAlgorithms.Rsa15)
+ {
+ return RsaUtils.Decrypt(
+ new ArraySegment(dataToDecrypt.Data),
+ certificate,
+ RsaUtils.Padding.Pkcs1,
+ logger);
+ }
+ break;
}
- break;
- case ECC_nistP256:
- case ECC_nistP384:
- case ECC_brainpoolP256r1:
- case ECC_brainpoolP384r1:
- case None:
- if (string.IsNullOrEmpty(dataToDecrypt.Algorithm))
+
+ default:
+ case AsymmetricEncryptionAlgorithm.RsaOaepSha256:
{
- return dataToDecrypt.Data;
+ if (dataToDecrypt.Algorithm == SecurityAlgorithms.RsaOaepSha256)
+ {
+ return RsaUtils.Decrypt(
+ new ArraySegment(dataToDecrypt.Data),
+ certificate,
+ RsaUtils.Padding.OaepSHA256,
+ logger);
+ }
+ break;
}
- break;
- default:
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityPolicyRejected,
- "Unsupported security policy: {0}",
- securityPolicyUri);
+ }
+ }
+
+ if (String.IsNullOrEmpty(dataToDecrypt.Algorithm))
+ {
+ return dataToDecrypt.Data;
}
throw ServiceResultException.Create(
@@ -448,185 +651,279 @@ public static byte[] Decrypt(
}
///
- /// Signs the data using the SecurityPolicyUri and returns the signature.
+ /// Creates a signature using the security enhancements if required by the SecurityPolicy.
///
- ///
- public static SignatureData Sign(
- X509Certificate2 certificate,
+ public static SignatureData CreateSignatureData(
string securityPolicyUri,
- byte[] dataToSign)
+ X509Certificate2 signingCertificate,
+ byte[] secureChannelSecret,
+ byte[] remoteCertificate,
+ byte[] remoteChannelCertificate,
+ byte[] localChannelCertificate,
+ byte[] remoteNonce,
+ byte[] localNonce)
{
var signatureData = new SignatureData();
- // check if nothing to do.
- if (dataToSign == null)
+ // nothing more to do if no encryption.
+ if (string.IsNullOrEmpty(securityPolicyUri))
{
return signatureData;
}
- // nothing more to do if no encryption.
- if (string.IsNullOrEmpty(securityPolicyUri))
+ // get the info object.
+ var info = GetInfo(securityPolicyUri);
+
+ // unsupported policy.
+ if (info == null)
{
- return signatureData;
+ throw ServiceResultException.Create(
+ StatusCodes.BadSecurityPolicyRejected,
+ "Unsupported security policy: {0}",
+ securityPolicyUri);
}
+ // create the data to sign.
+ byte[] dataToSign = (info.SecureChannelEnhancements)
+ ? Utils.Append(
+ secureChannelSecret ?? Array.Empty(),
+ remoteCertificate ?? Array.Empty(),
+ remoteChannelCertificate ?? Array.Empty(),
+ localChannelCertificate ?? Array.Empty(),
+ remoteNonce ?? Array.Empty(),
+ localNonce ?? Array.Empty())
+ :
+ Utils.Append(
+ remoteCertificate ?? Array.Empty(),
+ remoteNonce);
+
+ return CreateSignatureData(info, signingCertificate, dataToSign);
+ }
+
+ ///
+ /// Creates a signature on the data provided using the SecurityPolicy.
+ ///
+ public static SignatureData CreateSignatureData(
+ string securityPolicyUri,
+ X509Certificate2 localCertificate,
+ byte[] dataToSign)
+ {
+ var info = GetInfo(securityPolicyUri);
+ return CreateSignatureData(info, localCertificate, dataToSign);
+ }
+
+ ///
+ /// Creates a signature on the data provided using the SecurityPolicy.
+ ///
+ public static SignatureData CreateSignatureData(
+ SecurityPolicyInfo securityPolicy,
+ X509Certificate2 localCertificate,
+ byte[] dataToSign)
+ {
+ var signatureData = new SignatureData();
+
// sign data.
- switch (securityPolicyUri)
+ switch (securityPolicy.AsymmetricSignatureAlgorithm)
{
- case Basic256:
- case Basic128Rsa15:
+ case AsymmetricSignatureAlgorithm.RsaPkcs15Sha1:
signatureData.Algorithm = SecurityAlgorithms.RsaSha1;
- signatureData.Signature = RsaUtils.Rsa_Sign(
- new ArraySegment(dataToSign),
- certificate,
- HashAlgorithmName.SHA1,
- RSASignaturePadding.Pkcs1);
break;
- case Aes128_Sha256_RsaOaep:
- case Basic256Sha256:
+ case AsymmetricSignatureAlgorithm.RsaPkcs15Sha256:
signatureData.Algorithm = SecurityAlgorithms.RsaSha256;
- signatureData.Signature = RsaUtils.Rsa_Sign(
- new ArraySegment(dataToSign),
- certificate,
- HashAlgorithmName.SHA256,
- RSASignaturePadding.Pkcs1);
break;
- case Aes256_Sha256_RsaPss:
+ case AsymmetricSignatureAlgorithm.RsaPssSha256:
signatureData.Algorithm = SecurityAlgorithms.RsaPssSha256;
- signatureData.Signature = RsaUtils.Rsa_Sign(
- new ArraySegment(dataToSign),
- certificate,
- HashAlgorithmName.SHA256,
- RSASignaturePadding.Pss);
break;
- case ECC_nistP256:
- case ECC_brainpoolP256r1:
+ case AsymmetricSignatureAlgorithm.EcdsaSha256:
+ case AsymmetricSignatureAlgorithm.EcdsaSha384:
signatureData.Algorithm = null;
- signatureData.Signature = EccUtils.Sign(
- new ArraySegment(dataToSign),
- certificate,
- HashAlgorithmName.SHA256);
break;
- case ECC_nistP384:
- case ECC_brainpoolP384r1:
- signatureData.Algorithm = null;
- signatureData.Signature = EccUtils.Sign(
- new ArraySegment(dataToSign),
- certificate,
- HashAlgorithmName.SHA384);
- break;
- case None:
+ case AsymmetricSignatureAlgorithm.None:
signatureData.Algorithm = null;
signatureData.Signature = null;
- break;
+ return signatureData;
+ ;
default:
throw ServiceResultException.Create(
StatusCodes.BadSecurityPolicyRejected,
"Unsupported security policy: {0}",
- securityPolicyUri);
+ securityPolicy.Uri);
+ }
+
+ if (securityPolicy.SecureChannelEnhancements)
+ {
+ signatureData.Signature = null;
}
+ signatureData.Signature = CryptoUtils.Sign(
+ new ArraySegment(dataToSign),
+ localCertificate,
+ securityPolicy.AsymmetricSignatureAlgorithm);
+
return signatureData;
}
///
- /// Verifies the signature using the SecurityPolicyUri and return true if valid.
+ /// Creates a signature using the security enhancements if required by the SecurityPolicy.
///
- ///
- public static bool Verify(
- X509Certificate2 certificate,
+ public static bool VerifySignatureData(
+ SignatureData signature,
string securityPolicyUri,
- byte[] dataToVerify,
- SignatureData signature)
+ X509Certificate2 signingCertificate,
+ byte[] secureChannelSecret,
+ byte[] localCertificate,
+ byte[] localChannelCertificate,
+ byte[] remoteChannelCertificate,
+ byte[] localNonce,
+ byte[] remoteNonce)
{
- // check if nothing to do.
- if (signature == null)
+ var signatureData = new SignatureData();
+
+ // nothing more to do if no encryption.
+ if (string.IsNullOrEmpty(securityPolicyUri))
{
return true;
}
- // nothing more to do if no encryption.
- if (string.IsNullOrEmpty(securityPolicyUri))
+ // get the info object.
+ var info = GetInfo(securityPolicyUri);
+
+ // unsupported policy.
+ if (info == null)
+ {
+ throw ServiceResultException.Create(
+ StatusCodes.BadSecurityPolicyRejected,
+ "Unsupported security policy: {0}",
+ securityPolicyUri);
+ }
+
+ // create the data to sign.
+ byte[] dataToVerify = (info.SecureChannelEnhancements)
+ ? Utils.Append(
+ secureChannelSecret ?? Array.Empty(),
+ localCertificate ?? Array.Empty(),
+ localChannelCertificate ?? Array.Empty(),
+ remoteChannelCertificate ?? Array.Empty(),
+ localNonce ?? Array.Empty(),
+ remoteNonce ?? Array.Empty())
+ :
+ Utils.Append(
+ localCertificate ?? Array.Empty(),
+ localNonce);
+
+ return VerifySignatureData(signature, info, signingCertificate, dataToVerify);
+ }
+
+ ///
+ /// Verifies the signature using the SecurityPolicyUri and return true if valid.
+ ///
+ public static bool VerifySignatureData(
+ SignatureData signature,
+ string securityPolicyUri,
+ X509Certificate2 signingCertificate,
+ byte[] dataToVerify)
+ {
+ var info = GetInfo(securityPolicyUri);
+ return VerifySignatureData(signature, info, signingCertificate, dataToVerify);
+ }
+
+ ///
+ /// Verifies the signature using the SecurityPolicyUri and return true if valid.
+ ///
+ public static bool VerifySignatureData(
+ SignatureData signature,
+ SecurityPolicyInfo securityPolicy,
+ X509Certificate2 signingCertificate,
+ byte[] dataToVerify)
+ {
+ // check if nothing to do.
+ if (signature == null)
{
return true;
}
- // decrypt data.
- switch (securityPolicyUri)
+ // sign data.
+ switch (securityPolicy.AsymmetricSignatureAlgorithm)
{
- case Basic256:
- case Basic128Rsa15:
+ // always accept signatures if security is not used.
+ case AsymmetricSignatureAlgorithm.None:
+ return true;
+
+ case AsymmetricSignatureAlgorithm.RsaPkcs15Sha1:
+ {
if (signature.Algorithm == SecurityAlgorithms.RsaSha1)
{
return RsaUtils.Rsa_Verify(
new ArraySegment(dataToVerify),
signature.Signature,
- certificate,
+ signingCertificate,
HashAlgorithmName.SHA1,
RSASignaturePadding.Pkcs1);
}
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Unexpected signature algorithm for Basic256/Basic128Rsa15: {0}\n" +
- "Expected signature algorithm: {1}",
- signature.Algorithm,
- SecurityAlgorithms.RsaSha1);
- case Aes128_Sha256_RsaOaep:
- case Basic256Sha256:
- if (signature.Algorithm == SecurityAlgorithms.RsaSha256)
+ break;
+ }
+
+ case AsymmetricSignatureAlgorithm.RsaPkcs15Sha256:
+ {
+ if (String.IsNullOrEmpty(signature.Algorithm) || signature.Algorithm == SecurityAlgorithms.RsaSha256)
{
return RsaUtils.Rsa_Verify(
new ArraySegment(dataToVerify),
signature.Signature,
- certificate,
+ signingCertificate,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
}
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Unexpected signature algorithm for Basic256Sha256/Aes128_Sha256_RsaOaep: {0}\n" +
- "Expected signature algorithm: {1}",
- signature.Algorithm,
- SecurityAlgorithms.RsaSha256);
- case Aes256_Sha256_RsaPss:
- if (signature.Algorithm == SecurityAlgorithms.RsaPssSha256)
+ break;
+ }
+
+ case AsymmetricSignatureAlgorithm.RsaPssSha256:
+ {
+ if (String.IsNullOrEmpty(signature.Algorithm) || signature.Algorithm == SecurityAlgorithms.RsaPssSha256)
{
return RsaUtils.Rsa_Verify(
new ArraySegment(dataToVerify),
signature.Signature,
- certificate,
+ signingCertificate,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pss);
}
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Unexpected signature algorithm for Aes256_Sha256_RsaPss: {0}\n" +
- "Expected signature algorithm : {1}",
- signature.Algorithm,
- SecurityAlgorithms.RsaPssSha256);
- case ECC_nistP256:
- case ECC_brainpoolP256r1:
- return EccUtils.Verify(
- new ArraySegment(dataToVerify),
- signature.Signature,
- certificate,
- HashAlgorithmName.SHA256);
- case ECC_nistP384:
- case ECC_brainpoolP384r1:
- return EccUtils.Verify(
- new ArraySegment(dataToVerify),
- signature.Signature,
- certificate,
- HashAlgorithmName.SHA384);
- // always accept signatures if security is not used.
- case None:
- return true;
- default:
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityPolicyRejected,
- "Unsupported security policy: {0}",
- securityPolicyUri);
+ break;
+ }
+
+ case AsymmetricSignatureAlgorithm.EcdsaSha256:
+ {
+ if (String.IsNullOrEmpty(signature.Algorithm) || signature.Algorithm == securityPolicy.Uri)
+ {
+ return CryptoUtils.Verify(
+ new ArraySegment(dataToVerify),
+ signature.Signature,
+ signingCertificate,
+ securityPolicy.AsymmetricSignatureAlgorithm);
+ }
+
+ break;
+ }
+
+ case AsymmetricSignatureAlgorithm.EcdsaSha384:
+ {
+ if (String.IsNullOrEmpty(signature.Algorithm) || signature.Algorithm == securityPolicy.Uri)
+ {
+ return CryptoUtils.Verify(
+ new ArraySegment(dataToVerify),
+ signature.Signature,
+ signingCertificate,
+ securityPolicy.AsymmetricSignatureAlgorithm);
+ }
+
+ break;
+ }
}
+
+ throw ServiceResultException.Create(
+ StatusCodes.BadSecurityChecksFailed,
+ "Unexpected SignatureData algorithm: {0}",
+ signature.Algorithm);
}
///
@@ -669,6 +966,63 @@ public static bool Verify(
return keyValuePairs.ToFrozenDictionary();
#else
return new ReadOnlyDictionary(keyValuePairs);
+#endif
+ });
+
+ ///
+ /// Creates a dictionary of uris to SecurityPolicyInfo excluding base uri
+ ///
+ private static readonly Lazy> s_securityPolicyUriToInfo =
+ new(() =>
+ {
+#if NET8_0_OR_GREATER
+ return s_securityPolicyNameToInfo.Value.ToFrozenDictionary(k => k.Value.Uri, k => k.Value);
+#else
+ return new ReadOnlyDictionary(
+ s_securityPolicyNameToInfo.Value.ToDictionary(k => k.Value.Uri, k => k.Value));
+#endif
+ });
+
+ ///
+ /// Creates a dictionary for names to SecurityPolicyInfo excluding base uri
+ ///
+ private static readonly Lazy> s_securityPolicyNameToInfo =
+ new(() =>
+ {
+ FieldInfo[] policyFields = typeof(SecurityPolicies).GetFields(
+ BindingFlags.Public | BindingFlags.Static);
+
+ FieldInfo[] infoFields = typeof(SecurityPolicyInfo).GetFields(
+ BindingFlags.Public | BindingFlags.Static);
+
+ var keyValuePairs = new Dictionary();
+ foreach (FieldInfo field in policyFields)
+ {
+ string policyUri = (string)field.GetValue(typeof(SecurityPolicies));
+ if (field.Name == nameof(BaseUri) ||
+ field.Name == nameof(Https) ||
+ !policyUri.StartsWith(BaseUri, StringComparison.Ordinal))
+ {
+ continue;
+ }
+
+ // Find the corresponding SecurityPolicyInfo field by name
+ FieldInfo infoField = Array.Find(infoFields, f => f.Name == field.Name);
+ if (infoField != null && infoField.FieldType == typeof(SecurityPolicyInfo))
+ {
+ SecurityPolicyInfo info = (SecurityPolicyInfo)infoField.GetValue(null);
+ keyValuePairs.Add(field.Name, info);
+ }
+ else
+ {
+ // Fallback to creating a minimal instance for unknown policies
+ keyValuePairs.Add(field.Name, new SecurityPolicyInfo(policyUri, field.Name));
+ }
+ }
+#if NET8_0_OR_GREATER
+ return keyValuePairs.ToFrozenDictionary();
+#else
+ return new ReadOnlyDictionary(keyValuePairs);
#endif
});
}
diff --git a/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicyInfo.cs b/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicyInfo.cs
new file mode 100644
index 0000000000..786f714230
--- /dev/null
+++ b/Stack/Opc.Ua.Core/Security/Constants/SecurityPolicyInfo.cs
@@ -0,0 +1,1368 @@
+/* Copyright (c) 1996-2022 The OPC Foundation. All rights reserved.
+ The source code in this file is covered under a dual-license scenario:
+ - RCL: for OPC Foundation Corporate Members in good-standing
+ - GPL V2: everybody else
+ RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
+ GNU General Public License as published by the Free Software Foundation;
+ version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
+ This source code is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+*/
+
+using System;
+using System.IO;
+using System.Security.Cryptography;
+
+namespace Opc.Ua
+{
+ ///
+ /// Defines constants for key security policies.
+ ///
+ public class SecurityPolicyInfo
+ {
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The unique identifier.
+ /// The display name.
+ ///
+ public SecurityPolicyInfo(string uri, string name = null)
+ {
+ if (string.IsNullOrEmpty(uri))
+ {
+ throw new ArgumentException("The URI is not a valid security policy.", nameof(uri));
+ }
+
+ Uri = uri;
+ Name = name ?? SecurityPolicies.GetDisplayName(uri) ?? uri;
+ }
+
+ ///
+ /// Short name for the policy.
+ ///
+ public string Name { get; }
+
+ ///
+ /// The unique identifier for the policy.
+ ///
+ public string Uri { get; }
+
+ ///
+ /// Returns true if the policy is considered deprecated and should not be used for new deployments.
+ ///
+ public bool IsDeprecated { get; private set; }
+
+ ///
+ /// The symmetric signature algorithm to use.
+ ///
+ public SymmetricSignatureAlgorithm SymmetricSignatureAlgorithm { get; private set; }
+
+ ///
+ /// The symmetric encryption algorithm to use.
+ ///
+ public SymmetricEncryptionAlgorithm SymmetricEncryptionAlgorithm { get; private set; }
+
+ ///
+ /// The asymmetric signature algorithm to use.
+ ///
+ public AsymmetricSignatureAlgorithm AsymmetricSignatureAlgorithm { get; private set; }
+
+ ///
+ /// The symmetric encryption algorithm to use.
+ ///
+ public AsymmetricEncryptionAlgorithm AsymmetricEncryptionAlgorithm { get; private set; }
+
+ ///
+ /// The minimum length, in bits, for an asymmetric key.
+ ///
+ public int MinAsymmetricKeyLength { get; private set; }
+
+ ///
+ /// The maximum length, in bits, for an asymmetric key.
+ ///
+ public int MaxAsymmetricKeyLength { get; private set; }
+
+ ///
+ /// The key derivation algorithm to use.
+ ///
+ public KeyDerivationAlgorithm KeyDerivationAlgorithm { get; private set; }
+
+ ///
+ /// The length in bytes of the derived key used for message authentication.
+ ///
+ public int DerivedSignatureKeyLength { get; private set; }
+
+ ///
+ /// The asymmetric signature algorithm used to sign certificates.
+ ///
+ public AsymmetricSignatureAlgorithm CertificateSignatureAlgorithm { get; private set; }
+
+ ///
+ /// Returns algorithm family used to create asymmetric key pairs used with Certificates.
+ ///
+ public CertificateKeyFamily CertificateKeyFamily { get; private set; }
+
+ ///
+ /// The algorithm used to create asymmetric key pairs used with Certificates.
+ ///
+ public CertificateKeyAlgorithm CertificateKeyAlgorithm { get; private set; }
+
+ ///
+ /// The algorithm used to create asymmetric key pairs used for EphemeralKeys.
+ ///
+ public CertificateKeyAlgorithm EphemeralKeyAlgorithm { get; private set; }
+
+ ///
+ /// The algorithm used to calculate the thumbprint of the certificate.
+ ///
+ public CertificateThumbprintAlgorithm CertificateThumbprintAlgorithm { get; private set; }
+
+ ///
+ /// The length, in bytes, of the Nonces used when opening a SecureChannel.
+ ///
+ public int SecureChannelNonceLength { get; private set; }
+
+ ///
+ /// The length, in bytes, of the data used to initialize the symmetric algorithm.
+ ///
+ public int InitializationVectorLength { get; private set; }
+
+ ///
+ /// The length, in bytes, of the symmetric signature.
+ ///
+ public int SymmetricSignatureLength { get; private set; }
+
+ ///
+ /// The length, in bytes, of the symmetric encryption key.
+ ///
+ public int SymmetricEncryptionKeyLength { get; private set; }
+
+ ///
+ /// If TRUE, the 1024 based SequenceNumber rules apply to the SecurityPolicy.
+ /// If FALSE, the 0 based SequenceNumber rules apply.
+ ///
+ public bool LegacySequenceNumbers { get; private set; }
+
+ ///
+ /// If TRUE, the enhancements to the SecureChannel are required for the SecurityPolicy.
+ /// • Channel-bound Signature calculations in CreateSession/ActivateSession;
+ /// • Session transfer tokens in ActivateSession;
+ /// • Chained symmetric key derivation when renewing SecureChannels.
+ /// • Allow padding when using Authenticated Encryption;
+ ///
+ public bool SecureChannelEnhancements { get; private set; }
+
+ ///
+ /// Whether the padding is required with symmetric encryption.
+ ///
+ public bool NoSymmetricEncryptionPadding =>
+ SymmetricEncryptionAlgorithm == SymmetricEncryptionAlgorithm.Aes256Gcm ||
+ SymmetricEncryptionAlgorithm == SymmetricEncryptionAlgorithm.Aes128Gcm ||
+ SymmetricEncryptionAlgorithm == SymmetricEncryptionAlgorithm.ChaCha20Poly1305;
+
+ ///
+ /// Returns the derived server key data length.
+ ///
+ public int ServerKeyDataLength =>
+ (DerivedSignatureKeyLength + SymmetricEncryptionKeyLength + InitializationVectorLength);
+
+ ///
+ /// Returns the derived client key data length.
+ ///
+ public int ClientKeyDataLength =>
+ (DerivedSignatureKeyLength + SymmetricEncryptionKeyLength + InitializationVectorLength);
+
+ ///
+ /// Returns the data to be signed by the server when creating a session.
+ ///
+ public byte[] GetUserTokenSignatureData(
+ byte[] channelThumbprint,
+ byte[] serverNonce,
+ byte[] serverCertificate,
+ byte[] serverChannelCertificate,
+ byte[] clientCertificate,
+ byte[] clientChannelCertificate,
+ byte[] clientNonce)
+ {
+ byte[] data = null;
+
+ CryptoTrace.Start(ConsoleColor.Yellow, "UserTokenSignatureData");
+
+ if (SecureChannelEnhancements)
+ {
+ CryptoTrace.WriteLine($"ChannelThumbprint={CryptoTrace.KeyToString(channelThumbprint)}");
+ CryptoTrace.WriteLine($"ServerNonce={CryptoTrace.KeyToString(serverNonce)}");
+ CryptoTrace.WriteLine($"ServerCertificate={CryptoTrace.KeyToString(serverCertificate)}");
+ CryptoTrace.WriteLine($"ServerChannelCertificate={CryptoTrace.KeyToString(serverChannelCertificate)}");
+ CryptoTrace.WriteLine($"ClientCertificate={CryptoTrace.KeyToString(clientCertificate)}");
+ CryptoTrace.WriteLine($"ClientChannelCertificate={CryptoTrace.KeyToString(clientChannelCertificate)}");
+ CryptoTrace.WriteLine($"ClientNonce={CryptoTrace.KeyToString(clientNonce)}");
+
+ using HashAlgorithm hash = CertificateThumbprintAlgorithm switch
+ {
+ CertificateThumbprintAlgorithm.SHA256 => SHA256.Create(),
+ CertificateThumbprintAlgorithm.SHA384 => SHA384.Create(),
+ CertificateThumbprintAlgorithm.SHA512 => SHA512.Create(),
+ _ => throw new NotSupportedException()
+ };
+
+ var serverCertificateHash = serverCertificate != null ? hash.ComputeHash(serverCertificate) : null;
+ var serverChannelCertificateHash = serverChannelCertificate != null ? hash.ComputeHash(serverChannelCertificate) : null;
+ var clientCertificateHash = clientCertificate != null ? hash.ComputeHash(clientCertificate) : null;
+ var clientChannelCertificateHash = clientChannelCertificate != null ? hash.ComputeHash(clientChannelCertificate) : null;
+
+ data = Utils.Append(
+ channelThumbprint,
+ serverNonce,
+ serverCertificateHash,
+ serverChannelCertificateHash,
+ clientCertificateHash,
+ clientChannelCertificateHash,
+ clientNonce);
+ }
+ else
+ {
+ CryptoTrace.WriteLine($"ServerCertificate={CryptoTrace.KeyToString(serverCertificate)}");
+ CryptoTrace.WriteLine($"ServerNonce={CryptoTrace.KeyToString(serverNonce)}");
+
+ data = Utils.Append(
+ serverCertificate,
+ serverNonce);
+ }
+
+ CryptoTrace.Finish("UserTokenSignatureData");
+ return data;
+ }
+
+ ///
+ /// Returns the data to be signed by the server when creating a session.
+ ///
+ public byte[] GetServerSignatureData(
+ byte[] channelThumbprint,
+ byte[] clientNonce,
+ byte[] serverChannelCertificate,
+ byte[] clientCertificate,
+ byte[] clientChannelCertificate,
+ byte[] serverNonce)
+ {
+ byte[] data = null;
+
+ CryptoTrace.Start(ConsoleColor.Yellow, "ServerSignatureData");
+
+ if (SecureChannelEnhancements)
+ {
+ CryptoTrace.WriteLine($"ChannelThumbprint={CryptoTrace.KeyToString(channelThumbprint)}");
+ CryptoTrace.WriteLine($"ClientNonce={CryptoTrace.KeyToString(clientNonce)}");
+ CryptoTrace.WriteLine($"ServerChannelCertificate={CryptoTrace.KeyToString(serverChannelCertificate)}");
+ CryptoTrace.WriteLine($"ClientChannelCertificate={CryptoTrace.KeyToString(clientChannelCertificate)}");
+ CryptoTrace.WriteLine($"ServerNonce={CryptoTrace.KeyToString(serverNonce)}");
+
+ using HashAlgorithm hash = CertificateThumbprintAlgorithm switch
+ {
+ CertificateThumbprintAlgorithm.SHA256 => SHA256.Create(),
+ CertificateThumbprintAlgorithm.SHA384 => SHA384.Create(),
+ CertificateThumbprintAlgorithm.SHA512 => SHA512.Create(),
+ _ => throw new NotSupportedException()
+ };
+
+ var serverChannelCertificateHash = serverChannelCertificate != null ? hash.ComputeHash(serverChannelCertificate) : null;
+ var clientChannelCertificateHash = clientChannelCertificate != null ? hash.ComputeHash(clientChannelCertificate) : null;
+
+ data = Utils.Append(
+ channelThumbprint,
+ clientNonce,
+ serverChannelCertificateHash,
+ clientChannelCertificateHash,
+ serverNonce);
+ }
+ else
+ {
+ CryptoTrace.WriteLine($"ClientCertificate={CryptoTrace.KeyToString(clientCertificate)}");
+ CryptoTrace.WriteLine($"ClientNonce={CryptoTrace.KeyToString(clientNonce)}");
+
+ data = Utils.Append(
+ clientCertificate,
+ clientNonce);
+ }
+
+ CryptoTrace.Finish("ServerSignatureData");
+ return data;
+ }
+
+ ///
+ /// Returns the data to be signed by the client when creating a session.
+ ///
+ public byte[] GetClientSignatureData(
+ byte[] channelThumbprint,
+ byte[] serverNonce,
+ byte[] serverCertificate,
+ byte[] serverChannelCertificate,
+ byte[] clientChannelCertificate,
+ byte[] clientNonce)
+ {
+ byte[] data = null;
+
+ CryptoTrace.Start(ConsoleColor.Yellow, "ClientSignatureData");
+
+ if (SecureChannelEnhancements)
+ {
+ CryptoTrace.WriteLine($"ChannelThumbprint={CryptoTrace.KeyToString(channelThumbprint)}");
+ CryptoTrace.WriteLine($"ServerNonce={CryptoTrace.KeyToString(serverNonce)}");
+ CryptoTrace.WriteLine($"ServerCertificate={CryptoTrace.KeyToString(serverCertificate)}");
+ CryptoTrace.WriteLine($"ServerChannelCertificate={CryptoTrace.KeyToString(serverChannelCertificate)}");
+ CryptoTrace.WriteLine($"ClientChannelCertificate={CryptoTrace.KeyToString(clientChannelCertificate)}");
+ CryptoTrace.WriteLine($"ClientNonce={CryptoTrace.KeyToString(clientNonce)}");
+
+ using HashAlgorithm hash = CertificateThumbprintAlgorithm switch
+ {
+ CertificateThumbprintAlgorithm.SHA256 => SHA256.Create(),
+ CertificateThumbprintAlgorithm.SHA384 => SHA384.Create(),
+ CertificateThumbprintAlgorithm.SHA512 => SHA512.Create(),
+ _ => throw new NotSupportedException()
+ };
+
+ var serverCertificateHash = serverCertificate != null ? hash.ComputeHash(serverCertificate) : null;
+ var serverChannelCertificateHash = serverChannelCertificate != null ? hash.ComputeHash(serverChannelCertificate) : null;
+ var clientChannelCertificateHash = clientChannelCertificate != null ? hash.ComputeHash(clientChannelCertificate) : null;
+
+ data = Utils.Append(
+ channelThumbprint,
+ serverNonce,
+ serverCertificateHash,
+ serverChannelCertificateHash,
+ clientChannelCertificateHash,
+ clientNonce);
+ }
+ else
+ {
+ CryptoTrace.WriteLine($"ServerCertificate={CryptoTrace.KeyToString(serverCertificate)}");
+ CryptoTrace.WriteLine($"ServerNonce={CryptoTrace.KeyToString(serverNonce)}");
+
+ data = Utils.Append(
+ serverCertificate,
+ serverNonce);
+ }
+
+ CryptoTrace.Finish("ClientSignatureData");
+ return data;
+ }
+
+ ///
+ /// Returns a HMAC based on the symmetric signature algorithm.
+ ///
+ public HMAC CreateSignatureHmac(byte[] signingKey)
+ {
+#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms
+ return SymmetricSignatureAlgorithm switch
+ {
+ SymmetricSignatureAlgorithm.HmacSha1 => new HMACSHA1(signingKey),
+ SymmetricSignatureAlgorithm.HmacSha256 => new HMACSHA256(signingKey),
+ SymmetricSignatureAlgorithm.HmacSha384 => new HMACSHA384(signingKey),
+ _ => null
+ };
+#pragma warning restore CA5350 // Do Not Use Weak Cryptographic Algorithms
+ }
+
+ ///
+ /// Returns a HashAlgorithmName based on the KeyDerivationAlgorithm.
+ ///
+ public HashAlgorithmName GetKeyDerivationHashAlgorithmName()
+ {
+ return KeyDerivationAlgorithm switch
+ {
+ KeyDerivationAlgorithm.PSha1 => HashAlgorithmName.SHA1,
+ KeyDerivationAlgorithm.PSha256 => HashAlgorithmName.SHA256,
+ KeyDerivationAlgorithm.HKDFSha256 => HashAlgorithmName.SHA256,
+ KeyDerivationAlgorithm.HKDFSha384 => HashAlgorithmName.SHA384,
+ _ => HashAlgorithmName.SHA256
+ };
+ }
+
+ ///
+ /// The security policy that does not provide any security.
+ ///
+ public static readonly SecurityPolicyInfo None = new(SecurityPolicies.None)
+ {
+ DerivedSignatureKeyLength = 0,
+ SymmetricEncryptionKeyLength = 0,
+ InitializationVectorLength = 0,
+ SymmetricSignatureLength = 0,
+ MinAsymmetricKeyLength = 0,
+ MaxAsymmetricKeyLength = 0,
+ SecureChannelNonceLength = 32,
+ LegacySequenceNumbers = true,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.None,
+ CertificateKeyFamily = CertificateKeyFamily.None,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.None,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.None,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.None,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.None,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.None,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.None,
+ SecureChannelEnhancements = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA1
+ };
+
+ ///
+ /// The security policy that uses SHA1 and 128 bit encryption. This policy is considered insecure and should not be used for new deployments.
+ ///
+ public static readonly SecurityPolicyInfo Basic128Rsa15 = new(SecurityPolicies.Basic128Rsa15)
+ {
+ DerivedSignatureKeyLength = 128 / 8,
+ SymmetricEncryptionKeyLength = 128 / 8,
+ // HMAC-SHA1 produces a 160-bit MAC
+ SymmetricSignatureLength = 160 / 8,
+ InitializationVectorLength = 128 / 8,
+ MinAsymmetricKeyLength = 1024,
+ MaxAsymmetricKeyLength = 2048,
+ SecureChannelNonceLength = 16,
+ LegacySequenceNumbers = true,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.RsaPkcs15Sha1,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.RsaPkcs15Sha1,
+ CertificateKeyFamily = CertificateKeyFamily.RSA,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.RSA,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.RsaPkcs15Sha1,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.None,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.PSha1,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.Aes128Cbc,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.HmacSha1,
+ IsDeprecated = true,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA1
+ };
+
+ ///
+ /// The security policy that uses SHA1 and 256 bit encryption. This policy is considered insecure and should not be used for new deployments.
+ ///
+ public static readonly SecurityPolicyInfo Basic256 = new(SecurityPolicies.Basic256)
+ {
+ DerivedSignatureKeyLength = 192 / 8,
+ SymmetricEncryptionKeyLength = 256 / 8,
+ // HMAC-SHA1 produces a 160-bit MAC
+ SymmetricSignatureLength = 160 / 8,
+ InitializationVectorLength = 128 / 8,
+ MinAsymmetricKeyLength = 1024,
+ MaxAsymmetricKeyLength = 2048,
+ SecureChannelNonceLength = 32,
+ LegacySequenceNumbers = true,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.RsaOaepSha1,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.RsaPkcs15Sha1,
+ CertificateKeyFamily = CertificateKeyFamily.RSA,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.RSA,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.RsaPkcs15Sha1,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.None,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.PSha1,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.Aes256Cbc,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.HmacSha1,
+ IsDeprecated = true,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA1
+ };
+
+ ///
+ /// Aes128_Sha256_RsaOaep is a required minimum security policy. It uses SHA256 and 128 bit encryption.
+ ///
+ public static readonly SecurityPolicyInfo Aes128_Sha256_RsaOaep = new(SecurityPolicies.Aes128_Sha256_RsaOaep)
+ {
+ DerivedSignatureKeyLength = 256 / 8,
+ SymmetricEncryptionKeyLength = 128 / 8,
+ SymmetricSignatureLength = 256 / 8,
+ InitializationVectorLength = 128 / 8,
+ MinAsymmetricKeyLength = 2048,
+ MaxAsymmetricKeyLength = 4096,
+ SecureChannelNonceLength = 32,
+ LegacySequenceNumbers = true,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.RsaOaepSha1,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.RsaPkcs15Sha256,
+ CertificateKeyFamily = CertificateKeyFamily.RSA,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.RSA,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.RsaPkcs15Sha256,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.None,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.PSha256,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.Aes256Cbc,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.HmacSha256,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA1
+ };
+
+ ///
+ /// Basic256Sha256 is a required minimum security policy. It uses SHA256 and 256 bit encryption.
+ ///
+ public static readonly SecurityPolicyInfo Basic256Sha256 = new(SecurityPolicies.Basic256Sha256)
+ {
+ DerivedSignatureKeyLength = 256 / 8,
+ SymmetricEncryptionKeyLength = 256 / 8,
+ SymmetricSignatureLength = 256 / 8,
+ InitializationVectorLength = 128 / 8,
+ MinAsymmetricKeyLength = 2048,
+ MaxAsymmetricKeyLength = 4096,
+ SecureChannelNonceLength = 32,
+ LegacySequenceNumbers = true,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.RsaOaepSha1,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.RsaPkcs15Sha256,
+ CertificateKeyFamily = CertificateKeyFamily.RSA,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.RSA,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.RsaPkcs15Sha256,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.PSha256,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.None,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.Aes256Cbc,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.HmacSha256,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA1
+ };
+
+ ///
+ /// Aes256_Sha256_RsaPss is a optional high security policy. It uses SHA256 and 256 bit encryption.
+ ///
+ public static readonly SecurityPolicyInfo Aes256_Sha256_RsaPss = new(SecurityPolicies.Aes256_Sha256_RsaPss)
+ {
+ DerivedSignatureKeyLength = 256 / 8,
+ SymmetricEncryptionKeyLength = 256 / 8,
+ MinAsymmetricKeyLength = 2048,
+ MaxAsymmetricKeyLength = 4096,
+ SecureChannelNonceLength = 32,
+ LegacySequenceNumbers = true,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.RsaOaepSha256,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.RsaPssSha256,
+ CertificateKeyFamily = CertificateKeyFamily.RSA,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.RSA,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.RsaPkcs15Sha256,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.None,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.PSha256,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.Aes256Cbc,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.HmacSha256,
+ InitializationVectorLength = 128 / 8,
+ SymmetricSignatureLength = 256 / 8,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA1
+ };
+
+ ///
+ /// ECC curve25519 is a required minimum security policy. It uses ChaChaPoly and 256 bit encryption.
+ ///
+ public readonly static SecurityPolicyInfo ECC_curve25519 = new(SecurityPolicies.ECC_curve25519)
+ {
+ DerivedSignatureKeyLength = 0,
+ SymmetricEncryptionKeyLength = 256 / 8,
+ InitializationVectorLength = 96 / 8,
+ SymmetricSignatureLength = 128 / 8,
+ MinAsymmetricKeyLength = 256,
+ MaxAsymmetricKeyLength = 256,
+ SecureChannelNonceLength = 32,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaPure25519,
+ CertificateKeyFamily = CertificateKeyFamily.ECC,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.Curve25519,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaPure25519,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.Curve25519,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha256,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.ChaCha20Poly1305,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.ChaCha20Poly1305,
+ SecureChannelEnhancements = false,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA256
+ };
+
+ ///
+ /// ECC curve25519 is a required minimum security policy. It uses AES-GCM for symmetric encryption.
+ ///
+ public readonly static SecurityPolicyInfo ECC_curve25519_AesGcm = new(SecurityPolicies.ECC_curve25519_AesGcm)
+ {
+ DerivedSignatureKeyLength = 0,
+ SymmetricEncryptionKeyLength = 128 / 8,
+ InitializationVectorLength = 96 / 8,
+ SymmetricSignatureLength = 128 / 8,
+ MinAsymmetricKeyLength = 256,
+ MaxAsymmetricKeyLength = 256,
+ SecureChannelNonceLength = 32,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaPure25519,
+ CertificateKeyFamily = CertificateKeyFamily.ECC,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.Curve25519,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaPure25519,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.Curve25519,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha256,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.Aes128Gcm,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.Aes128Gcm,
+ SecureChannelEnhancements = true,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA256
+ };
+
+ ///
+ /// ECC curve25519 is a required minimum security policy. It uses ChaCha20Poly1305 for symmetric encryption.
+ ///
+ public readonly static SecurityPolicyInfo ECC_curve25519_ChaChaPoly = new(SecurityPolicies.ECC_curve25519_ChaChaPoly)
+ {
+ DerivedSignatureKeyLength = 0,
+ SymmetricEncryptionKeyLength = 256 / 8,
+ InitializationVectorLength = 96 / 8,
+ SymmetricSignatureLength = 128 / 8,
+ MinAsymmetricKeyLength = 256,
+ MaxAsymmetricKeyLength = 256,
+ SecureChannelNonceLength = 32,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaPure25519,
+ CertificateKeyFamily = CertificateKeyFamily.ECC,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.Curve25519,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaPure25519,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.Curve25519,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha256,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.ChaCha20Poly1305,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.ChaCha20Poly1305,
+ SecureChannelEnhancements = true,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA256
+ };
+
+ ///
+ /// ECC curve448 is a required minimum security policy. It uses ChaChaPoly and 256 bit encryption.
+ ///
+ public readonly static SecurityPolicyInfo ECC_curve448 = new(SecurityPolicies.ECC_curve448)
+ {
+ DerivedSignatureKeyLength = 0,
+ SymmetricEncryptionKeyLength = 256 / 8,
+ InitializationVectorLength = 96 / 8,
+ SymmetricSignatureLength = 128 / 8,
+ MinAsymmetricKeyLength = 456,
+ MaxAsymmetricKeyLength = 456,
+ SecureChannelNonceLength = 56,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaPure448,
+ CertificateKeyFamily = CertificateKeyFamily.ECC,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.Curve448,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaPure448,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.Curve448,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha256,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.ChaCha20Poly1305,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.ChaCha20Poly1305,
+ SecureChannelEnhancements = false,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA384
+ };
+
+ ///
+ /// ECC curve448 is a required minimum security policy. It uses AES-GCM for symmetric encryption.
+ ///
+ public readonly static SecurityPolicyInfo ECC_curve448_AesGcm = new(SecurityPolicies.ECC_curve448_AesGcm)
+ {
+ DerivedSignatureKeyLength = 0,
+ SymmetricEncryptionKeyLength = 256 / 8,
+ InitializationVectorLength = 96 / 8,
+ SymmetricSignatureLength = 128 / 8,
+ MinAsymmetricKeyLength = 456,
+ MaxAsymmetricKeyLength = 456,
+ SecureChannelNonceLength = 56,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaPure448,
+ CertificateKeyFamily = CertificateKeyFamily.ECC,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.Curve448,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaPure448,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.Curve448,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha256,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.Aes128Gcm,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.Aes128Gcm,
+ SecureChannelEnhancements = true,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA384
+ };
+
+ ///
+ /// ECC Curve448 is a required minimum security policy. It uses ChaCha20Poly1305 for symmetric encryption.
+ ///
+ public readonly static SecurityPolicyInfo ECC_curve448_ChaChaPoly = new(SecurityPolicies.ECC_curve448_ChaChaPoly)
+ {
+ DerivedSignatureKeyLength = 0,
+ SymmetricEncryptionKeyLength = 256 / 8,
+ InitializationVectorLength = 96 / 8,
+ SymmetricSignatureLength = 128 / 8,
+ MinAsymmetricKeyLength = 456,
+ MaxAsymmetricKeyLength = 456,
+ SecureChannelNonceLength = 56,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaPure448,
+ CertificateKeyFamily = CertificateKeyFamily.ECC,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.Curve448,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaPure448,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.Curve448,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha256,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.ChaCha20Poly1305,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.ChaCha20Poly1305,
+ SecureChannelEnhancements = true,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA384
+ };
+
+ ///
+ /// The ECC nistP256 is a required minimum security policy.
+ ///
+ public readonly static SecurityPolicyInfo ECC_nistP256 = new(SecurityPolicies.ECC_nistP256)
+ {
+ DerivedSignatureKeyLength = 256 / 8,
+ SymmetricEncryptionKeyLength = 128 / 8,
+ InitializationVectorLength = 128 / 8,
+ SymmetricSignatureLength = 256 / 8,
+ MinAsymmetricKeyLength = 256,
+ MaxAsymmetricKeyLength = 256,
+ SecureChannelNonceLength = 64,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha256,
+ CertificateKeyFamily = CertificateKeyFamily.ECC,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.NistP256,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha256,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.NistP256,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha256,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.Aes128Cbc,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.HmacSha256,
+ SecureChannelEnhancements = false,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA256
+ };
+
+ ///
+ /// The ECC_nistP256_AesGcm is an ECC nistP256 variant that uses AES-GCM for symmetric encryption.
+ ///
+ public readonly static SecurityPolicyInfo ECC_nistP256_AesGcm = new(SecurityPolicies.ECC_nistP256_AesGcm)
+ {
+ DerivedSignatureKeyLength = 0,
+ SymmetricEncryptionKeyLength = 128 / 8,
+ InitializationVectorLength = 96 / 8,
+ SymmetricSignatureLength = 128 / 8,
+ MinAsymmetricKeyLength = 256,
+ MaxAsymmetricKeyLength = 256,
+ SecureChannelNonceLength = 64,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha256,
+ CertificateKeyFamily = CertificateKeyFamily.ECC,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.NistP256,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha256,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.NistP256,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha256,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.Aes128Gcm,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.Aes128Gcm,
+ SecureChannelEnhancements = true,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA256
+ };
+
+ ///
+ /// The ECC_nistP256_AesGcm is an ECC nistP256 variant that uses ChaCha20Poly1305 for symmetric encryption.
+ ///
+ public readonly static SecurityPolicyInfo ECC_nistP256_ChaChaPoly = new(SecurityPolicies.ECC_nistP256_ChaChaPoly)
+ {
+ DerivedSignatureKeyLength = 0,
+ SymmetricEncryptionKeyLength = 256 / 8,
+ InitializationVectorLength = 96 / 8,
+ SymmetricSignatureLength = 128 / 8,
+ MinAsymmetricKeyLength = 256,
+ MaxAsymmetricKeyLength = 256,
+ SecureChannelNonceLength = 64,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha256,
+ CertificateKeyFamily = CertificateKeyFamily.ECC,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.NistP256,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha256,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.NistP256,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha256,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.ChaCha20Poly1305,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.ChaCha20Poly1305,
+ SecureChannelEnhancements = true,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA256
+ };
+
+ ///
+ /// The ECC nistP384 is an optional high security policy.
+ ///
+ public readonly static SecurityPolicyInfo ECC_nistP384 = new(SecurityPolicies.ECC_nistP384)
+ {
+ DerivedSignatureKeyLength = 384 / 8,
+ SymmetricEncryptionKeyLength = 256 / 8,
+ InitializationVectorLength = 128 / 8,
+ SymmetricSignatureLength = 384 / 8,
+ MinAsymmetricKeyLength = 384,
+ MaxAsymmetricKeyLength = 384,
+ SecureChannelNonceLength = 96,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha384,
+ CertificateKeyFamily = CertificateKeyFamily.ECC,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.NistP384,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha384,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.NistP384,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha384,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.Aes256Cbc,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.HmacSha384,
+ SecureChannelEnhancements = false,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA384
+ };
+
+ ///
+ /// The ECC nistP384 is an optional high security policy that uses AES-GCM for symmetric encryption.
+ ///
+ public readonly static SecurityPolicyInfo ECC_nistP384_AesGcm = new(SecurityPolicies.ECC_nistP384_AesGcm)
+ {
+ DerivedSignatureKeyLength = 0,
+ SymmetricEncryptionKeyLength = 256 / 8,
+ InitializationVectorLength = 96 / 8,
+ SymmetricSignatureLength = 128 / 8,
+ MinAsymmetricKeyLength = 384,
+ MaxAsymmetricKeyLength = 384,
+ SecureChannelNonceLength = 96,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha384,
+ CertificateKeyFamily = CertificateKeyFamily.ECC,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.NistP384,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha384,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.NistP384,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha384,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.Aes128Gcm,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.Aes128Gcm,
+ SecureChannelEnhancements = true,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA384
+ };
+
+ ///
+ /// The ECC nistP384 is an optional high security policy that uses ChaCha20Poly1305 for symmetric encryption.
+ ///
+ public readonly static SecurityPolicyInfo ECC_nistP384_ChaChaPoly = new(SecurityPolicies.ECC_nistP384_ChaChaPoly)
+ {
+ DerivedSignatureKeyLength = 0,
+ SymmetricEncryptionKeyLength = 256 / 8,
+ InitializationVectorLength = 96 / 8,
+ SymmetricSignatureLength = 128 / 8,
+ MinAsymmetricKeyLength = 384,
+ MaxAsymmetricKeyLength = 384,
+ SecureChannelNonceLength = 96,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha384,
+ CertificateKeyFamily = CertificateKeyFamily.ECC,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.NistP384,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha384,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.NistP384,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha384,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.ChaCha20Poly1305,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.ChaCha20Poly1305,
+ SecureChannelEnhancements = true,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA384
+ };
+
+ ///
+ /// The ECC brainpoolP256r1 is a required minimum security policy.
+ ///
+ public readonly static SecurityPolicyInfo ECC_brainpoolP256r1 = new(SecurityPolicies.ECC_brainpoolP256r1)
+ {
+ DerivedSignatureKeyLength = 256 / 8,
+ SymmetricEncryptionKeyLength = 128 / 8,
+ InitializationVectorLength = 128 / 8,
+ SymmetricSignatureLength = 256 / 8,
+ MinAsymmetricKeyLength = 256,
+ MaxAsymmetricKeyLength = 256,
+ SecureChannelNonceLength = 64,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha256,
+ CertificateKeyFamily = CertificateKeyFamily.ECC,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.NistP256,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha256,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.BrainpoolP256r1,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha256,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.Aes128Cbc,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.HmacSha256,
+ SecureChannelEnhancements = false,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA256
+ };
+
+ ///
+ /// The ECC_brainpoolP256r1_AesGcm is an ECC brainpoolP256 variant that uses AES-GCM for symmetric encryption.
+ ///
+ public readonly static SecurityPolicyInfo ECC_brainpoolP256r1_AesGcm = new (SecurityPolicies.ECC_brainpoolP256r1_AesGcm)
+ {
+ DerivedSignatureKeyLength = 0,
+ SymmetricEncryptionKeyLength = 128 / 8,
+ InitializationVectorLength = 96 / 8,
+ SymmetricSignatureLength = 128 / 8,
+ MinAsymmetricKeyLength = 256,
+ MaxAsymmetricKeyLength = 256,
+ SecureChannelNonceLength = 64,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha256,
+ CertificateKeyFamily = CertificateKeyFamily.ECC,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.BrainpoolP256r1,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha256,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.BrainpoolP256r1,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha256,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.Aes128Gcm,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.Aes128Gcm,
+ SecureChannelEnhancements = true,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA256
+ };
+
+ ///
+ /// The ECC_brainpoolP256_AES is an ECC brainpoolP256 variant that uses ChaCha20Poly1305 for symmetric encryption.
+ ///
+ public readonly static SecurityPolicyInfo ECC_brainpoolP256r1_ChaChaPoly = new(SecurityPolicies.ECC_brainpoolP256r1_ChaChaPoly)
+ {
+ DerivedSignatureKeyLength = 0,
+ SymmetricEncryptionKeyLength = 256 / 8,
+ InitializationVectorLength = 96 / 8,
+ SymmetricSignatureLength = 128 / 8,
+ MinAsymmetricKeyLength = 256,
+ MaxAsymmetricKeyLength = 256,
+ SecureChannelNonceLength = 64,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha256,
+ CertificateKeyFamily = CertificateKeyFamily.ECC,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.BrainpoolP256r1,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha256,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.BrainpoolP256r1,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha256,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.ChaCha20Poly1305,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.ChaCha20Poly1305,
+ SecureChannelEnhancements = true,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA256
+ };
+
+ ///
+ /// The ECC brainpoolP384r1 is an optional high security policy.
+ ///
+ public readonly static SecurityPolicyInfo ECC_brainpoolP384r1 = new(SecurityPolicies.ECC_brainpoolP384r1)
+ {
+ DerivedSignatureKeyLength = 384 / 8,
+ SymmetricEncryptionKeyLength = 256 / 8,
+ InitializationVectorLength = 128 / 8,
+ SymmetricSignatureLength = 384 / 8,
+ MinAsymmetricKeyLength = 384,
+ MaxAsymmetricKeyLength = 384,
+ SecureChannelNonceLength = 96,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha384,
+ CertificateKeyFamily = CertificateKeyFamily.ECC,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.BrainpoolP384r1,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha384,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.BrainpoolP384r1,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha384,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.Aes256Cbc,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.HmacSha384,
+ SecureChannelEnhancements = false,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA384
+ };
+
+ ///
+ /// The ECC brainpoolP384r1 is an optional high security policy that uses AES-GCM for symmetric encryption.
+ ///
+ public readonly static SecurityPolicyInfo ECC_brainpoolP384r1_AesGcm = new(SecurityPolicies.ECC_brainpoolP384r1_AesGcm)
+ {
+ DerivedSignatureKeyLength = 0,
+ SymmetricEncryptionKeyLength = 256 / 8,
+ InitializationVectorLength = 96 / 8,
+ SymmetricSignatureLength = 128 / 8,
+ MinAsymmetricKeyLength = 384,
+ MaxAsymmetricKeyLength = 384,
+ SecureChannelNonceLength = 96,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha384,
+ CertificateKeyFamily = CertificateKeyFamily.ECC,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.BrainpoolP384r1,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha384,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.BrainpoolP384r1,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha384,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.Aes256Gcm,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.Aes256Gcm,
+ SecureChannelEnhancements = true,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA384
+ };
+
+ ///
+ /// The ECC brainpoolP384r1 is an optional high security policy that uses ChaCha20Poly1305 for symmetric encryption.
+ ///
+ public readonly static SecurityPolicyInfo ECC_brainpoolP384r1_ChaChaPoly = new(SecurityPolicies.ECC_brainpoolP384r1_ChaChaPoly)
+ {
+ DerivedSignatureKeyLength = 0,
+ SymmetricEncryptionKeyLength = 256 / 8,
+ InitializationVectorLength = 96 / 8,
+ SymmetricSignatureLength = 128 / 8,
+ MinAsymmetricKeyLength = 384,
+ MaxAsymmetricKeyLength = 384,
+ SecureChannelNonceLength = 96,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha384,
+ CertificateKeyFamily = CertificateKeyFamily.ECC,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.BrainpoolP384r1,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.EcdsaSha384,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.BrainpoolP384r1,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha384,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.ChaCha20Poly1305,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.ChaCha20Poly1305,
+ SecureChannelEnhancements = true,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA384
+ };
+
+ ///
+ /// The RSA_DH_AES_GCM is an high security policy that uses AES GCM for symmetric encryption.
+ ///
+ public readonly static SecurityPolicyInfo RSA_DH_AesGcm = new(SecurityPolicies.RSA_DH_AesGcm)
+ {
+ DerivedSignatureKeyLength = 0,
+ SymmetricEncryptionKeyLength = 128 / 8,
+ InitializationVectorLength = 96 / 8,
+ SymmetricSignatureLength = 128 / 8,
+ MinAsymmetricKeyLength = 2048,
+ MaxAsymmetricKeyLength = 4096,
+ SecureChannelNonceLength = 384,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.RsaPkcs15Sha256,
+ CertificateKeyFamily = CertificateKeyFamily.RSA,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.RSA,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.RsaPkcs15Sha256,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.RSADH,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha256,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.Aes128Gcm,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.Aes128Gcm,
+ SecureChannelEnhancements = true,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA256
+ };
+
+ ///
+ /// The RSA_DH_ChaChaPoly is an high security policy that uses ChaCha20Poly1305 for symmetric encryption.
+ ///
+ public readonly static SecurityPolicyInfo RSA_DH_ChaChaPoly = new(SecurityPolicies.RSA_DH_ChaChaPoly)
+ {
+ DerivedSignatureKeyLength = 0,
+ SymmetricEncryptionKeyLength = 256 / 8,
+ InitializationVectorLength = 96 / 8,
+ SymmetricSignatureLength = 128 / 8,
+ MinAsymmetricKeyLength = 2048,
+ MaxAsymmetricKeyLength = 4096,
+ SecureChannelNonceLength = 384,
+ LegacySequenceNumbers = false,
+ AsymmetricEncryptionAlgorithm = AsymmetricEncryptionAlgorithm.None,
+ AsymmetricSignatureAlgorithm = AsymmetricSignatureAlgorithm.RsaPkcs15Sha256,
+ CertificateKeyFamily = CertificateKeyFamily.RSA,
+ CertificateKeyAlgorithm = CertificateKeyAlgorithm.RSA,
+ CertificateSignatureAlgorithm = AsymmetricSignatureAlgorithm.RsaPkcs15Sha256,
+ EphemeralKeyAlgorithm = CertificateKeyAlgorithm.RSADH,
+ KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDFSha256,
+ SymmetricEncryptionAlgorithm = SymmetricEncryptionAlgorithm.ChaCha20Poly1305,
+ SymmetricSignatureAlgorithm = SymmetricSignatureAlgorithm.ChaCha20Poly1305,
+ SecureChannelEnhancements = true,
+ IsDeprecated = false,
+ CertificateThumbprintAlgorithm = CertificateThumbprintAlgorithm.SHA256
+ };
+ }
+
+ ///
+ /// The algorithm family used to generate key pairs.
+ ///
+ public enum CertificateKeyFamily
+ {
+ ///
+ /// Does not apply.
+ ///
+ None,
+
+ ///
+ /// The RSA algorithm.
+ ///
+ RSA,
+
+ ///
+ /// Ellipic curve algorithms.
+ ///
+ ECC
+ }
+
+ ///
+ /// The algorithm used to generate key pairs.
+ ///
+ public enum CertificateKeyAlgorithm
+ {
+ ///
+ /// Does not apply.
+ ///
+ None,
+
+ ///
+ /// The RSA algorithm.
+ ///
+ RSA,
+
+ ///
+ /// The Diffie-Hellman algorith with RSA public keys.
+ ///
+ RSADH,
+
+ ///
+ /// The NIST P-256 ellipic curve algorithm.
+ ///
+ NistP256,
+
+ ///
+ /// The NIST P-384 ellipic curve algorithm.
+ ///
+ NistP384,
+
+ ///
+ /// The non-twisted Brainpool P-256 ellipic curve algorithm.
+ ///
+ BrainpoolP256r1,
+
+ ///
+ /// The non-twisted Brainpool P-384 ellipic curve algorithm.
+ ///
+ BrainpoolP384r1,
+
+ ///
+ /// The Edward Curve25519 ellipic curve algorithm.
+ ///
+ Curve25519,
+
+ ///
+ /// The Edward Curve25519 ellipic curve algorithm.
+ ///
+ Curve448
+ }
+
+ ///
+ /// The symmetric key derivation algorithm used to create shared keys.
+ ///
+ public enum KeyDerivationAlgorithm
+ {
+ ///
+ /// Does not apply.
+ ///
+ None,
+
+ ///
+ /// The P_SHA pseudo-random function with SHA1. This algorithm is considered insecure.
+ ///
+ PSha1,
+
+ ///
+ /// The P_SHA pseudo-random function with SHA256.
+ ///
+ PSha256,
+
+ ///
+ /// The HKDF pseudo-random function with SHA256.
+ ///
+ HKDFSha256,
+
+ ///
+ /// The HKDF pseudo-random function with SHA384.
+ ///
+ HKDFSha384
+ }
+
+ ///
+ /// The asymmetric encryption algorithm used to encrypt messages.
+ ///
+ public enum AsymmetricEncryptionAlgorithm
+ {
+ ///
+ /// Does not apply.
+ ///
+ None,
+
+ ///
+ /// RSA PKCS #1 v1.5. This algorithm is considered insecure.
+ ///
+ RsaPkcs15Sha1,
+
+ ///
+ /// RSA with OAEP padding with SHA1. This algorithm is considered insecure.
+ ///
+ RsaOaepSha1,
+
+ ///
+ /// RSA with OAEP padding with SHA256 .
+ ///
+ RsaOaepSha256
+ }
+
+ ///
+ /// The asymmetric signature algorithm used to sign messages.
+ ///
+ public enum AsymmetricSignatureAlgorithm
+ {
+ ///
+ /// Does not apply.
+ ///
+ None,
+
+ ///
+ /// RSA PKCS #1 v1.5 with SHA1. This algorithm is considered insecure.
+ ///
+ RsaPkcs15Sha1,
+
+ ///
+ /// RSA PKCS #1 v1.5 with SHA256.
+ ///
+ RsaPkcs15Sha256,
+
+ ///
+ /// RSA PSS with SHA256.
+ ///
+ RsaPssSha256,
+
+ ///
+ /// ECDSA with SHA256.
+ ///
+ EcdsaSha256,
+
+ ///
+ /// ECDSA with SHA384.
+ ///
+ EcdsaSha384,
+
+ ///
+ /// ECDSA with Curve 25519.
+ ///
+ EcdsaPure25519,
+
+ ///
+ /// ECDSA with Curve 448.
+ ///
+ EcdsaPure448
+ }
+
+ ///
+ /// The symmetric signature algorithm used to sign messages.
+ ///
+ public enum SymmetricSignatureAlgorithm
+ {
+ ///
+ /// Does not apply.
+ ///
+ None,
+
+ ///
+ /// HMAC with SHA1
+ ///
+ HmacSha1,
+
+ ///
+ /// HMAC with SHA256
+ ///
+ HmacSha256,
+
+ ///
+ /// HMAC with SHA384
+ ///
+ HmacSha384,
+
+ ///
+ /// ChaCha20Poly1305
+ ///
+ ChaCha20Poly1305,
+
+ ///
+ /// AES GCM with 128 bit key
+ ///
+ Aes128Gcm,
+
+ ///
+ /// AES GCM with 256 bit key
+ ///
+ Aes256Gcm
+ }
+
+ ///
+ /// The symmetric ecryption algorithm used to encrypt messages.
+ ///
+ public enum SymmetricEncryptionAlgorithm
+ {
+ ///
+ /// Does not apply.
+ ///
+ None,
+
+ ///
+ /// AES 128 bit in CBC mode
+ ///
+ Aes128Cbc,
+
+ ///
+ /// AES 256 bit in CBC mode
+ ///
+ Aes256Cbc,
+
+ ///
+ /// AES 128 bit in counter mode
+ ///
+ Aes128Ctr,
+
+ ///
+ /// AES 256 bit in counter mode
+ ///
+ Aes256Ctr,
+
+ ///
+ /// ChaCha20Poly1305
+ ///
+ ChaCha20Poly1305,
+
+ ///
+ /// AES 128 in GCM mode
+ ///
+ Aes128Gcm,
+
+ ///
+ /// AES 256 in GCM mode
+ ///
+ Aes256Gcm
+ }
+
+ ///
+ /// The algorithm used to generate certificate thumbprints.
+ ///
+ public enum CertificateThumbprintAlgorithm
+ {
+ ///
+ /// The SHA1 algorithm. This algorithm is considered insecure.
+ ///
+ SHA1,
+
+ ///
+ /// The SHA256 algorithm.
+ ///
+ SHA256,
+
+ ///
+ /// The SHA384 algorithm.
+ ///
+ SHA384,
+
+ ///
+ /// The SHA512 algorithm.
+ ///
+ SHA512
+ }
+}
diff --git a/Stack/Opc.Ua.Core/Stack/Client/ClientChannelManager.cs b/Stack/Opc.Ua.Core/Stack/Client/ClientChannelManager.cs
index 493493f194..e7b1323b8b 100644
--- a/Stack/Opc.Ua.Core/Stack/Client/ClientChannelManager.cs
+++ b/Stack/Opc.Ua.Core/Stack/Client/ClientChannelManager.cs
@@ -380,6 +380,15 @@ private sealed class ClientChannel : ITransportChannel
///
public IServiceMessageContext MessageContext => m_channel.MessageContext;
+ ///
+ public byte[] ChannelThumbprint => m_channel?.ChannelThumbprint ?? [];
+
+ ///
+ public byte[] ClientChannelCertificate => m_channel?.ClientChannelCertificate ?? [];
+
+ ///
+ public byte[] ServerChannelCertificate => m_channel?.ServerChannelCertificate ?? [];
+
///
public int OperationTimeout
{
diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/EndpointDescription.cs b/Stack/Opc.Ua.Core/Stack/Configuration/EndpointDescription.cs
index 7af466800e..df561dfd8b 100644
--- a/Stack/Opc.Ua.Core/Stack/Configuration/EndpointDescription.cs
+++ b/Stack/Opc.Ua.Core/Stack/Configuration/EndpointDescription.cs
@@ -80,12 +80,12 @@ public UserTokenPolicy FindUserTokenPolicy(string policyId, string tokenSecurity
else if ((
policy.SecurityPolicyUri != null &&
tokenSecurityPolicyUri != null &&
- EccUtils.IsEccPolicy(policy.SecurityPolicyUri) &&
- EccUtils.IsEccPolicy(tokenSecurityPolicyUri)
+ CryptoUtils.IsEccPolicy(policy.SecurityPolicyUri) &&
+ CryptoUtils.IsEccPolicy(tokenSecurityPolicyUri)
) ||
(
- !EccUtils.IsEccPolicy(policy.SecurityPolicyUri) &&
- !EccUtils.IsEccPolicy(tokenSecurityPolicyUri)))
+ !CryptoUtils.IsEccPolicy(policy.SecurityPolicyUri) &&
+ !CryptoUtils.IsEccPolicy(tokenSecurityPolicyUri)))
{
sameEncryptionAlgorithm ??= policy;
}
@@ -153,12 +153,12 @@ public UserTokenPolicy FindUserTokenPolicy(
else if ((
policy.SecurityPolicyUri != null &&
tokenSecurityPolicyUri != null &&
- EccUtils.IsEccPolicy(policy.SecurityPolicyUri) &&
- EccUtils.IsEccPolicy(tokenSecurityPolicyUri)
+ CryptoUtils.IsEccPolicy(policy.SecurityPolicyUri) &&
+ CryptoUtils.IsEccPolicy(tokenSecurityPolicyUri)
) ||
(
- !EccUtils.IsEccPolicy(policy.SecurityPolicyUri) &&
- !EccUtils.IsEccPolicy(tokenSecurityPolicyUri)))
+ !CryptoUtils.IsEccPolicy(policy.SecurityPolicyUri) &&
+ !CryptoUtils.IsEccPolicy(tokenSecurityPolicyUri)))
{
sameEncryptionAlgorithm ??= policy;
}
diff --git a/Stack/Opc.Ua.Core/Stack/Https/HttpsTransportChannel.cs b/Stack/Opc.Ua.Core/Stack/Https/HttpsTransportChannel.cs
index 973a4094a9..2782ca2ced 100644
--- a/Stack/Opc.Ua.Core/Stack/Https/HttpsTransportChannel.cs
+++ b/Stack/Opc.Ua.Core/Stack/Https/HttpsTransportChannel.cs
@@ -134,17 +134,22 @@ public IServiceMessageContext MessageContext
=> m_quotas?.MessageContext ?? throw BadNotConnected();
///
- public ChannelToken? CurrentToken => null;
+ public ChannelToken CurrentToken => new();
+
+ ///
+ public byte[] ChannelThumbprint => [];
+
+ ///
+ public byte[] ClientChannelCertificate { get; private set; } = [];
+
+ ///
+ public byte[] ServerChannelCertificate { get; private set; } = [];
///
public event ChannelTokenActivatedEventHandler OnTokenActivated
{
- add
- {
- }
- remove
- {
- }
+ add {}
+ remove {}
}
///
@@ -410,6 +415,7 @@ private void CreateHttpClient()
}
#endif
handler.ClientCertificates.Add(clientCertificate);
+ ClientChannelCertificate = clientCertificate.RawData;
}
Func<
@@ -456,7 +462,7 @@ private void CreateHttpClient()
}
m_quotas.CertificateValidator?.ValidateAsync(validationChain, default).GetAwaiter().GetResult();
-
+ ServerChannelCertificate = cert.RawData;
return true;
}
catch (Exception ex)
@@ -484,6 +490,7 @@ private void CreateHttpClient()
#pragma warning disable CA5400 // HttpClient is created without enabling CheckCertificateRevocationList
m_client = new HttpClient(handler);
#pragma warning restore CA5400 // HttpClient is created without enabling CheckCertificateRevocationList
+
}
catch (Exception ex)
{
diff --git a/Stack/Opc.Ua.Core/Stack/Server/SecureChannelContext.cs b/Stack/Opc.Ua.Core/Stack/Server/SecureChannelContext.cs
index c5f34f3446..e393444af1 100644
--- a/Stack/Opc.Ua.Core/Stack/Server/SecureChannelContext.cs
+++ b/Stack/Opc.Ua.Core/Stack/Server/SecureChannelContext.cs
@@ -43,14 +43,23 @@ public class SecureChannelContext
/// The secure channel identifier.
/// The endpoint description.
/// The message encoding.
+ /// The unique hash for the secure channel calculated during channel creation.
+ /// The client certificate used to establish the secure channel.
+ /// The server certificate used to establish the secure channel.
public SecureChannelContext(
string secureChannelId,
EndpointDescription endpointDescription,
- RequestEncoding messageEncoding)
+ RequestEncoding messageEncoding,
+ byte[] clientChannelCertificate,
+ byte[] serverChannelCertificate,
+ byte[] channelThumbprint = null)
{
SecureChannelId = secureChannelId;
EndpointDescription = endpointDescription;
MessageEncoding = messageEncoding;
+ ClientChannelCertificate = clientChannelCertificate;
+ ServerChannelCertificate = serverChannelCertificate;
+ ChannelThumbprint = channelThumbprint;
}
///
@@ -71,6 +80,21 @@ public SecureChannelContext(
/// The message encoding.
public RequestEncoding MessageEncoding { get; }
+ ///
+ /// The unique hash for the secure channel calculated during channel creation.
+ ///
+ public byte[] ChannelThumbprint { get; }
+
+ ///
+ /// The client certificate used to establsih the secure channel.
+ ///
+ public byte[] ClientChannelCertificate { get; }
+
+ ///
+ /// The server certificate used to establsih the secure channel.
+ ///
+ public byte[] ServerChannelCertificate { get; }
+
///
/// The active secure channel for the thread.
///
diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/ChannelToken.cs b/Stack/Opc.Ua.Core/Stack/Tcp/ChannelToken.cs
index 010a87c61b..c1af365da7 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/ChannelToken.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/ChannelToken.cs
@@ -37,6 +37,8 @@ namespace Opc.Ua.Bindings
///
public sealed class ChannelToken : IDisposable
{
+ private bool m_disposed;
+
///
/// Creates an object with default values.
///
@@ -51,17 +53,18 @@ private void Dispose(bool disposing)
{
if (!m_disposed)
{
- if (disposing)
+ if (ServerHmac != null)
+ {
+ ServerHmac.Dispose();
+ ServerHmac = null;
+ }
+
+ if (ClientHmac != null)
{
- Utils.SilentDispose(ClientHmac);
- Utils.SilentDispose(ServerHmac);
- Utils.SilentDispose(ClientEncryptor);
- Utils.SilentDispose(ServerEncryptor);
+ ClientHmac.Dispose();
+ ClientHmac = null;
}
- ClientHmac = null;
- ServerHmac = null;
- ClientEncryptor = null;
- ServerEncryptor = null;
+
m_disposed = true;
}
}
@@ -123,6 +126,21 @@ public void Dispose()
(HiResClock.TickCount - CreatedAtTickCount) >
(int)Math.Round(Lifetime * TcpMessageLimits.TokenActivationPeriod);
+ ///
+ /// The SecurityPolicy used to encrypt and sign the messages.
+ ///
+ public SecurityPolicyInfo SecurityPolicy { get; set; }
+
+ ///
+ /// The secret used to compute the keys.
+ ///
+ internal byte[] Secret { get; set; }
+
+ ///
+ /// The previous server nonce used to compute the keys.
+ ///
+ internal byte[] PreviousSecret { get; set; }
+
///
/// The nonce provided by the client.
///
@@ -136,53 +154,41 @@ public void Dispose()
///
/// The key used to sign messages sent by the client.
///
- public byte[] ClientSigningKey { get; set; }
+ internal byte[] ClientSigningKey { get; set; }
///
/// The key used to encrypt messages sent by the client.
///
- public byte[] ClientEncryptingKey { get; set; }
+ internal byte[] ClientEncryptingKey { get; set; }
///
/// The initialization vector by the client when encrypting a message.
///
- public byte[] ClientInitializationVector { get; set; }
+ internal byte[] ClientInitializationVector { get; set; }
///
/// The key used to sign messages sent by the server.
///
- public byte[] ServerSigningKey { get; set; }
+ internal byte[] ServerSigningKey { get; set; }
///
/// The key used to encrypt messages sent by the server.
///
- public byte[] ServerEncryptingKey { get; set; }
+ internal byte[] ServerEncryptingKey { get; set; }
///
/// The initialization vector by the server when encrypting a message.
///
- public byte[] ServerInitializationVector { get; set; }
-
- ///
- /// The SymmetricAlgorithm object used by the client to encrypt messages.
- ///
- public SymmetricAlgorithm ClientEncryptor { get; set; }
+ internal byte[] ServerInitializationVector { get; set; }
///
- /// The SymmetricAlgorithm object used by the server to encrypt messages.
+ /// A pre-allocated HMAC used to improve performance for SecurityPolicies that need it.
///
- public SymmetricAlgorithm ServerEncryptor { get; set; }
+ internal HMAC ServerHmac { get; set; }
///
- /// The HMAC object used by the client to sign messages.
+ /// A pre-allocated HMAC used to improve performance for SecurityPolicies that need it.
///
- public HMAC ClientHmac { get; set; }
-
- ///
- /// The HMAC object used by the server to sign messages.
- ///
- public HMAC ServerHmac { get; set; }
-
- private bool m_disposed;
+ internal HMAC ClientHmac { get; set; }
}
}
diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpListenerChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpListenerChannel.cs
index c8569f3e0a..089c9a07c4 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpListenerChannel.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpListenerChannel.cs
@@ -509,71 +509,6 @@ protected virtual void CompleteReverseHello(Exception e)
// intentionally left empty
}
- ///
- /// Sends a fault response secured with the asymmetric keys.
- ///
- protected void SendServiceFault(uint requestId, ServiceResult fault)
- {
- m_logger.LogDebug(
- "ChannelId {Id}: Request {RequestId}: SendServiceFault={ServiceFault}",
- ChannelId,
- requestId,
- fault.StatusCode);
-
- BufferCollection chunksToSend = null;
-
- try
- {
- // construct fault.
- var response = new ServiceFault();
-
- response.ResponseHeader.ServiceResult = fault.Code;
-
- var stringTable = new StringTable();
-
- response.ResponseHeader.ServiceDiagnostics = new DiagnosticInfo(
- fault,
- DiagnosticsMasks.NoInnerStatus,
- true,
- stringTable,
- m_logger);
-
- response.ResponseHeader.StringTable = stringTable.ToArray();
-
- // serialize fault.
- byte[] buffer = BinaryEncoder.EncodeMessage(response, Quotas.MessageContext);
-
- // secure message.
- chunksToSend = WriteAsymmetricMessage(
- TcpMessageType.Open,
- requestId,
- ServerCertificate,
- ClientCertificate,
- new ArraySegment(buffer, 0, buffer.Length));
-
- // write the message to the server.
- BeginWriteMessage(chunksToSend, null);
- chunksToSend = null;
- }
- catch (Exception e)
- {
- chunksToSend?.Release(BufferManager, "SendServiceFault");
-
- m_logger.LogError(
- e,
- "ChannelId {Id}: Request {RequestId}: SendServiceFault={ServiceFault}: Unexpected error.",
- ChannelId,
- requestId,
- fault.StatusCode);
-
- ForceChannelFault(
- ServiceResult.Create(
- e,
- StatusCodes.BadTcpInternalError,
- "Unexpected error sending a service fault."));
- }
- }
-
///
/// Handles a reconnect request.
///
diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs
index 69b0705e8c..5a91a69891 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpMessageType.cs
@@ -27,6 +27,11 @@
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
+using System;
+using System.Globalization;
+using System.Text;
+using Microsoft.Extensions.Logging;
+
namespace Opc.Ua.Bindings
{
///
@@ -158,6 +163,23 @@ public static bool IsValid(uint messageType)
}
}
+
+ internal static string GetTypeAndSize(ArraySegment chunk)
+ {
+ StringBuilder sb = new StringBuilder();
+
+ for (int ii = 0; ii < 1; ii++)
+ {
+ uint size = BitConverter.ToUInt32(chunk.Array ?? [], 4);
+ sb.Append(Encoding.ASCII.GetString(chunk.Array ?? [], 0, 4));
+ sb.Append("=>");
+ sb.Append(BitConverter.ToUInt32(chunk.Array ?? [], 4));
+ sb.Append((size != chunk.Count) ? " X " : " O ");
+ sb.Append(chunk.Count);
+ }
+
+ return sb.ToString();
+ }
}
///
diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs
index 811a38f009..df83e166d2 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpServerChannel.cs
@@ -30,6 +30,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
@@ -273,7 +274,7 @@ public override void Reconnect(
State = TcpChannelState.Open;
// send response.
- SendOpenSecureChannelResponse(requestId, token, request);
+ SendOpenSecureChannelResponse(requestId, token, request, true);
// send any queued responses.
ResetQueuedResponses(OnChannelReconnected);
@@ -566,13 +567,28 @@ private bool ProcessOpenSecureChannelRequest(
try
{
+ m_oscRequestSignature = null;
+ byte[] signature;
+
messageBody = ReadAsymmetricMessage(
messageChunk,
ServerCertificate,
out channelId,
out clientCertificate,
out requestId,
- out sequenceNumber);
+ out sequenceNumber,
+ m_oscRequestSignature,
+ out signature);
+
+ // don't keep signature if secure channel enhancements are not used.
+ m_oscRequestSignature = (SecurityPolicy.SecureChannelEnhancements) ? signature : null;
+
+ CryptoTrace.Start(ConsoleColor.Magenta, $"ProcessOpenSecureChannelRequest ({(State != TcpChannelState.Opening ? "RENEW" : "OPEN")})");
+ CryptoTrace.WriteLine($"ClientCertificate={clientCertificate?.Thumbprint}");
+ CryptoTrace.WriteLine($"ServerCertificate={ServerCertificate?.Thumbprint}");
+ CryptoTrace.WriteLine($"RequestSignature={CryptoTrace.KeyToString(signature)}");
+ CryptoTrace.WriteLine($"ChannelThumbprint={CryptoTrace.KeyToString(ChannelThumbprint)}");
+ CryptoTrace.Finish("ProcessOpenSecureChannelRequest");
// check for replay attacks.
if (!VerifySequenceNumber(sequenceNumber, "ProcessOpenSecureChannelRequest"))
@@ -680,6 +696,14 @@ const string errorSecurityChecksFailed
token = CreateToken();
token.TokenId = GetNewTokenId();
token.ServerNonce = CreateNonce(ServerCertificate);
+
+ CryptoTrace.Start(ConsoleColor.Red, $"PreviousSecret");
+ CryptoTrace.WriteLine($"PreviousSecret={CryptoTrace.KeyToString(token.PreviousSecret)}");
+ CryptoTrace.WriteLine($"CurrentSecret={CryptoTrace.KeyToString(CurrentToken?.Secret)}");
+ CryptoTrace.Finish($"PreviousSecret");
+
+ token.PreviousSecret = CurrentToken?.Secret;
+
// check the client nonce.
token.ClientNonce = request.ClientNonce;
if (!ValidateNonce(ClientCertificate, token.ClientNonce))
@@ -791,11 +815,11 @@ const string errorSecurityChecksFailed
// send the response.
if (requestType == SecurityTokenRequestType.Renew)
{
- SendOpenSecureChannelResponse(requestId, RenewedToken, request);
+ SendOpenSecureChannelResponse(requestId, RenewedToken, request, true);
}
else
{
- SendOpenSecureChannelResponse(requestId, CurrentToken, request);
+ SendOpenSecureChannelResponse(requestId, CurrentToken, request, false);
}
// notify reverse
@@ -821,11 +845,14 @@ const string errorSecurityChecksFailed
// report the audit event for open secure channel
ReportAuditOpenSecureChannelEvent?.Invoke(this, request, ClientCertificate, e);
- SendServiceFault(
- requestId, ServiceResult.Create(
+ SendServiceFault(
+ requestId,
+ State == TcpChannelState.Open,
+ ServiceResult.Create(
e,
StatusCodes.BadTcpInternalError,
"Unexpected error processing OpenSecureChannel request."));
+
CompleteReverseHello(e);
return false;
}
@@ -861,13 +888,92 @@ protected override void CompleteReverseHello(Exception e)
}
}
+ ///
+ /// Sends a fault response secured with the asymmetric keys.
+ ///
+ protected void SendServiceFault(uint requestId, bool renew, ServiceResult fault)
+ {
+ m_logger.LogDebug(
+ "ChannelId {Id}: Request {RequestId}: SendServiceFault={ServiceFault}",
+ ChannelId,
+ requestId,
+ fault.StatusCode);
+
+ BufferCollection chunksToSend = null;
+
+ try
+ {
+ // construct fault.
+ var response = new ServiceFault();
+
+ response.ResponseHeader.ServiceResult = fault.Code;
+
+ var stringTable = new StringTable();
+
+ response.ResponseHeader.ServiceDiagnostics = new DiagnosticInfo(
+ fault,
+ DiagnosticsMasks.NoInnerStatus,
+ true,
+ stringTable,
+ m_logger);
+
+ response.ResponseHeader.StringTable = stringTable.ToArray();
+
+ // serialize fault.
+ byte[] buffer = BinaryEncoder.EncodeMessage(response, Quotas.MessageContext);
+ byte[] signature = null;
+
+ CryptoTrace.WriteLine($"messageBody={CryptoTrace.KeyToString(buffer)}");
+
+ // secure message.
+ chunksToSend = WriteAsymmetricMessage(
+ TcpMessageType.Open,
+ requestId,
+ ServerCertificate,
+ null,
+ ClientCertificate,
+ new ArraySegment(buffer, 0, buffer.Length),
+ !renew ? m_oscRequestSignature : null,
+ out signature);
+
+ // write the message to the server.
+ BeginWriteMessage(chunksToSend, null);
+ chunksToSend = null;
+ }
+ catch (Exception e)
+ {
+ chunksToSend?.Release(BufferManager, "SendServiceFault");
+
+ m_logger.LogError(
+ e,
+ "ChannelId {Id}: Request {RequestId}: SendServiceFault={ServiceFault}: Unexpected error.",
+ ChannelId,
+ requestId,
+ fault.StatusCode);
+
+ ForceChannelFault(
+ ServiceResult.Create(
+ e,
+ StatusCodes.BadTcpInternalError,
+ "Unexpected error sending a service fault."));
+ }
+ }
+
///
/// Sends an OpenSecureChannel response.
///
+ /// The request identifier.
+ /// The security token to return in the response.
+ /// The OpenSecureChannel request being answered.
+ ///
+ /// true when answering a token renewal request. Renewal keeps the same channel, but issues a new
+ /// security token (new token id and server nonce), false for the initial open.
+ ///
private void SendOpenSecureChannelResponse(
uint requestId,
ChannelToken token,
- OpenSecureChannelRequest request)
+ OpenSecureChannelRequest request,
+ bool renew)
{
m_logger.LogDebug("ChannelId {Id}: SendOpenSecureChannelResponse()", ChannelId);
@@ -883,6 +989,7 @@ private void SendOpenSecureChannelResponse(
response.ServerNonce = token.ServerNonce;
byte[] buffer = BinaryEncoder.EncodeMessage(response, Quotas.MessageContext);
+ byte[] signature;
BufferCollection chunksToSend = WriteAsymmetricMessage(
TcpMessageType.Open,
@@ -890,9 +997,24 @@ private void SendOpenSecureChannelResponse(
ServerCertificate,
ServerCertificateChain,
ClientCertificate,
- new ArraySegment(buffer, 0, buffer.Length));
+ new ArraySegment(buffer, 0, buffer.Length),
+ !renew ? m_oscRequestSignature : null,
+ out signature);
+
+ if (!renew)
+ {
+ ChannelThumbprint = signature;
+ }
+
+ CryptoTrace.Start(ConsoleColor.Magenta, $"SendOpenSecureChannelResponse ({(renew ? "RENEW" : "OPEN")})");
+ CryptoTrace.WriteLine($"ServerCertificate={ServerCertificate?.Thumbprint}");
+ CryptoTrace.WriteLine($"ClientCertificate={ClientCertificate?.Thumbprint}");
+ CryptoTrace.WriteLine($"RequestSignature={CryptoTrace.KeyToString(m_oscRequestSignature)}");
+ CryptoTrace.WriteLine($"ResponseSignature={CryptoTrace.KeyToString(signature)}");
+ CryptoTrace.WriteLine($"ChannelThumbprint={CryptoTrace.KeyToString(ChannelThumbprint)}");
+ CryptoTrace.Finish("SendOpenSecureChannelResponse");
- // write the message to the server.
+ // write the response to the client.
try
{
BeginWriteMessage(chunksToSend, null);
@@ -1342,6 +1464,7 @@ private bool ValidateDiscoveryServiceCall(
private readonly ILogger m_logger;
private SortedDictionary m_queuedResponses;
private ReverseConnectAsyncResult m_pendingReverseHello;
+ private byte[] m_oscRequestSignature;
private static readonly string s_implementationString =
".NET Standard ServerChannel UA-TCP " + Utils.GetAssemblyBuildNumber();
diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs
index d25a6c2a22..9f43a754f7 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs
@@ -1028,7 +1028,10 @@ private async void OnRequestReceivedAsync(
var context = new SecureChannelContext(
channel.GlobalChannelId,
channel.EndpointDescription,
- RequestEncoding.Binary);
+ RequestEncoding.Binary,
+ channel.ClientCertificate?.RawData,
+ channel.ServerCertificate?.RawData,
+ channel.ChannelThumbprint);
IServiceResponse response = await m_callback.ProcessRequestAsync(
context,
diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs
index dabb2e5e70..e42f4e0024 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs
@@ -68,7 +68,7 @@ protected set
///
/// The certificate for the server.
///
- protected X509Certificate2 ServerCertificate { get; private set; }
+ internal X509Certificate2 ServerCertificate { get; private set; }
///
/// The server certificate chain.
@@ -83,7 +83,20 @@ protected set
///
/// The security policy used with the channel.
///
- protected string SecurityPolicyUri { get; private set; }
+ protected string SecurityPolicyUri
+ {
+ get => SecurityPolicy.Uri;
+
+ private set
+ {
+ SecurityPolicy = SecurityPolicies.GetInfo(value);
+ }
+ }
+
+ ///
+ /// The security policy used with the channel.
+ ///
+ protected SecurityPolicyInfo SecurityPolicy { get; private set; }
///
/// Whether the channel is restricted to discovery operations.
@@ -93,7 +106,7 @@ protected set
///
/// The certificate for the client.
///
- protected X509Certificate2 ClientCertificate { get; set; }
+ internal X509Certificate2 ClientCertificate { get; set; }
///
/// The client certificate chain.
@@ -187,33 +200,28 @@ protected static void CompareCertificates(
///
protected byte[] CreateNonce(X509Certificate2 certificate)
{
- switch (SecurityPolicyUri)
+ switch (SecurityPolicy.CertificateKeyFamily)
{
- case SecurityPolicies.Basic128Rsa15:
- case SecurityPolicies.Basic256:
- case SecurityPolicies.Basic256Sha256:
- case SecurityPolicies.Aes128_Sha256_RsaOaep:
- case SecurityPolicies.Aes256_Sha256_RsaPss:
- uint length = Nonce.GetNonceLength(SecurityPolicyUri);
-
- if (length > 0)
+ case CertificateKeyFamily.RSA:
+ if (SecurityPolicy.EphemeralKeyAlgorithm == CertificateKeyAlgorithm.RSADH)
{
- return Nonce.CreateRandomNonceData(length);
+ m_localNonce = Nonce.CreateNonce(SecurityPolicy);
+ return m_localNonce.Data;
}
- break;
- case SecurityPolicies.ECC_nistP256:
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP256r1:
- case SecurityPolicies.ECC_brainpoolP384r1:
- case SecurityPolicies.ECC_curve25519:
- case SecurityPolicies.ECC_curve448:
- m_localNonce = Nonce.CreateNonce(SecurityPolicyUri);
+ // Basic128Rsa15 is the only RSA based security policy that allows nonces
+ // with a length less than 32 bytes for compatibility reasons.
+ bool enforceMinimumLength = !SecurityPolicy.Uri.Equals(
+ SecurityPolicies.Basic128Rsa15,
+ StringComparison.Ordinal);
+ return Nonce.CreateRandomNonceData(
+ SecurityPolicy.SecureChannelNonceLength,
+ enforceMinimumLength);
+ case CertificateKeyFamily.ECC:
+ m_localNonce = Nonce.CreateNonce(SecurityPolicy);
return m_localNonce.Data;
default:
return null;
}
-
- return null;
}
///
@@ -228,18 +236,20 @@ protected bool ValidateNonce(X509Certificate2 certificate, byte[] nonce)
}
// check the length.
- if (nonce == null || nonce.Length != Nonce.GetNonceLength(SecurityPolicyUri))
+ if (nonce == null || nonce.Length != SecurityPolicy.SecureChannelNonceLength)
{
return false;
}
- switch (SecurityPolicyUri)
+ switch (SecurityPolicy.CertificateKeyFamily)
{
- case SecurityPolicies.Basic128Rsa15:
- case SecurityPolicies.Basic256:
- case SecurityPolicies.Basic256Sha256:
- case SecurityPolicies.Aes128_Sha256_RsaOaep:
- case SecurityPolicies.Aes256_Sha256_RsaPss:
+ case CertificateKeyFamily.RSA:
+ if (SecurityPolicy.EphemeralKeyAlgorithm == CertificateKeyAlgorithm.RSADH)
+ {
+ m_remoteNonce = Nonce.CreateNonce(SecurityPolicy, nonce);
+ return true;
+ }
+
// try to catch programming errors by rejecting nonces with all zeros.
for (int ii = 0; ii < nonce.Length; ii++)
{
@@ -248,19 +258,13 @@ protected bool ValidateNonce(X509Certificate2 certificate, byte[] nonce)
return true;
}
}
-
- return false;
- case SecurityPolicies.ECC_nistP256:
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP256r1:
- case SecurityPolicies.ECC_brainpoolP384r1:
- case SecurityPolicies.ECC_curve25519:
- case SecurityPolicies.ECC_curve448:
- m_remoteNonce = Nonce.CreateNonce(SecurityPolicyUri, nonce);
+ break;
+ case CertificateKeyFamily.ECC:
+ m_remoteNonce = Nonce.CreateNonce(SecurityPolicy, nonce);
return true;
- default:
- return false;
}
+
+ return false;
}
///
@@ -268,19 +272,23 @@ protected bool ValidateNonce(X509Certificate2 certificate, byte[] nonce)
///
protected int GetPlainTextBlockSize(X509Certificate2 receiverCertificate)
{
- switch (SecurityPolicyUri)
+ if (SecurityPolicy.AsymmetricSignatureAlgorithm == AsymmetricSignatureAlgorithm.None ||
+ SecurityPolicy.EphemeralKeyAlgorithm != CertificateKeyAlgorithm.None)
{
- case SecurityPolicies.Basic256:
- case SecurityPolicies.Basic256Sha256:
- case SecurityPolicies.Aes128_Sha256_RsaOaep:
+ return 1;
+ }
+
+ switch (SecurityPolicy.AsymmetricEncryptionAlgorithm)
+ {
+ case AsymmetricEncryptionAlgorithm.RsaOaepSha1:
return RsaUtils.GetPlainTextBlockSize(
receiverCertificate,
RsaUtils.Padding.OaepSHA1);
- case SecurityPolicies.Aes256_Sha256_RsaPss:
+ case AsymmetricEncryptionAlgorithm.RsaOaepSha256:
return RsaUtils.GetPlainTextBlockSize(
receiverCertificate,
RsaUtils.Padding.OaepSHA256);
- case SecurityPolicies.Basic128Rsa15:
+ case AsymmetricEncryptionAlgorithm.RsaPkcs15Sha1:
return RsaUtils.GetPlainTextBlockSize(
receiverCertificate,
RsaUtils.Padding.Pkcs1);
@@ -294,13 +302,17 @@ protected int GetPlainTextBlockSize(X509Certificate2 receiverCertificate)
///
protected int GetCipherTextBlockSize(X509Certificate2 receiverCertificate)
{
- switch (SecurityPolicyUri)
+ if (SecurityPolicy.AsymmetricSignatureAlgorithm == AsymmetricSignatureAlgorithm.None ||
+ SecurityPolicy.EphemeralKeyAlgorithm != CertificateKeyAlgorithm.None)
{
- case SecurityPolicies.Basic256:
- case SecurityPolicies.Basic256Sha256:
- case SecurityPolicies.Aes128_Sha256_RsaOaep:
- case SecurityPolicies.Aes256_Sha256_RsaPss:
- case SecurityPolicies.Basic128Rsa15:
+ return 1;
+ }
+
+ switch (SecurityPolicy.AsymmetricEncryptionAlgorithm)
+ {
+ case AsymmetricEncryptionAlgorithm.RsaOaepSha1:
+ case AsymmetricEncryptionAlgorithm.RsaOaepSha256:
+ case AsymmetricEncryptionAlgorithm.RsaPkcs15Sha1:
return RsaUtils.GetCipherTextBlockSize(receiverCertificate);
default:
return 1;
@@ -399,21 +411,17 @@ protected int GetAsymmetricHeaderSize(
///
protected int GetAsymmetricSignatureSize(X509Certificate2 senderCertificate)
{
- switch (SecurityPolicyUri)
+ switch (SecurityPolicy.AsymmetricSignatureAlgorithm)
{
- case SecurityPolicies.Basic128Rsa15:
- case SecurityPolicies.Basic256:
- case SecurityPolicies.Basic256Sha256:
- case SecurityPolicies.Aes128_Sha256_RsaOaep:
- case SecurityPolicies.Aes256_Sha256_RsaPss:
+ case AsymmetricSignatureAlgorithm.RsaPkcs15Sha1:
+ case AsymmetricSignatureAlgorithm.RsaPkcs15Sha256:
+ case AsymmetricSignatureAlgorithm.RsaPssSha256:
return RsaUtils.GetSignatureLength(senderCertificate);
- case SecurityPolicies.ECC_nistP256:
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP256r1:
- case SecurityPolicies.ECC_brainpoolP384r1:
- case SecurityPolicies.ECC_curve25519:
- case SecurityPolicies.ECC_curve448:
- return EccUtils.GetSignatureLength(senderCertificate);
+ case AsymmetricSignatureAlgorithm.EcdsaSha256:
+ case AsymmetricSignatureAlgorithm.EcdsaSha384:
+ case AsymmetricSignatureAlgorithm.EcdsaPure25519:
+ case AsymmetricSignatureAlgorithm.EcdsaPure448:
+ return CryptoUtils.GetSignatureLength(senderCertificate);
default:
return 0;
}
@@ -551,18 +559,29 @@ protected BufferCollection WriteAsymmetricMessage(
X509Certificate2 receiverCertificate,
ArraySegment messageBody)
{
+ byte[] unused = null;
return WriteAsymmetricMessage(
messageType,
requestId,
senderCertificate,
null,
receiverCertificate,
- messageBody);
+ messageBody,
+ null,
+ out unused);
}
///
/// Sends a OpenSecureChannel request.
///
+ /// The UA TCP message type (for example, Open or OpenFinal).
+ /// The request identifier used in the sequence header.
+ /// The certificate used to sign the asymmetric message.
+ /// The optional sender certificate chain to include in the message header.
+ /// The receiver certificate used for asymmetric encryption.
+ /// The encoded message body to send.
+ /// The signature from the OpenSecureChannel request.
+ /// Returns the signature generated for the message being written.
///
///
protected BufferCollection WriteAsymmetricMessage(
@@ -571,8 +590,12 @@ protected BufferCollection WriteAsymmetricMessage(
X509Certificate2 senderCertificate,
X509Certificate2Collection senderCertificateChain,
X509Certificate2 receiverCertificate,
- ArraySegment messageBody)
+ ArraySegment messageBody,
+ byte[] oscRequestSignature,
+ out byte[] signature)
{
+ signature = null;
+
bool success = false;
var chunksToSend = new BufferCollection();
@@ -663,7 +686,8 @@ protected BufferCollection WriteAsymmetricMessage(
if (SecurityMode != MessageSecurityMode.None)
{
- if (receiverCertificate.GetRSAPublicKey() != null)
+ if (SecurityPolicy.EphemeralKeyAlgorithm == CertificateKeyAlgorithm.None &&
+ receiverCertificate.GetRSAPublicKey() != null)
{
if (X509Utils.GetRSAPublicKeySize(receiverCertificate) <=
TcpMessageLimits.KeySizeExtraPadding)
@@ -721,10 +745,30 @@ protected BufferCollection WriteAsymmetricMessage(
// put the message size after encryption into the header.
UpdateMessageSize(buffer, 0, cipherTextSize + headerSize);
+ ArraySegment dataToSign;
+
+ if (oscRequestSignature != null && SecurityPolicy.SecureChannelEnhancements)
+ {
+ // copy osc request signature if provided before verifying.
+ dataToSign = new ArraySegment(
+ buffer,
+ 0,
+ encoder.Position + oscRequestSignature.Length);
+
+ Array.Copy(
+ oscRequestSignature,
+ 0,
+ buffer,
+ encoder.Position,
+ oscRequestSignature.Length);
+ }
+ else
+ {
+ dataToSign = new ArraySegment(buffer, 0, encoder.Position);
+ }
+
// write the signature.
- byte[] signature = Sign(
- new ArraySegment(buffer, 0, encoder.Position),
- senderCertificate);
+ signature = Sign(dataToSign, senderCertificate);
if (signature != null)
{
@@ -765,6 +809,7 @@ protected BufferCollection WriteAsymmetricMessage(
// ensure the buffers don't get clean up on exit.
success = true;
+
return chunksToSend;
}
catch (Exception ex)
@@ -988,8 +1033,12 @@ protected ArraySegment ReadAsymmetricMessage(
out uint channelId,
out X509Certificate2 senderCertificate,
out uint requestId,
- out uint sequenceNumber)
+ out uint sequenceNumber,
+ byte[] oscRequestSignature,
+ out byte[] signature)
{
+ signature = null;
+
int headerSize;
using (var decoder = new BinaryDecoder(buffer, Quotas.MessageContext))
{
@@ -1096,23 +1145,43 @@ protected ArraySegment ReadAsymmetricMessage(
// extract signature.
int signatureSize = GetAsymmetricSignatureSize(senderCertificate);
- byte[] signature = new byte[signatureSize];
+ signature = new byte[signatureSize];
for (int ii = 0; ii < signatureSize; ii++)
{
- signature[ii] = plainText.Array[
- plainText.Offset + plainText.Count - signatureSize + ii];
+ signature[ii] = plainText.Array[plainText.Offset + plainText.Count - signatureSize + ii];
}
- // verify the signature.
- var dataToVerify = new ArraySegment(
- plainText.Array,
- plainText.Offset,
- plainText.Count - signatureSize);
+ ArraySegment dataToVerify;
+
+ if (oscRequestSignature != null && SecurityPolicy.SecureChannelEnhancements)
+ {
+ // copy osc request signature if provided before verifying.
+ dataToVerify = new ArraySegment(
+ plainText.Array,
+ plainText.Offset,
+ plainText.Count - signatureSize + oscRequestSignature.Length);
+
+ Array.Copy(
+ oscRequestSignature,
+ dataToVerify.Offset,
+ dataToVerify.Array,
+ dataToVerify.Count - oscRequestSignature.Length,
+ oscRequestSignature.Length);
+ }
+ else
+ {
+ dataToVerify = new ArraySegment(
+ plainText.Array,
+ plainText.Offset,
+ plainText.Count - signatureSize);
+ }
+ // verify the signature.
if (!Verify(dataToVerify, signature, senderCertificate))
{
m_logger.LogWarning("Could not verify signature on message.");
+
throw ServiceResultException.Create(
StatusCodes.BadSecurityChecksFailed,
"Could not verify the signature on the message.");
@@ -1122,6 +1191,7 @@ protected ArraySegment ReadAsymmetricMessage(
int paddingCount = 0;
if (SecurityMode != MessageSecurityMode.None &&
+ SecurityPolicy.EphemeralKeyAlgorithm == CertificateKeyAlgorithm.None &&
receiverCertificate.GetRSAPublicKey() != null)
{
int paddingEnd;
@@ -1194,39 +1264,7 @@ protected ArraySegment ReadAsymmetricMessage(
///
protected byte[] Sign(ArraySegment dataToSign, X509Certificate2 senderCertificate)
{
- switch (SecurityPolicyUri)
- {
- case SecurityPolicies.Basic256:
- case SecurityPolicies.Basic128Rsa15:
- return Rsa_Sign(
- dataToSign,
- senderCertificate,
- HashAlgorithmName.SHA1,
- RSASignaturePadding.Pkcs1);
- case SecurityPolicies.Aes128_Sha256_RsaOaep:
- case SecurityPolicies.Basic256Sha256:
- return Rsa_Sign(
- dataToSign,
- senderCertificate,
- HashAlgorithmName.SHA256,
- RSASignaturePadding.Pkcs1);
- case SecurityPolicies.Aes256_Sha256_RsaPss:
- return Rsa_Sign(
- dataToSign,
- senderCertificate,
- HashAlgorithmName.SHA256,
- RSASignaturePadding.Pss);
- case SecurityPolicies.ECC_nistP256:
- case SecurityPolicies.ECC_brainpoolP256r1:
- case SecurityPolicies.ECC_curve25519:
- case SecurityPolicies.ECC_curve448:
- return EccUtils.Sign(dataToSign, senderCertificate, HashAlgorithmName.SHA256);
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP384r1:
- return EccUtils.Sign(dataToSign, senderCertificate, HashAlgorithmName.SHA384);
- default:
- return null;
- }
+ return CryptoUtils.Sign(dataToSign, senderCertificate, SecurityPolicyUri);
}
///
@@ -1242,53 +1280,11 @@ protected bool Verify(
byte[] signature,
X509Certificate2 senderCertificate)
{
- // verify signature.
- switch (SecurityPolicyUri)
- {
- case SecurityPolicies.None:
- return true;
- case SecurityPolicies.Basic128Rsa15:
- case SecurityPolicies.Basic256:
- return Rsa_Verify(
- dataToVerify,
- signature,
- senderCertificate,
- HashAlgorithmName.SHA1,
- RSASignaturePadding.Pkcs1);
- case SecurityPolicies.Aes128_Sha256_RsaOaep:
- case SecurityPolicies.Basic256Sha256:
- return Rsa_Verify(
- dataToVerify,
- signature,
- senderCertificate,
- HashAlgorithmName.SHA256,
- RSASignaturePadding.Pkcs1);
- case SecurityPolicies.Aes256_Sha256_RsaPss:
- return Rsa_Verify(
- dataToVerify,
- signature,
- senderCertificate,
- HashAlgorithmName.SHA256,
- RSASignaturePadding.Pss);
- case SecurityPolicies.ECC_nistP256:
- case SecurityPolicies.ECC_brainpoolP256r1:
- case SecurityPolicies.ECC_curve25519:
- case SecurityPolicies.ECC_curve448:
- return EccUtils.Verify(
- dataToVerify,
- signature,
- senderCertificate,
- HashAlgorithmName.SHA256);
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP384r1:
- return EccUtils.Verify(
- dataToVerify,
- signature,
- senderCertificate,
- HashAlgorithmName.SHA384);
- default:
- return false;
- }
+ return CryptoUtils.Verify(
+ dataToVerify,
+ signature,
+ senderCertificate,
+ SecurityPolicyUri);
}
///
@@ -1304,48 +1300,51 @@ protected ArraySegment Encrypt(
ArraySegment headerToCopy,
X509Certificate2 receiverCertificate)
{
- switch (SecurityPolicyUri)
+ if (SecurityPolicy.AsymmetricSignatureAlgorithm == AsymmetricSignatureAlgorithm.None ||
+ SecurityPolicy.EphemeralKeyAlgorithm != CertificateKeyAlgorithm.None)
{
- case SecurityPolicies.Basic256:
- case SecurityPolicies.Aes128_Sha256_RsaOaep:
- case SecurityPolicies.Basic256Sha256:
+ byte[] encryptedBuffer = BufferManager.TakeBuffer(SendBufferSize, "Encrypt");
+
+ Array.Copy(
+ headerToCopy.Array,
+ headerToCopy.Offset,
+ encryptedBuffer,
+ 0,
+ headerToCopy.Count);
+ Array.Copy(
+ dataToEncrypt.Array,
+ dataToEncrypt.Offset,
+ encryptedBuffer,
+ headerToCopy.Count,
+ dataToEncrypt.Count);
+
+ return new ArraySegment(
+ encryptedBuffer,
+ 0,
+ dataToEncrypt.Count + headerToCopy.Count);
+ }
+
+ switch (SecurityPolicy.AsymmetricEncryptionAlgorithm)
+ {
+ case AsymmetricEncryptionAlgorithm.RsaOaepSha1:
return Rsa_Encrypt(
dataToEncrypt,
headerToCopy,
receiverCertificate,
RsaUtils.Padding.OaepSHA1);
- case SecurityPolicies.Aes256_Sha256_RsaPss:
+ case AsymmetricEncryptionAlgorithm.RsaOaepSha256:
return Rsa_Encrypt(
dataToEncrypt,
headerToCopy,
receiverCertificate,
RsaUtils.Padding.OaepSHA256);
- case SecurityPolicies.Basic128Rsa15:
+ default:
+ case AsymmetricEncryptionAlgorithm.RsaPkcs15Sha1:
return Rsa_Encrypt(
dataToEncrypt,
headerToCopy,
receiverCertificate,
RsaUtils.Padding.Pkcs1);
- default:
- byte[] encryptedBuffer = BufferManager.TakeBuffer(SendBufferSize, "Encrypt");
-
- Array.Copy(
- headerToCopy.Array,
- headerToCopy.Offset,
- encryptedBuffer,
- 0,
- headerToCopy.Count);
- Array.Copy(
- dataToEncrypt.Array,
- dataToEncrypt.Offset,
- encryptedBuffer,
- headerToCopy.Count,
- dataToEncrypt.Count);
-
- return new ArraySegment(
- encryptedBuffer,
- 0,
- dataToEncrypt.Count + headerToCopy.Count);
}
}
@@ -1361,6 +1360,30 @@ protected ArraySegment Decrypt(
ArraySegment headerToCopy,
X509Certificate2 receiverCertificate)
{
+ if (SecurityPolicy.AsymmetricSignatureAlgorithm == AsymmetricSignatureAlgorithm.None ||
+ SecurityPolicy.EphemeralKeyAlgorithm != CertificateKeyAlgorithm.None)
+ {
+ byte[] decryptedBuffer = BufferManager.TakeBuffer(SendBufferSize, "Decrypt");
+
+ Array.Copy(
+ headerToCopy.Array,
+ headerToCopy.Offset,
+ decryptedBuffer,
+ 0,
+ headerToCopy.Count);
+ Array.Copy(
+ dataToDecrypt.Array,
+ dataToDecrypt.Offset,
+ decryptedBuffer,
+ headerToCopy.Count,
+ dataToDecrypt.Count);
+
+ return new ArraySegment(
+ decryptedBuffer,
+ 0,
+ dataToDecrypt.Count + headerToCopy.Count);
+ }
+
switch (SecurityPolicyUri)
{
case SecurityPolicies.Basic256:
@@ -1371,7 +1394,10 @@ protected ArraySegment Decrypt(
headerToCopy,
receiverCertificate,
RsaUtils.Padding.OaepSHA1);
+ default:
case SecurityPolicies.Aes256_Sha256_RsaPss:
+ case SecurityPolicies.RSA_DH_AesGcm:
+ case SecurityPolicies.RSA_DH_ChaChaPoly:
return Rsa_Decrypt(
dataToDecrypt,
headerToCopy,
@@ -1383,26 +1409,6 @@ protected ArraySegment Decrypt(
headerToCopy,
receiverCertificate,
RsaUtils.Padding.Pkcs1);
- default:
- byte[] decryptedBuffer = BufferManager.TakeBuffer(SendBufferSize, "Decrypt");
-
- Array.Copy(
- headerToCopy.Array,
- headerToCopy.Offset,
- decryptedBuffer,
- 0,
- headerToCopy.Count);
- Array.Copy(
- dataToDecrypt.Array,
- dataToDecrypt.Offset,
- decryptedBuffer,
- headerToCopy.Count,
- dataToDecrypt.Count);
-
- return new ArraySegment(
- decryptedBuffer,
- 0,
- dataToDecrypt.Count + headerToCopy.Count);
}
}
diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Rsa.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Rsa.cs
index bd180e54de..04d509782b 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Rsa.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Rsa.cs
@@ -59,12 +59,14 @@ private static byte[] Rsa_Sign(
"No private key for certificate.");
// create the signature.
- return rsa.SignData(
+ var signature = rsa.SignData(
dataToSign.Array,
dataToSign.Offset,
dataToSign.Count,
algorithm,
padding);
+
+ return signature;
}
///
diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs
index dcb7548c87..9d8dbb24b5 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs
@@ -45,12 +45,12 @@ public partial class UaSCUaBinaryChannel
///
/// Returns the current security token.
///
- protected ChannelToken PreviousToken { get; private set; }
+ protected internal ChannelToken PreviousToken { get; private set; }
///
/// Returns the renewed but not yet activated token.
///
- protected ChannelToken RenewedToken { get; private set; }
+ protected internal ChannelToken RenewedToken { get; private set; }
///
/// Called when the token changes
@@ -136,11 +136,6 @@ protected void DiscardTokens()
OnTokenActivated?.Invoke(null, null);
}
- ///
- /// Indicates that an explicit signature is not present.
- ///
- private bool AuthenticatedEncryption { get; set; }
-
///
/// The byte length of the MAC (a.k.a signature) attached to each message.
///
@@ -154,71 +149,19 @@ protected void DiscardTokens()
///
/// Calculates the symmetric key sizes based on the current security policy.
///
+ ///
protected void CalculateSymmetricKeySizes()
{
- AuthenticatedEncryption = false;
-
- switch (SecurityPolicyUri)
- {
- case SecurityPolicies.Basic128Rsa15:
- SymmetricSignatureSize = 20;
- m_signatureKeySize = 16;
- m_encryptionKeySize = 16;
- EncryptionBlockSize = 16;
- break;
- case SecurityPolicies.Basic256:
- SymmetricSignatureSize = 20;
- m_signatureKeySize = 24;
- m_encryptionKeySize = 32;
- EncryptionBlockSize = 16;
- break;
- case SecurityPolicies.Basic256Sha256:
- SymmetricSignatureSize = 32;
- m_signatureKeySize = 32;
- m_encryptionKeySize = 32;
- EncryptionBlockSize = 16;
- break;
- case SecurityPolicies.Aes128_Sha256_RsaOaep:
- SymmetricSignatureSize = 32;
- m_signatureKeySize = 32;
- m_encryptionKeySize = 16;
- EncryptionBlockSize = 16;
- break;
- case SecurityPolicies.Aes256_Sha256_RsaPss:
- SymmetricSignatureSize = 32;
- m_signatureKeySize = 32;
- m_encryptionKeySize = 32;
- EncryptionBlockSize = 16;
- break;
- case SecurityPolicies.ECC_nistP256:
- case SecurityPolicies.ECC_brainpoolP256r1:
- SymmetricSignatureSize = 32;
- m_signatureKeySize = 32;
- m_encryptionKeySize = 16;
- EncryptionBlockSize = 16;
- break;
- case SecurityPolicies.ECC_curve25519:
- case SecurityPolicies.ECC_curve448:
- AuthenticatedEncryption = true;
- SymmetricSignatureSize = 16;
- m_signatureKeySize = 32;
- m_encryptionKeySize = 32;
- EncryptionBlockSize = 12;
- break;
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP384r1:
- SymmetricSignatureSize = 48;
- m_signatureKeySize = 48;
- m_encryptionKeySize = 32;
- EncryptionBlockSize = 16;
- break;
- default:
- SymmetricSignatureSize = 0;
- m_signatureKeySize = 0;
- m_encryptionKeySize = 0;
- EncryptionBlockSize = 1;
- break;
- }
+ SecurityPolicyInfo info = SecurityPolicies.GetInfo(SecurityPolicyUri)
+ ?? throw ServiceResultException.Create(
+ StatusCodes.BadSecurityPolicyRejected,
+ "Unsupported security policy: {0}",
+ SecurityPolicyUri);
+
+ SymmetricSignatureSize = info.SymmetricSignatureLength;
+ m_signatureKeySize = info.DerivedSignatureKeyLength;
+ m_encryptionKeySize = info.SymmetricEncryptionKeyLength;
+ EncryptionBlockSize = info.InitializationVectorLength != 0 ? info.InitializationVectorLength : 1;
}
private void DeriveKeysWithPSHA(
@@ -228,9 +171,15 @@ private void DeriveKeysWithPSHA(
ChannelToken token,
bool isServer)
{
+ using HMAC hmac = Utils.CreateHMAC(algorithmName, secret);
+
int length = m_signatureKeySize + m_encryptionKeySize + EncryptionBlockSize;
- using HMAC hmac = Utils.CreateHMAC(algorithmName, secret);
+ if (!isServer && SecurityPolicy.SecureChannelEnhancements)
+ {
+ length += hmac.HashSize/8;
+ }
+
byte[] output = Utils.PSHA(hmac, null, seed, 0, length);
byte[] signingKey = new byte[m_signatureKeySize];
@@ -246,44 +195,52 @@ private void DeriveKeysWithPSHA(
token.ServerSigningKey = signingKey;
token.ServerEncryptingKey = encryptingKey;
token.ServerInitializationVector = iv;
+ token.ServerHmac = SecurityPolicy.CreateSignatureHmac(signingKey);
}
else
{
token.ClientSigningKey = signingKey;
token.ClientEncryptingKey = encryptingKey;
token.ClientInitializationVector = iv;
+ token.ClientHmac = SecurityPolicy.CreateSignatureHmac(signingKey);
}
}
private void DeriveKeysWithHKDF(
- HashAlgorithmName algorithmName,
- byte[] salt,
ChannelToken token,
- bool isServer)
+ byte[] salt,
+ bool isServer,
+ int length)
{
- int length = m_signatureKeySize + m_encryptionKeySize + EncryptionBlockSize;
+ CryptoTrace.WriteLine($"DeriveKeys for {((isServer) ? "SERVER" : "CLIENT")}");
- byte[] output = m_localNonce.DeriveKey(m_remoteNonce, salt, algorithmName, length);
+ byte[] keyData = m_localNonce.DeriveKeyData(
+ token.Secret,
+ salt,
+ token.SecurityPolicy.KeyDerivationAlgorithm,
+ length);
byte[] signingKey = new byte[m_signatureKeySize];
byte[] encryptingKey = new byte[m_encryptionKeySize];
byte[] iv = new byte[EncryptionBlockSize];
- Buffer.BlockCopy(output, 0, signingKey, 0, signingKey.Length);
- Buffer.BlockCopy(output, m_signatureKeySize, encryptingKey, 0, encryptingKey.Length);
- Buffer.BlockCopy(output, m_signatureKeySize + m_encryptionKeySize, iv, 0, iv.Length);
+ Buffer.BlockCopy(keyData, 0, signingKey, 0, signingKey.Length);
+ Buffer.BlockCopy(keyData, m_signatureKeySize, encryptingKey, 0, encryptingKey.Length);
+ Buffer.BlockCopy(keyData, m_signatureKeySize + m_encryptionKeySize, iv, 0, iv.Length);
if (isServer)
{
token.ServerSigningKey = signingKey;
token.ServerEncryptingKey = encryptingKey;
token.ServerInitializationVector = iv;
+ token.ServerHmac = token.SecurityPolicy.CreateSignatureHmac(signingKey);
}
else
{
token.ClientSigningKey = signingKey;
token.ClientEncryptingKey = encryptingKey;
token.ClientInitializationVector = iv;
+ token.ClientHmac = token.SecurityPolicy.CreateSignatureHmac(signingKey);
}
}
@@ -292,6 +249,8 @@ private void DeriveKeysWithHKDF(
///
protected void ComputeKeys(ChannelToken token)
{
+ token.SecurityPolicy = SecurityPolicies.GetInfo(SecurityPolicyUri);
+
if (SecurityMode == MessageSecurityMode.None)
{
return;
@@ -300,170 +259,50 @@ protected void ComputeKeys(ChannelToken token)
byte[] serverSecret = token.ServerNonce;
byte[] clientSecret = token.ClientNonce;
- HashAlgorithmName algorithmName = HashAlgorithmName.SHA256;
- switch (SecurityPolicyUri)
+ switch (token.SecurityPolicy.KeyDerivationAlgorithm)
{
- case SecurityPolicies.ECC_nistP256:
- case SecurityPolicies.ECC_brainpoolP256r1:
+ case KeyDerivationAlgorithm.HKDFSha256:
+ case KeyDerivationAlgorithm.HKDFSha384:
{
- algorithmName = HashAlgorithmName.SHA256;
- byte[] length =
- SecurityMode == MessageSecurityMode.Sign
- ? s_hkdfAes128SignOnlyKeyLength
- : s_hkdfAes128SignAndEncryptKeyLength;
- byte[] serverSalt = Utils.Append(
- length,
- s_hkdfServerLabel,
- serverSecret,
- clientSecret);
- byte[] clientSalt = Utils.Append(
- length,
- s_hkdfClientLabel,
- clientSecret,
- serverSecret);
-
-#if DEBUG
- m_logger.LogDebug("Length={Length}", Utils.ToHexString(length));
- m_logger.LogDebug("ClientSecret={ClientSecret}", Utils.ToHexString(clientSecret));
- m_logger.LogDebug("ServerSecret={ServerSecret}", Utils.ToHexString(clientSecret));
- m_logger.LogDebug("ServerSalt={ServerSalt}", Utils.ToHexString(serverSalt));
- m_logger.LogDebug("ClientSalt={ClientSalt}", Utils.ToHexString(clientSalt));
-#endif
+ token.Secret = m_localNonce.GenerateSecret(m_remoteNonce, token.PreviousSecret);
- DeriveKeysWithHKDF(algorithmName, serverSalt, token, true);
- DeriveKeysWithHKDF(algorithmName, clientSalt, token, false);
- break;
- }
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP384r1:
- {
- algorithmName = HashAlgorithmName.SHA384;
- byte[] length =
- SecurityMode == MessageSecurityMode.Sign
- ? s_hkdfAes256SignOnlyKeyLength
- : s_hkdfAes256SignAndEncryptKeyLength;
- byte[] serverSalt = Utils.Append(
- length,
- s_hkdfServerLabel,
- serverSecret,
- clientSecret);
byte[] clientSalt = Utils.Append(
- length,
+ BitConverter.GetBytes((ushort)token.SecurityPolicy.ClientKeyDataLength),
s_hkdfClientLabel,
clientSecret,
serverSecret);
-#if DEBUG
- m_logger.LogDebug("Length={Length}", Utils.ToHexString(length));
- m_logger.LogDebug("ClientSecret={ClientSecret}", Utils.ToHexString(clientSecret));
- m_logger.LogDebug("ServerSecret={ServerSecret}", Utils.ToHexString(clientSecret));
- m_logger.LogDebug("ServerSalt={ServerSalt}", Utils.ToHexString(serverSalt));
- m_logger.LogDebug("ClientSalt={ClientSalt}", Utils.ToHexString(clientSalt));
-#endif
+ DeriveKeysWithHKDF(token, clientSalt, false, token.SecurityPolicy.ClientKeyDataLength);
- DeriveKeysWithHKDF(algorithmName, serverSalt, token, true);
- DeriveKeysWithHKDF(algorithmName, clientSalt, token, false);
- break;
- }
- case SecurityPolicies.ECC_curve25519:
- case SecurityPolicies.ECC_curve448:
- {
- algorithmName = HashAlgorithmName.SHA256;
- byte[] length = s_hkdfChaCha20Poly1305KeyLength;
byte[] serverSalt = Utils.Append(
- length,
+ BitConverter.GetBytes((ushort)token.SecurityPolicy.ServerKeyDataLength),
s_hkdfServerLabel,
serverSecret,
clientSecret);
- byte[] clientSalt = Utils.Append(
- length,
- s_hkdfClientLabel,
- clientSecret,
- serverSecret);
-
-#if DEBUG
- m_logger.LogDebug("Length={Length}", Utils.ToHexString(length));
- m_logger.LogDebug("ClientSecret={ClientSecret}", Utils.ToHexString(clientSecret));
- m_logger.LogDebug("ServerSecret={ServerSecret}", Utils.ToHexString(clientSecret));
- m_logger.LogDebug("ServerSalt={ServerSalt}", Utils.ToHexString(serverSalt));
- m_logger.LogDebug("ClientSalt={ClientSalt}", Utils.ToHexString(clientSalt));
-#endif
- DeriveKeysWithHKDF(algorithmName, serverSalt, token, true);
- DeriveKeysWithHKDF(algorithmName, clientSalt, token, false);
+ DeriveKeysWithHKDF(token, serverSalt, true, token.SecurityPolicy.ServerKeyDataLength);
+
+ CryptoTrace.Start(ConsoleColor.Green, $"ComputeKeys (TokenId={token.TokenId})");
+ CryptoTrace.WriteLine($"IKM={CryptoTrace.KeyToString(token.Secret)}");
+ CryptoTrace.WriteLine($"ServerNonce={CryptoTrace.KeyToString(serverSecret)}");
+ CryptoTrace.WriteLine($"ClientNonce={CryptoTrace.KeyToString(clientSecret)}");
+ CryptoTrace.WriteLine($"ServerSalt={CryptoTrace.KeyToString(serverSalt)}");
+ CryptoTrace.WriteLine($"ServerEncryptingKey={CryptoTrace.KeyToString(token.ServerEncryptingKey)}");
+ CryptoTrace.WriteLine($"ServerInitializationVector={CryptoTrace.KeyToString(token.ServerInitializationVector)}");
+ CryptoTrace.WriteLine($"ClientEncryptingKey={CryptoTrace.KeyToString(token.ClientEncryptingKey)}");
+ CryptoTrace.WriteLine($"ClientInitializationVector={CryptoTrace.KeyToString(token.ClientInitializationVector)}");
+ CryptoTrace.Finish("ComputeKeys");
break;
}
- case SecurityPolicies.Basic128Rsa15:
- case SecurityPolicies.Basic256:
- algorithmName = HashAlgorithmName.SHA1;
- goto default;
+
default:
+ case KeyDerivationAlgorithm.PSha1:
+ case KeyDerivationAlgorithm.PSha256:
+ HashAlgorithmName algorithmName = token.SecurityPolicy.GetKeyDerivationHashAlgorithmName();
DeriveKeysWithPSHA(algorithmName, serverSecret, clientSecret, token, false);
DeriveKeysWithPSHA(algorithmName, clientSecret, serverSecret, token, true);
break;
}
-
- switch (SecurityPolicyUri)
- {
- case SecurityPolicies.Basic128Rsa15:
- case SecurityPolicies.Basic256:
- case SecurityPolicies.Basic256Sha256:
- case SecurityPolicies.Aes128_Sha256_RsaOaep:
- case SecurityPolicies.Aes256_Sha256_RsaPss:
- case SecurityPolicies.ECC_nistP256:
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP256r1:
- case SecurityPolicies.ECC_brainpoolP384r1:
- // create encryptors.
- var aesCbcEncryptorProvider = Aes.Create();
- aesCbcEncryptorProvider.Mode = CipherMode.CBC;
- aesCbcEncryptorProvider.Padding = PaddingMode.None;
- aesCbcEncryptorProvider.Key = token.ClientEncryptingKey;
- aesCbcEncryptorProvider.IV = token.ClientInitializationVector;
- token.ClientEncryptor = aesCbcEncryptorProvider;
-
- var aesCbcDecryptorProvider = Aes.Create();
- aesCbcDecryptorProvider.Mode = CipherMode.CBC;
- aesCbcDecryptorProvider.Padding = PaddingMode.None;
- aesCbcDecryptorProvider.Key = token.ServerEncryptingKey;
- aesCbcDecryptorProvider.IV = token.ServerInitializationVector;
- token.ServerEncryptor = aesCbcDecryptorProvider;
- break;
- default:
- // TODO: is this even legal or should we throw? What are the implications
- token.ClientEncryptor = null;
- token.ServerEncryptor = null;
- break;
- }
-
- switch (SecurityPolicyUri)
- {
- case SecurityPolicies.Basic128Rsa15:
- case SecurityPolicies.Basic256:
-#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms
- token.ServerHmac = new HMACSHA1(token.ServerSigningKey);
- token.ClientHmac = new HMACSHA1(token.ClientSigningKey);
-#pragma warning restore CA5350 // Do Not Use Weak Cryptographic Algorithms
- break;
- case SecurityPolicies.Basic256Sha256:
- case SecurityPolicies.Aes128_Sha256_RsaOaep:
- case SecurityPolicies.Aes256_Sha256_RsaPss:
- case SecurityPolicies.ECC_nistP256:
- case SecurityPolicies.ECC_brainpoolP256r1:
- token.ServerHmac = new HMACSHA256(token.ServerSigningKey);
- token.ClientHmac = new HMACSHA256(token.ClientSigningKey);
- break;
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP384r1:
- token.ServerHmac = new HMACSHA384(token.ServerSigningKey);
- token.ClientHmac = new HMACSHA384(token.ClientSigningKey);
- break;
- default:
- // TODO: is this even legal or should we throw? What are the implications
- token.ServerHmac = null;
- token.ClientHmac = null;
- break;
- }
}
///
@@ -487,20 +326,22 @@ protected BufferCollection WriteSymmetricMessage(
int maxCipherTextSize = SendBufferSize - TcpMessageLimits.SymmetricHeaderSize;
int maxCipherBlocks = maxCipherTextSize / EncryptionBlockSize;
int maxPlainTextSize = maxCipherBlocks * EncryptionBlockSize;
+
+ int signatureSize = SymmetricSignatureSize;
+ int paddingCountSize =
+ (SecurityMode != MessageSecurityMode.SignAndEncrypt || token.SecurityPolicy.NoSymmetricEncryptionPadding)
+ ? 0
+ : (EncryptionBlockSize > byte.MaxValue ? 2 : 1);
+
int maxPayloadSize =
maxPlainTextSize -
- SymmetricSignatureSize -
- 1 -
- TcpMessageLimits.SequenceHeaderSize;
+ signatureSize -
+ TcpMessageLimits.SequenceHeaderSize -
+ paddingCountSize;
+
const int headerSize = TcpMessageLimits.SymmetricHeaderSize +
TcpMessageLimits.SequenceHeaderSize;
- // no padding byte.
- if (AuthenticatedEncryption)
- {
- maxPayloadSize++;
- }
-
// write the body to stream.
var ostrm = new ArraySegmentStream(
BufferManager,
@@ -540,14 +381,12 @@ protected BufferCollection WriteSymmetricMessage(
byte[] buffer = BufferManager.TakeBuffer(
SendBufferSize,
"WriteSymmetricMessage");
+
chunksToProcess.Add(new ArraySegment(buffer, 0, 0));
}
var chunksToSend = new BufferCollection(chunksToProcess.Capacity);
-#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
- Span paddingBuffer = stackalloc byte[EncryptionBlockSize];
-#endif
int messageSize = 0;
for (int ii = 0; ii < chunksToProcess.Count; ii++)
@@ -563,6 +402,7 @@ protected BufferCollection WriteSymmetricMessage(
var strm = new MemoryStream(chunkToProcess.Array, 0, SendBufferSize);
using var encoder = new BinaryEncoder(strm, Quotas.MessageContext, false);
+
// check if the message needs to be aborted.
if (MessageLimitsExceeded(
isRequest,
@@ -593,6 +433,7 @@ protected BufferCollection WriteSymmetricMessage(
limitsExceeded = true;
}
+
// check if the message is complete.
else if (ii == chunksToProcess.Count - 1)
{
@@ -608,25 +449,24 @@ protected BufferCollection WriteSymmetricMessage(
count += TcpMessageLimits.SequenceHeaderSize;
count += chunkToProcess.Count;
- count += SymmetricSignatureSize;
+ count += paddingCountSize;
+ count += signatureSize;
- // calculate the padding.
int padding = 0;
- if (SecurityMode == MessageSecurityMode.SignAndEncrypt &&
- !AuthenticatedEncryption)
+ if (paddingCountSize > 0)
{
- // reserve one byte for the padding size.
- count++;
+ padding = EncryptionBlockSize - (count % EncryptionBlockSize);
- // use padding as helper to calc the real padding
- padding = count % EncryptionBlockSize;
- if (padding != 0)
+ if (padding == EncryptionBlockSize)
{
- padding = EncryptionBlockSize - padding;
+ padding = 0;
}
- count += padding;
+ if (padding > 0)
+ {
+ count += padding;
+ }
}
count += TcpMessageLimits.SymmetricHeaderSize;
@@ -637,7 +477,6 @@ protected BufferCollection WriteSymmetricMessage(
uint sequenceNumber = GetNewSequenceNumber();
encoder.WriteUInt32(null, sequenceNumber);
-
encoder.WriteUInt32(null, requestId);
// skip body.
@@ -646,63 +485,27 @@ protected BufferCollection WriteSymmetricMessage(
// update message size count.
messageSize += chunkToProcess.Count;
- // write padding.
- if (SecurityMode == MessageSecurityMode.SignAndEncrypt &&
- !AuthenticatedEncryption)
- {
-#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
- if (padding > 1)
- {
- Span buffer = paddingBuffer[..(padding + 1)];
- buffer.Fill((byte)padding);
- encoder.WriteRawBytes(buffer);
- }
- else
-#endif
- {
- for (int jj = 0; jj <= padding; jj++)
- {
- encoder.WriteByte(null, (byte)padding);
- }
- }
- }
+ ArraySegment dataToSend;
- // calculate and write signature.
if (SecurityMode != MessageSecurityMode.None)
{
- if (AuthenticatedEncryption)
- {
- strm.Seek(SymmetricSignatureSize, SeekOrigin.Current);
- }
- else
- {
- byte[] signature = Sign(
- token,
- new ArraySegment(chunkToProcess.Array, 0, encoder.Position),
- isRequest);
-
- if (signature != null)
- {
- encoder.WriteRawBytes(signature, 0, signature.Length);
- }
- }
- }
-
- if ((SecurityMode == MessageSecurityMode.SignAndEncrypt &&
- !AuthenticatedEncryption) ||
- (SecurityMode != MessageSecurityMode.None && AuthenticatedEncryption))
- {
- // encrypt the data.
- var dataToEncrypt = new ArraySegment(
+ dataToSend = new ArraySegment(
chunkToProcess.Array,
TcpMessageLimits.SymmetricHeaderSize,
encoder.Position - TcpMessageLimits.SymmetricHeaderSize);
- Encrypt(token, dataToEncrypt, isRequest);
+
+ dataToSend = EncryptAndSign(token, dataToSend, isRequest);
+ }
+ else
+ {
+ dataToSend = new ArraySegment(
+ chunkToProcess.Array,
+ 0,
+ encoder.Position);
}
// add the header into chunk.
- chunksToSend.Add(
- new ArraySegment(chunkToProcess.Array, 0, encoder.Position));
+ chunksToSend.Add(dataToSend);
}
// ensure the buffers don't get cleaned up on exit.
@@ -717,6 +520,22 @@ protected BufferCollection WriteSymmetricMessage(
}
}
}
+ private ArraySegment EncryptAndSign(
+ ChannelToken token,
+ ArraySegment dataToEncrypt,
+ bool useClientKeys)
+ {
+ return CryptoUtils.SymmetricEncryptAndSign(
+ dataToEncrypt,
+ token.SecurityPolicy,
+ useClientKeys ? token.ClientEncryptingKey : token.ServerEncryptingKey,
+ useClientKeys ? token.ClientInitializationVector : token.ServerInitializationVector,
+ useClientKeys ? token.ClientSigningKey : token.ServerSigningKey,
+ useClientKeys ? token.ClientHmac : token.ServerHmac,
+ this.SecurityMode == MessageSecurityMode.Sign,
+ token.TokenId,
+ (uint)(m_localSequenceNumber - 1)); // already incremented to create this message. need the last one sent.
+ }
///
/// Decrypts and verifies a message chunk.
@@ -750,6 +569,7 @@ protected ArraySegment ReadSymmetricMessage(
{
ActivateToken(RenewedToken);
}
+
// check if activation of the new token should be forced.
else if (RenewedToken != null && CurrentToken.ActivationRequired)
{
@@ -803,717 +623,55 @@ protected ArraySegment ReadSymmetricMessage(
int headerSize = decoder.Position;
- if (SecurityMode == MessageSecurityMode.SignAndEncrypt)
- {
- // decrypt the message.
- Decrypt(
- token,
- new ArraySegment(
- buffer.Array,
- buffer.Offset + headerSize,
- buffer.Count - headerSize),
- isRequest);
- }
+ var dataToProcess = new ArraySegment(
+ buffer.Array,
+ buffer.Offset,
+ buffer.Count);
- int paddingCount = 0;
if (SecurityMode != MessageSecurityMode.None)
{
- int signatureStart = buffer.Offset + buffer.Count - SymmetricSignatureSize;
-
- // extract signature.
- byte[] signature = new byte[SymmetricSignatureSize];
- Array.Copy(buffer.Array, signatureStart, signature, 0, signature.Length);
-
- // verify the signature.
- if (!Verify(
- token,
- signature,
- new ArraySegment(
- buffer.Array,
- buffer.Offset,
- buffer.Count - SymmetricSignatureSize),
- isRequest))
- {
- m_logger.LogError("ChannelId {Id}: Could not verify signature on message.", Id);
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Could not verify the signature on the message.");
- }
+ dataToProcess = new ArraySegment(
+ buffer.Array,
+ buffer.Offset + headerSize,
+ buffer.Count - headerSize);
- if (SecurityMode == MessageSecurityMode.SignAndEncrypt)
- {
- // verify padding.
- int paddingStart = signatureStart - 1;
- paddingCount = buffer.Array[paddingStart];
-
- for (int ii = paddingStart - paddingCount; ii < paddingStart; ii++)
- {
- if (buffer.Array[ii] != paddingCount)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Could not verify the padding in the message.");
- }
- }
-
- // add byte for size.
- paddingCount++;
- }
+ dataToProcess = DecryptAndVerify(
+ token,
+ dataToProcess,
+ isRequest);
}
// extract request id and sequence number.
sequenceNumber = decoder.ReadUInt32(null);
requestId = decoder.ReadUInt32(null);
- // return an the data contained in the message.
- int startOfBody =
- buffer.Offset +
- TcpMessageLimits.SymmetricHeaderSize +
- TcpMessageLimits.SequenceHeaderSize;
- int sizeOfBody =
- buffer.Count -
- TcpMessageLimits.SymmetricHeaderSize -
- TcpMessageLimits.SequenceHeaderSize -
- paddingCount -
- SymmetricSignatureSize;
-
- return new ArraySegment(buffer.Array, startOfBody, sizeOfBody);
- }
-
- ///
- /// Returns the symmetric signature for the data.
- ///
- protected byte[] Sign(ChannelToken token, ArraySegment dataToSign, bool useClientKeys)
- {
- switch (SecurityPolicyUri)
- {
- case SecurityPolicies.Basic128Rsa15:
- case SecurityPolicies.Basic256:
- case SecurityPolicies.Basic256Sha256:
- case SecurityPolicies.Aes128_Sha256_RsaOaep:
- case SecurityPolicies.Aes256_Sha256_RsaPss:
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP256r1:
- case SecurityPolicies.ECC_brainpoolP384r1:
- case SecurityPolicies.ECC_nistP256:
- return SymmetricSign(token, dataToSign, useClientKeys);
- default:
- return null;
- }
- }
-
- ///
- /// Returns the symmetric signature for the data.
- ///
- protected bool Verify(
- ChannelToken token,
- byte[] signature,
- ArraySegment dataToVerify,
- bool useClientKeys)
- {
- // verify signature.
- switch (SecurityPolicyUri)
- {
- case SecurityPolicies.None:
- return true;
- case SecurityPolicies.Basic128Rsa15:
- case SecurityPolicies.Basic256:
- case SecurityPolicies.Basic256Sha256:
- case SecurityPolicies.Aes128_Sha256_RsaOaep:
- case SecurityPolicies.Aes256_Sha256_RsaPss:
- case SecurityPolicies.ECC_nistP256:
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP256r1:
- case SecurityPolicies.ECC_brainpoolP384r1:
- return SymmetricVerify(token, signature, dataToVerify, useClientKeys);
- default:
- return false;
- }
- }
-
- ///
- /// Decrypts the data in a buffer using symmetric encryption.
- ///
- ///
- protected void Encrypt(
- ChannelToken token,
- ArraySegment dataToEncrypt,
- bool useClientKeys)
- {
- switch (SecurityPolicyUri)
- {
- case SecurityPolicies.None:
- break;
- case SecurityPolicies.Basic256:
- case SecurityPolicies.Basic256Sha256:
- case SecurityPolicies.Basic128Rsa15:
- case SecurityPolicies.Aes128_Sha256_RsaOaep:
- case SecurityPolicies.Aes256_Sha256_RsaPss:
- case SecurityPolicies.ECC_nistP256:
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP256r1:
- case SecurityPolicies.ECC_brainpoolP384r1:
- SymmetricEncrypt(token, dataToEncrypt, useClientKeys);
- break;
+ headerSize += TcpMessageLimits.SequenceHeaderSize;
-#if CURVE25519
- case SecurityPolicies.ECC_curve25519:
- case SecurityPolicies.ECC_curve448:
- {
- if (SecurityMode == MessageSecurityMode.SignAndEncrypt)
- {
- // narowing conversion can safely be done on m_localSequenceNumber
- SymmetricEncryptWithChaCha20Poly1305(
- token,
- (uint)m_localSequenceNumber,
- dataToEncrypt,
- useClientKeys);
- break;
- }
- // narowing conversion can safely be done on m_localSequenceNumber
- SymmetricSignWithPoly1305(token, (uint)m_localSequenceNumber, dataToEncrypt, useClientKeys);
- break;
- }
-#endif
- default:
- throw new NotSupportedException(SecurityPolicyUri);
- }
+ // return only the data contained in the message.
+ return new ArraySegment(
+ dataToProcess.Array,
+ dataToProcess.Offset + headerSize,
+ dataToProcess.Count - headerSize);
}
- ///
- /// Decrypts the data in a buffer using symmetric encryption.
- ///
- ///
- protected void Decrypt(
+ private ArraySegment DecryptAndVerify(
ChannelToken token,
ArraySegment dataToDecrypt,
bool useClientKeys)
{
- switch (SecurityPolicyUri)
- {
- case SecurityPolicies.None:
- break;
- case SecurityPolicies.Basic256:
- case SecurityPolicies.Basic256Sha256:
- case SecurityPolicies.Basic128Rsa15:
- case SecurityPolicies.ECC_nistP256:
- case SecurityPolicies.ECC_nistP384:
- case SecurityPolicies.ECC_brainpoolP256r1:
- case SecurityPolicies.ECC_brainpoolP384r1:
- case SecurityPolicies.Aes128_Sha256_RsaOaep:
- case SecurityPolicies.Aes256_Sha256_RsaPss:
- SymmetricDecrypt(token, dataToDecrypt, useClientKeys);
- break;
-
-#if CURVE25519
- case SecurityPolicies.ECC_curve25519:
- case SecurityPolicies.ECC_curve448:
- {
- if (SecurityMode == MessageSecurityMode.SignAndEncrypt)
- {
- SymmetricDecryptWithChaCha20Poly1305(
- token,
- m_remoteSequenceNumber,
- dataToDecrypt,
- useClientKeys);
- break;
- }
-
- SymmetricVerifyWithPoly1305(token, m_remoteSequenceNumber, dataToDecrypt, useClientKeys);
- break;
- }
-#endif
-
- default:
- throw new NotSupportedException(SecurityPolicyUri);
- }
- }
-
-#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
- ///
- /// Signs the message using HMAC.
- ///
- ///
- private static byte[] SymmetricSign(
- ChannelToken token,
- ReadOnlySpan dataToSign,
- bool useClientKeys)
- {
- // get HMAC object.
- HMAC hmac = useClientKeys ? token.ClientHmac : token.ServerHmac;
-
- // compute hash.
- int hashSizeInBytes = hmac.HashSize >> 3;
- byte[] signature = new byte[hashSizeInBytes];
- bool result = hmac.TryComputeHash(dataToSign, signature, out int bytesWritten);
-
- // check result
- if (!result || bytesWritten != hashSizeInBytes)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "The computed hash doesn't match the expected size.");
- }
-
- // return signature.
- return signature;
- }
-#else
- ///
- /// Signs the message using HMAC.
- ///
- private static byte[] SymmetricSign(
- ChannelToken token,
- ArraySegment dataToSign,
- bool useClientKeys)
- {
- // get HMAC object.
- HMAC hmac = useClientKeys ? token.ClientHmac : token.ServerHmac;
- // compute hash.
- var istrm = new MemoryStream(
- dataToSign.Array,
- dataToSign.Offset,
- dataToSign.Count,
- false);
- byte[] signature = hmac.ComputeHash(istrm);
- istrm.Dispose();
-
- // return signature.
- return signature;
- }
-#endif
-
-#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
- ///
- /// Verifies a HMAC for a message.
- ///
- private bool SymmetricVerify(
- ChannelToken token,
- ReadOnlySpan signature,
- ReadOnlySpan dataToVerify,
- bool useClientKeys)
- {
- // get HMAC object.
- HMAC hmac = useClientKeys ? token.ClientHmac : token.ServerHmac;
-
- // compute hash.
- int hashSizeInBytes = hmac.HashSize >> 3;
- Span computedSignature = stackalloc byte[hashSizeInBytes];
- bool result = hmac.TryComputeHash(
- dataToVerify,
- computedSignature,
- out int bytesWritten);
- System.Diagnostics.Debug.Assert(bytesWritten == hashSizeInBytes);
- // compare signatures.
- if (!result || !computedSignature.SequenceEqual(signature))
- {
- string expectedSignature = Utils.ToHexString(computedSignature.ToArray());
- string messageType = Encoding.UTF8.GetString(dataToVerify[..4]);
- int messageLength = BitConverter.ToInt32(dataToVerify[4..]);
- string actualSignature = Utils.ToHexString(signature);
-#else
- ///
- /// Verifies a HMAC for a message.
- ///
- private bool SymmetricVerify(
- ChannelToken token,
- byte[] signature,
- ArraySegment dataToVerify,
- bool useClientKeys)
- {
- // get HMAC object.
- HMAC hmac = useClientKeys ? token.ClientHmac : token.ServerHmac;
-
- var istrm = new MemoryStream(
- dataToVerify.Array,
- dataToVerify.Offset,
- dataToVerify.Count,
- false);
- byte[] computedSignature = hmac.ComputeHash(istrm);
- istrm.Dispose();
- // compare signatures.
- if (!Utils.IsEqual(computedSignature, signature))
- {
- string expectedSignature = Utils.ToHexString(computedSignature);
- string messageType = Encoding.UTF8
- .GetString(dataToVerify.Array, dataToVerify.Offset, 4);
- int messageLength = BitConverter.ToInt32(
- dataToVerify.Array,
- dataToVerify.Offset + 4);
- string actualSignature = Utils.ToHexString(signature);
-#endif
- m_logger.LogError(
- "Channel{Id}: Could not validate signature. ChannelId={ChannelId}, TokenId={TokenId}, MessageType={MessageType}, Length={Length} ExpectedSignature={ExpectedSignature} ActualSignature={ActualSignature}",
- Id,
- token.ChannelId,
- token.TokenId,
- messageType,
- messageLength,
- expectedSignature,
- actualSignature);
-
- return false;
- }
-
- return true;
- }
-
- ///
- /// Encrypts a message using a symmetric algorithm.
- ///
- ///
- private static void SymmetricEncrypt(
- ChannelToken token,
- ArraySegment dataToEncrypt,
- bool useClientKeys)
- {
- SymmetricAlgorithm encryptingKey =
- (useClientKeys ? token.ClientEncryptor : token.ServerEncryptor)
- ?? throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Token missing symmetric key object.");
-
- using ICryptoTransform encryptor = encryptingKey.CreateEncryptor();
- byte[] blockToEncrypt = dataToEncrypt.Array;
-
- int start = dataToEncrypt.Offset;
- int count = dataToEncrypt.Count;
-
- if (count % encryptor.InputBlockSize != 0)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Input data is not an even number of encryption blocks.");
- }
- encryptor.TransformBlock(blockToEncrypt, start, count, blockToEncrypt, start);
- }
-
- ///
- /// Decrypts a message using a symmetric algorithm.
- ///
- ///
- private static void SymmetricDecrypt(
- ChannelToken token,
- ArraySegment dataToDecrypt,
- bool useClientKeys)
- {
- // get the decrypting key.
- SymmetricAlgorithm decryptingKey =
- (useClientKeys ? token.ClientEncryptor : token.ServerEncryptor)
- ?? throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Token missing symmetric key object.");
-
- using ICryptoTransform decryptor = decryptingKey.CreateDecryptor();
- byte[] blockToDecrypt = dataToDecrypt.Array;
-
- int start = dataToDecrypt.Offset;
- int count = dataToDecrypt.Count;
-
- if (count % decryptor.InputBlockSize != 0)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Input data is not an even number of encryption blocks.");
- }
-
- decryptor.TransformBlock(blockToDecrypt, start, count, blockToDecrypt, start);
- }
-
-#if CURVE25519
- ///
- /// Encrypts a message using a symmetric algorithm.
- ///
- private static void SymmetricEncryptWithChaCha20Poly1305(
- ChannelToken token,
- uint lastSequenceNumber,
- ArraySegment dataToEncrypt,
- bool useClientKeys)
- {
- var signingKey = (useClientKeys) ? token.ClientSigningKey : token.ServerSigningKey;
-
- if (signingKey == null)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Token missing symmetric key object.");
- }
-
- var encryptingKey = (useClientKeys) ? token.ClientEncryptingKey : token.ServerEncryptingKey;
-
- if (encryptingKey == null)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Token missing symmetric key object.");
- }
-
- var iv = (useClientKeys) ? token.ClientInitializationVector : token.ServerInitializationVector;
-
- if (iv == null)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Token missing symmetric key object.");
- }
-
- // Utils.Trace($"EncryptKey={Utils.ToHexString(encryptingKey)}");
- // Utils.Trace($"EncryptIV1={Utils.ToHexString(iv)}");
- ApplyChaCha20Poly1305Mask(token, lastSequenceNumber, iv);
- // Utils.Trace($"EncryptIV2={Utils.ToHexString(iv)}");
-
- int signatureLength = 16;
-
- var plaintext = dataToEncrypt.Array;
- int headerSize = dataToEncrypt.Offset;
- int plainTextLength = dataToEncrypt.Offset + dataToEncrypt.Count - signatureLength;
-
- // Utils.Trace($"OUT={headerSize}|{plainTextLength}|{signatureLength}|[{plainTextLength + signatureLength}]");
-
- AeadParameters parameters = new AeadParameters(
- new KeyParameter(encryptingKey),
- signatureLength * 8,
- iv,
- null);
-
- ChaCha20Poly1305 encryptor = new ChaCha20Poly1305();
- encryptor.Init(true, parameters);
- encryptor.ProcessAadBytes(plaintext, 0, headerSize);
-
- byte[] ciphertext = new byte[encryptor.GetOutputSize(plainTextLength - headerSize) + headerSize];
- Buffer.BlockCopy(plaintext, 0, ciphertext, 0, headerSize);
- int length = encryptor.ProcessBytes(
- plaintext,
- headerSize,
- plainTextLength - headerSize,
- ciphertext,
- headerSize);
- length += encryptor.DoFinal(ciphertext, length + headerSize);
-
- if (ciphertext.Length - headerSize != length)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- $"Cipher text not the expected size. [{ciphertext.Length - headerSize} != {length}]");
- }
-
- Buffer.BlockCopy(ciphertext, 0, plaintext, 0, plainTextLength + signatureLength);
-
- // byte[] mac = new byte[16];
- // Buffer.BlockCopy(plaintext, plainTextLength, mac, 0, signatureLength);
- // Utils.Trace($"EncryptMAC1={Utils.ToHexString(encryptor.GetMac())}");
- // Utils.Trace($"EncryptMAC2={Utils.ToHexString(mac)}");
- }
-
- ///
- /// Encrypts a message using a symmetric algorithm.
- ///
- private static void SymmetricSignWithPoly1305(
- ChannelToken token,
- uint lastSequenceNumber,
- ArraySegment dataToEncrypt,
- bool useClientKeys)
- {
- var signingKey = (useClientKeys) ? token.ClientSigningKey : token.ServerSigningKey;
-
- if (signingKey == null)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Token missing symmetric key object.");
- }
-
- ApplyChaCha20Poly1305Mask(token, lastSequenceNumber, signingKey);
-
- using (var hash = SHA256.Create())
- {
- signingKey = hash.ComputeHash(signingKey);
- }
-
- // Utils.Trace($"SigningKey={Utils.ToHexString(signingKey)}");
-
- int signatureLength = 16;
-
- var plaintext = dataToEncrypt.Array;
- int headerSize = dataToEncrypt.Offset;
- int plainTextLength = dataToEncrypt.Offset + dataToEncrypt.Count - signatureLength;
-
- // Utils.Trace($"OUT={headerSize}|{plainTextLength}|{signatureLength}|[{plainTextLength + signatureLength}]");
-
- Poly1305 poly = new Poly1305();
-
- poly.Init(new KeyParameter(signingKey, 0, signingKey.Length));
- poly.BlockUpdate(plaintext, 0, plainTextLength);
- int length = poly.DoFinal(plaintext, plainTextLength);
-
- if (signatureLength != length)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- $"Signed data not the expected size. [{plainTextLength + signatureLength} != {length}]");
- }
- }
-
- ///
- /// Decrypts a message using a symmetric algorithm.
- ///
- private static void SymmetricDecryptWithChaCha20Poly1305(
- ChannelToken token,
- uint lastSequenceNumber,
- ArraySegment dataToDecrypt,
- bool useClientKeys)
- {
- var encryptingKey = (useClientKeys) ? token.ClientEncryptingKey : token.ServerEncryptingKey;
-
- if (encryptingKey == null)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Token missing symmetric key object.");
- }
-
- var iv = (useClientKeys) ? token.ClientInitializationVector : token.ServerInitializationVector;
-
- if (iv == null)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Token missing symmetric key object.");
- }
-
- // Utils.Trace($"DecryptKey={Utils.ToHexString(encryptingKey)}");
- // Utils.Trace($"DecryptIV1={Utils.ToHexString(iv)}");
- ApplyChaCha20Poly1305Mask(token, lastSequenceNumber, iv);
- // Utils.Trace($"DecryptIV2={Utils.ToHexString(iv)}");
-
- int signatureLength = 16;
-
- var ciphertext = dataToDecrypt.Array;
- int headerSize = dataToDecrypt.Offset;
- int cipherTextLength = dataToDecrypt.Offset + dataToDecrypt.Count - signatureLength;
-
- // Utils.Trace($"OUT={headerSize}|{cipherTextLength}|{signatureLength}|[{cipherTextLength + signatureLength}]");
-
- byte[] mac = new byte[16];
- Buffer.BlockCopy(ciphertext, cipherTextLength, mac, 0, signatureLength);
- // Utils.Trace($"DecryptMAC={Utils.ToHexString(mac)}");
-
- AeadParameters parameters = new AeadParameters(
- new KeyParameter(encryptingKey),
- signatureLength * 8,
- iv,
- null);
-
- ChaCha20Poly1305 decryptor = new ChaCha20Poly1305();
- decryptor.Init(false, parameters);
- decryptor.ProcessAadBytes(ciphertext, 0, headerSize);
-
- var plaintext = new byte[
- decryptor.GetOutputSize(cipherTextLength + signatureLength - headerSize) + headerSize
- ];
- Buffer.BlockCopy(ciphertext, headerSize, plaintext, 0, headerSize);
-
- int length = decryptor.ProcessBytes(
- ciphertext,
- headerSize,
- cipherTextLength + signatureLength - headerSize,
- plaintext,
- headerSize);
- length += decryptor.DoFinal(plaintext, length + headerSize);
-
- if (plaintext.Length - headerSize != length)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- $"Plain text not the expected size. [{plaintext.Length - headerSize} != {length}]");
- }
-
- Buffer.BlockCopy(plaintext, 0, ciphertext, 0, cipherTextLength);
- }
-
- ///
- /// Encrypts a message using a symmetric algorithm.
- ///
- private static void SymmetricVerifyWithPoly1305(
- ChannelToken token,
- uint lastSequenceNumber,
- ArraySegment dataToDecrypt,
- bool useClientKeys)
- {
- var signingKey = (useClientKeys) ? token.ClientSigningKey : token.ServerSigningKey;
-
- if (signingKey == null)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- "Token missing symmetric key object.");
- }
-
- ApplyChaCha20Poly1305Mask(token, lastSequenceNumber, signingKey);
- // Utils.Trace($"SigningKey={Utils.ToHexString(signingKey)}");
-
- using (var hash = SHA256.Create())
- {
- signingKey = hash.ComputeHash(signingKey);
- }
-
- int signatureLength = 16;
-
- var plaintext = dataToDecrypt.Array;
- int headerSize = dataToDecrypt.Offset;
- int plainTextLength = dataToDecrypt.Offset + dataToDecrypt.Count - signatureLength;
-
- // Utils.Trace($"OUT={headerSize}|{plainTextLength}|{signatureLength}|[{plainTextLength + signatureLength}]");
-
- Poly1305 poly = new Poly1305();
-
- poly.Init(new KeyParameter(signingKey, 0, signingKey.Length));
- poly.BlockUpdate(plaintext, 0, plainTextLength);
-
- byte[] mac = new byte[poly.GetMacSize()];
- int length = poly.DoFinal(mac, 0);
-
- if (signatureLength != length)
- {
- throw ServiceResultException.Create(
- StatusCodes.BadSecurityChecksFailed,
- $"Signed data not the expected size. [{plainTextLength + signatureLength} != {length}]");
- }
-
- for (int ii = 0; ii < mac.Length; ii++)
- {
- if (mac[ii] != plaintext[plainTextLength + ii])
- {
- throw ServiceResultException.Create(StatusCodes.BadSecurityChecksFailed, $"Invaid MAC on data.");
- }
- }
- }
-
- private static void ApplyChaCha20Poly1305Mask(ChannelToken token, uint lastSequenceNumber, byte[] iv)
- {
- iv[0] ^= (byte)((token.TokenId & 0x000000FF));
- iv[1] ^= (byte)((token.TokenId & 0x0000FF00) >> 8);
- iv[2] ^= (byte)((token.TokenId & 0x00FF0000) >> 16);
- iv[3] ^= (byte)((token.TokenId & 0xFF000000) >> 24);
- iv[4] ^= (byte)((lastSequenceNumber & 0x000000FF));
- iv[5] ^= (byte)((lastSequenceNumber & 0x0000FF00) >> 8);
- iv[6] ^= (byte)((lastSequenceNumber & 0x00FF0000) >> 16);
- iv[7] ^= (byte)((lastSequenceNumber & 0xFF000000) >> 24);
+ return CryptoUtils.SymmetricDecryptAndVerify(
+ dataToDecrypt,
+ token.SecurityPolicy,
+ useClientKeys ? token.ClientEncryptingKey : token.ServerEncryptingKey,
+ useClientKeys ? token.ClientInitializationVector : token.ServerInitializationVector,
+ useClientKeys ? token.ClientSigningKey : token.ServerSigningKey,
+ this.SecurityMode == MessageSecurityMode.Sign,
+ token.TokenId,
+ (uint)m_remoteSequenceNumber);
}
-#endif
private static readonly byte[] s_hkdfClientLabel = Encoding.UTF8.GetBytes("opcua-client");
private static readonly byte[] s_hkdfServerLabel = Encoding.UTF8.GetBytes("opcua-server");
- private static readonly byte[] s_hkdfAes128SignOnlyKeyLength = BitConverter.GetBytes(
- (ushort)32);
- private static readonly byte[] s_hkdfAes256SignOnlyKeyLength = BitConverter.GetBytes(
- (ushort)48);
- private static readonly byte[] s_hkdfAes128SignAndEncryptKeyLength = BitConverter.GetBytes(
- (ushort)64);
- private static readonly byte[] s_hkdfAes256SignAndEncryptKeyLength = BitConverter.GetBytes(
- (ushort)96);
- private static readonly byte[] s_hkdfChaCha20Poly1305KeyLength = BitConverter.GetBytes(
- (ushort)76);
private int m_signatureKeySize;
private int m_encryptionKeySize;
}
diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs
index 61b1e91354..cdab6337c9 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs
@@ -251,6 +251,15 @@ protected virtual void Dispose(bool disposing)
///
public string GlobalChannelId { get; private set; }
+ ///
+ internal byte[] ChannelThumbprint { get; set; }
+
+ ///
+ public byte[] ClientChannelCertificate { get; protected set; }
+
+ ///
+ public byte[] ServerChannelCertificate { get; protected set; }
+
///
/// Raised when the state of the channel changes.
///
@@ -284,7 +293,7 @@ protected void ChannelStateChanged(TcpChannelState state, ServiceResult reason)
///
protected uint GetNewSequenceNumber()
{
- bool isLegacy = !EccUtils.IsEccPolicy(SecurityPolicyUri);
+ bool isLegacy = SecurityPolicy.LegacySequenceNumbers;
long newSeqNumber = Interlocked.Increment(ref m_sequenceNumber);
bool maxValueOverflow = isLegacy
@@ -333,8 +342,8 @@ protected bool VerifySequenceNumber(uint sequenceNumber, string context)
// Accept the first sequence number depending on security policy
if (m_firstReceivedSequenceNumber &&
(
- !EccUtils.IsEccPolicy(SecurityPolicyUri) ||
- (EccUtils.IsEccPolicy(SecurityPolicyUri) && (sequenceNumber == 0))))
+ !CryptoUtils.IsEccPolicy(SecurityPolicyUri) ||
+ (CryptoUtils.IsEccPolicy(SecurityPolicyUri) && (sequenceNumber == 0))))
{
m_remoteSequenceNumber = sequenceNumber;
m_firstReceivedSequenceNumber = false;
@@ -355,8 +364,8 @@ protected bool VerifySequenceNumber(uint sequenceNumber, string context)
// only one rollover per token is allowed and with valid values depending on security policy
if (!m_sequenceRollover &&
(
- !EccUtils.IsEccPolicy(SecurityPolicyUri) ||
- (EccUtils.IsEccPolicy(SecurityPolicyUri) && (sequenceNumber == 0))))
+ !CryptoUtils.IsEccPolicy(SecurityPolicyUri) ||
+ (CryptoUtils.IsEccPolicy(SecurityPolicyUri) && (sequenceNumber == 0))))
{
m_sequenceRollover = true;
m_remoteSequenceNumber = sequenceNumber;
@@ -630,6 +639,8 @@ protected void BeginWriteMessage(BufferCollection buffers, object state)
try
{
+ // m_logger.LogWarning("OUT:{Id}", TcpMessageType.GetTypeAndSize(buffers[0]));
+
Interlocked.Increment(ref m_activeWriteRequests);
args.BufferList = buffers;
args.Completed += OnWriteComplete;
diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs
index f97a51b9bc..c0e1128632 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs
@@ -34,6 +34,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
+using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
@@ -551,6 +552,11 @@ private void SendOpenSecureChannelRequest(bool renew)
ChannelToken token = CreateToken();
token.ClientNonce = CreateNonce(ClientCertificate);
+ if (renew)
+ {
+ token.PreviousSecret = CurrentToken?.Secret;
+ }
+
// construct the request.
var request = new OpenSecureChannelRequest();
request.RequestHeader.Timestamp = DateTime.UtcNow;
@@ -565,6 +571,12 @@ private void SendOpenSecureChannelRequest(bool renew)
// encode the request.
byte[] buffer = BinaryEncoder.EncodeMessage(request, Quotas.MessageContext);
+ ClientChannelCertificate = ClientCertificate?.RawData;
+ ServerChannelCertificate = ServerCertificate?.RawData;
+
+ m_oscRequestSignature = null;
+ byte[] signature;
+
// write the asymmetric message.
BufferCollection? chunksToSend = WriteAsymmetricMessage(
TcpMessageType.Open,
@@ -572,7 +584,18 @@ private void SendOpenSecureChannelRequest(bool renew)
ClientCertificate,
ClientCertificateChain,
ServerCertificate,
- new ArraySegment(buffer, 0, buffer.Length));
+ new ArraySegment(buffer, 0, buffer.Length),
+ m_oscRequestSignature,
+ out signature);
+
+ // don't keep signature if secure channel enhancements are not used.
+ m_oscRequestSignature = (SecurityPolicy.SecureChannelEnhancements) ? signature : null;
+
+ CryptoTrace.Start(ConsoleColor.Magenta, $"SendOpenSecureChannelRequest ({(renew ? "RENEW" : "OPEN")})");
+ CryptoTrace.WriteLine($"ClientCertificate={ClientCertificate?.Thumbprint}");
+ CryptoTrace.WriteLine($"ServerCertificate={ServerCertificate?.Thumbprint}");
+ CryptoTrace.WriteLine($"RequestSignature={CryptoTrace.KeyToString(signature)}");
+ CryptoTrace.Finish("SendOpenSecureChannelRequest");
// save token.
m_requestedToken = token;
@@ -627,13 +650,32 @@ private bool ProcessOpenSecureChannelResponse(
uint sequenceNumber;
try
{
+ byte[] signature;
+
messageBody = ReadAsymmetricMessage(
messageChunk,
ClientCertificate,
out channelId,
out serverCertificate,
out requestId,
- out sequenceNumber);
+ out sequenceNumber,
+ (State == TcpChannelState.Opening) ? m_oscRequestSignature : null,
+ out signature);
+
+ if (State == TcpChannelState.Opening)
+ {
+ ChannelThumbprint = signature;
+ }
+
+ CryptoTrace.Start(ConsoleColor.Magenta, $"ProcessOpenSecureChannelResponse ({(State != TcpChannelState.Opening ? "RENEW" : "OPEN")})");
+ CryptoTrace.WriteLine($"messageBody={CryptoTrace.KeyToString(messageBody)}");
+ CryptoTrace.WriteLine($"messageBody.Offset={messageBody.Offset}");
+ CryptoTrace.WriteLine($"ClientCertificate={ClientCertificate?.Thumbprint}");
+ CryptoTrace.WriteLine($"ServerCertificate={ServerCertificate?.Thumbprint}");
+ CryptoTrace.WriteLine($"RequestSignature={CryptoTrace.KeyToString(m_oscRequestSignature)}");
+ CryptoTrace.WriteLine($"ResponseSignature={CryptoTrace.KeyToString(signature)}");
+ CryptoTrace.WriteLine($"ChannelThumbprint={CryptoTrace.KeyToString(ChannelThumbprint)}");
+ CryptoTrace.Finish("ProcessOpenSecureChannelResponse");
}
catch (Exception e)
{
@@ -1753,5 +1795,6 @@ private bool ProcessResponseMessage(uint messageType, ArraySegment message
private List? m_queuedOperations;
private readonly ILogger m_logger;
private readonly ITelemetryContext m_telemetry;
+ private byte[]? m_oscRequestSignature;
}
}
diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryTransportChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryTransportChannel.cs
index c3658c7230..4ee76828fb 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryTransportChannel.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryTransportChannel.cs
@@ -121,7 +121,16 @@ public IServiceMessageContext MessageContext
=> m_quotas?.MessageContext ?? throw BadNotConnected();
///
- public ChannelToken? CurrentToken => m_channel?.CurrentToken;
+ public ChannelToken CurrentToken => m_channel?.CurrentToken ?? new();
+
+ ///
+ public byte[] ChannelThumbprint => m_channel?.ChannelThumbprint ?? [];
+
+ ///
+ public byte[] ClientChannelCertificate => m_channel?.ClientChannelCertificate ?? [];
+
+ ///
+ public byte[] ServerChannelCertificate => m_channel?.ServerChannelCertificate ?? [];
///
public int OperationTimeout { get; set; }
@@ -445,9 +454,11 @@ private UaSCUaBinaryClientChannel CreateChannel(
}
}
+ var id = Guid.NewGuid().ToString();
+
// create the channel.
var channel = new UaSCUaBinaryClientChannel(
- Guid.NewGuid().ToString(),
+ id,
m_bufferManager,
m_messageSocketFactory,
m_quotas,
diff --git a/Stack/Opc.Ua.Core/Stack/Transport/ITransportChannel.cs b/Stack/Opc.Ua.Core/Stack/Transport/ITransportChannel.cs
index 23ce1b64df..7de67cf0f1 100644
--- a/Stack/Opc.Ua.Core/Stack/Transport/ITransportChannel.cs
+++ b/Stack/Opc.Ua.Core/Stack/Transport/ITransportChannel.cs
@@ -116,6 +116,21 @@ public interface ITransportChannel : IDisposable
///
EndpointConfiguration EndpointConfiguration { get; }
+ ///
+ /// The unique identifier for the secure channel.
+ ///
+ byte[] ChannelThumbprint { get; }
+
+ ///
+ /// The client certificate used to establsih the secure channel.
+ ///
+ byte[] ClientChannelCertificate { get; }
+
+ ///
+ /// The server certificate used to establsih the secure channel.
+ ///
+ byte[] ServerChannelCertificate { get; }
+
///
/// Gets the context used when serializing messages exchanged
/// via the channel. Throws if the channel is not yet opened.
diff --git a/Stack/Opc.Ua.Core/Stack/Transport/NullChannel.cs b/Stack/Opc.Ua.Core/Stack/Transport/NullChannel.cs
index f0c728bebd..b6787e56b5 100644
--- a/Stack/Opc.Ua.Core/Stack/Transport/NullChannel.cs
+++ b/Stack/Opc.Ua.Core/Stack/Transport/NullChannel.cs
@@ -41,6 +41,10 @@ namespace Opc.Ua
///
internal sealed class NullChannel : ITransportChannel, ISecureChannel
{
+ ///
+ public ChannelToken CurrentToken
+ => throw Unexpected(nameof(CurrentToken));
+
///
public TransportChannelFeatures SupportedFeatures
=> throw Unexpected(nameof(SupportedFeatures));
@@ -58,8 +62,16 @@ public IServiceMessageContext MessageContext
=> throw Unexpected(nameof(MessageContext));
///
- public ChannelToken CurrentToken
- => throw Unexpected(nameof(CurrentToken));
+ public byte[] ChannelThumbprint
+ => throw Unexpected(nameof(ChannelThumbprint));
+
+ ///
+ public byte[] ClientChannelCertificate
+ => throw Unexpected(nameof(ClientChannelCertificate));
+
+ ///
+ public byte[] ServerChannelCertificate
+ => throw Unexpected(nameof(ServerChannelCertificate));
///
public int OperationTimeout { get; set; }
diff --git a/Stack/Opc.Ua.Core/Stack/Types/UserNameIdentityTokenHandler.cs b/Stack/Opc.Ua.Core/Stack/Types/UserNameIdentityTokenHandler.cs
index 95363b6b1b..5c5972a1df 100644
--- a/Stack/Opc.Ua.Core/Stack/Types/UserNameIdentityTokenHandler.cs
+++ b/Stack/Opc.Ua.Core/Stack/Types/UserNameIdentityTokenHandler.cs
@@ -114,7 +114,9 @@ public void Encrypt(
}
// handle RSA encryption.
- if (!EccUtils.IsEccPolicy(securityPolicyUri))
+ var securityPolicy = SecurityPolicies.GetInfo(securityPolicyUri);
+
+ if (securityPolicy.EphemeralKeyAlgorithm == CertificateKeyAlgorithm.None)
{
byte[] dataToEncrypt = Utils.Append(DecryptedPassword, receiverNonce);
@@ -129,7 +131,8 @@ public void Encrypt(
m_token.EncryptionAlgorithm = encryptedData.Algorithm;
Array.Clear(dataToEncrypt, 0, dataToEncrypt.Length);
}
- // handle ECC encryption.
+
+ // handle ECC and RSADH encryption.
else
{
// check if the complete chain is included in the sender issuers.
@@ -154,7 +157,7 @@ public void Encrypt(
receiverCertificate,
receiverEphemeralKey,
senderCertificate,
- Nonce.CreateNonce(securityPolicyUri),
+ Nonce.CreateNonce(securityPolicy),
null,
doNotEncodeSenderCertificate);
@@ -190,7 +193,9 @@ public void Decrypt(
}
// handle RSA encryption.
- if (!EccUtils.IsEccPolicy(securityPolicyUri))
+ var securityPolicy = SecurityPolicies.GetInfo(securityPolicyUri);
+
+ if (securityPolicy.EphemeralKeyAlgorithm == CertificateKeyAlgorithm.None)
{
var encryptedData = new EncryptedData
{
@@ -234,7 +239,8 @@ public void Decrypt(
Array.Copy(decryptedPassword, DecryptedPassword, startOfNonce);
Array.Clear(decryptedPassword, 0, decryptedPassword.Length);
}
- // handle ECC encryption.
+
+ // handle ECC and RSADH encryption.
else
{
var secret = new EncryptedSecret(
diff --git a/Stack/Opc.Ua.Core/Stack/Types/X509IdentityTokenHandler.cs b/Stack/Opc.Ua.Core/Stack/Types/X509IdentityTokenHandler.cs
index 45817b6cc8..a72bcd00c4 100644
--- a/Stack/Opc.Ua.Core/Stack/Types/X509IdentityTokenHandler.cs
+++ b/Stack/Opc.Ua.Core/Stack/Types/X509IdentityTokenHandler.cs
@@ -43,6 +43,13 @@ public sealed class X509IdentityTokenHandler : IUserIdentityTokenHandler
public X509IdentityTokenHandler(X509IdentityToken token)
{
m_token = token;
+
+ if (m_token.CertificateData != null)
+ {
+ m_certificate = CertificateFactory.Create(m_token.CertificateData);
+ }
+
+ m_ownsCertificate = true;
}
///
@@ -66,12 +73,29 @@ public X509IdentityTokenHandler(X509Certificate2 certificate)
}
Certificate = certificate;
+ m_ownsCertificate = true;
m_token = new X509IdentityToken
{
CertificateData = certificate.RawData
};
}
+ ///
+ /// Private constructor for . The cloned handler
+ /// shares the certificate reference but does not own it, so it will
+ /// not dispose the certificate. This is necessary because the
+ /// certificate's private key may reside in protected storage and
+ /// cannot be deep-copied.
+ ///
+ private X509IdentityTokenHandler(
+ X509IdentityToken token,
+ X509Certificate2 certificate)
+ {
+ m_token = token;
+ m_certificate = certificate;
+ m_ownsCertificate = false;
+ }
+
///
/// The certificate associated with the token.
///
@@ -134,16 +158,13 @@ public SignatureData Sign(
byte[] dataToSign,
string securityPolicyUri)
{
- X509Certificate2 certificate = Certificate ??
- CertificateFactory.Create(m_token.CertificateData);
+ var info = SecurityPolicies.GetInfo(securityPolicyUri);
- SignatureData signatureData = SecurityPolicies.Sign(
- certificate,
- securityPolicyUri,
+ var signatureData = SecurityPolicies.CreateSignatureData(
+ info,
+ m_certificate,
dataToSign);
- m_token.CertificateData = certificate.RawData;
-
return signatureData;
}
@@ -155,16 +176,13 @@ public bool Verify(
{
try
{
- X509Certificate2 certificate = Certificate ??
- CertificateFactory.Create(m_token.CertificateData);
-
- bool valid = SecurityPolicies.Verify(
- certificate,
- securityPolicyUri,
- dataToVerify,
- signatureData);
+ var info = SecurityPolicies.GetInfo(securityPolicyUri);
- m_token.CertificateData = certificate.RawData;
+ bool valid = SecurityPolicies.VerifySignatureData(
+ signatureData,
+ info,
+ m_certificate,
+ dataToVerify);
return valid;
}
@@ -180,17 +198,19 @@ public bool Verify(
///
public void Dispose()
{
- // TODOL Utils.SilentDispose(m_certificate);
+ if (m_ownsCertificate)
+ {
+ Utils.SilentDispose(m_certificate);
+ }
m_certificate = null;
}
///
public object Clone()
{
- return new X509IdentityTokenHandler(Utils.Clone(m_token))
- {
- // TODO: m_certificate = m_certificate
- };
+ return new X509IdentityTokenHandler(
+ Utils.Clone(m_token),
+ m_certificate);
}
///
@@ -204,6 +224,7 @@ public bool Equals(IUserIdentityTokenHandler other)
}
private readonly X509IdentityToken m_token;
+ private readonly bool m_ownsCertificate;
private X509Certificate2 m_certificate;
}
}
diff --git a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs
index c3c653fbf6..9cbc24cf79 100644
--- a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs
+++ b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs
@@ -666,7 +666,14 @@ public static IPAddress[] GetHostAddresses(string hostNameOrAddress)
/// If the platform returns a FQDN, only the host name is returned.
public static string GetHostName()
{
- return Dns.GetHostName().Split('.')[0].ToLowerInvariant();
+ var hostName = Dns.GetHostName();
+ // If platform returns an IPv4 or IPv6 address return it as is
+ if (IPAddress.TryParse(hostName, out _))
+ {
+ return hostName;
+ }
+
+ return hostName.Split('.')[0].ToLowerInvariant();
}
///
diff --git a/Stack/Opc.Ua.Types/Diagnostics/TelemetryUtils.cs b/Stack/Opc.Ua.Types/Diagnostics/TelemetryUtils.cs
index 905cbd9bd0..2c034f926a 100644
--- a/Stack/Opc.Ua.Types/Diagnostics/TelemetryUtils.cs
+++ b/Stack/Opc.Ua.Types/Diagnostics/TelemetryUtils.cs
@@ -141,7 +141,7 @@ public void Dispose()
[Conditional("CHECKED")]
private static void DebugCheck()
{
- Debug.Fail("Using a NullLogger");
+ //Debug.Fail("Using a NullLogger");
}
}
diff --git a/Stack/Opc.Ua.Types/Encoders/BinaryDecoder.cs b/Stack/Opc.Ua.Types/Encoders/BinaryDecoder.cs
index c6663d3c13..3ec98875fc 100644
--- a/Stack/Opc.Ua.Types/Encoders/BinaryDecoder.cs
+++ b/Stack/Opc.Ua.Types/Encoders/BinaryDecoder.cs
@@ -245,7 +245,8 @@ public IEncodeable DecodeMessage(Type expectedType)
// lookup message type.
Type actualType =
Context.Factory.GetSystemType(absoluteId)
- ?? throw ServiceResultException.Create(
+ ??
+ throw ServiceResultException.Create(
StatusCodes.BadDecodingError,
"Cannot decode message with type id: {0}.",
absoluteId);
diff --git a/Tests/Opc.Ua.Client.Tests/ClientTest.cs b/Tests/Opc.Ua.Client.Tests/ClientTest.cs
index 815c63a970..2544345e32 100644
--- a/Tests/Opc.Ua.Client.Tests/ClientTest.cs
+++ b/Tests/Opc.Ua.Client.Tests/ClientTest.cs
@@ -79,6 +79,31 @@ public ClientTest(string uriScheme = Utils.UriSchemeOpcTcp)
ObjectIds.XmlSchema_TypeSystem
];
+ public static readonly string[] SupportedEccPolicies =
+ [
+ .. GetSupportedEccPolicyUris(includeCurvePolicies: false)
+ ];
+
+ public static readonly string[] SupportedEccX509Policies =
+ [
+ .. SupportedEccPolicies.Where(policyUri =>
+ {
+ CertificateKeyAlgorithm certificateKeyAlgorithm =
+ SecurityPolicies.GetInfo(policyUri).CertificateKeyAlgorithm;
+ return certificateKeyAlgorithm != CertificateKeyAlgorithm.Curve25519 &&
+ certificateKeyAlgorithm != CertificateKeyAlgorithm.Curve448;
+ })
+ ];
+
+ public static IEnumerable ReconnectSessionOnAlternateChannelWithSavedSessionSecretsEccTestCases()
+ {
+ foreach (string securityPolicy in SupportedEccPolicies)
+ {
+ yield return new TestCaseData(securityPolicy, true);
+ yield return new TestCaseData(securityPolicy, false);
+ }
+ }
+
///
/// Set up a Server and a Client instance.
///
@@ -841,18 +866,17 @@ await session1.ReadValueAsync(
[TestCase(SecurityPolicies.None, false)]
[TestCase(SecurityPolicies.Basic256Sha256, true)]
[TestCase(SecurityPolicies.Basic256Sha256, false)]
- [TestCase(SecurityPolicies.ECC_brainpoolP256r1, true)]
- [TestCase(SecurityPolicies.ECC_brainpoolP256r1, false)]
- [TestCase(SecurityPolicies.ECC_brainpoolP384r1, true)]
- [TestCase(SecurityPolicies.ECC_brainpoolP384r1, false)]
- [TestCase(SecurityPolicies.ECC_nistP256, true)]
- [TestCase(SecurityPolicies.ECC_nistP256, false)]
- [TestCase(SecurityPolicies.ECC_nistP384, true)]
- [TestCase(SecurityPolicies.ECC_nistP384, false)]
+ [TestCase(SecurityPolicies.RSA_DH_AesGcm, true)]
+ [TestCase(SecurityPolicies.RSA_DH_AesGcm, false)]
+ [TestCase(SecurityPolicies.RSA_DH_ChaChaPoly, true)]
+ [TestCase(SecurityPolicies.RSA_DH_ChaChaPoly, false)]
+ [TestCaseSource(nameof(ReconnectSessionOnAlternateChannelWithSavedSessionSecretsEccTestCases))]
public async Task ReconnectSessionOnAlternateChannelWithSavedSessionSecretsAsync(
string securityPolicy,
bool anonymous)
{
+ await IgnoreIfPolicyNotAdvertisedAsync(securityPolicy).ConfigureAwait(false);
+
ServiceResultException sre;
UserIdentity userIdentity = anonymous
@@ -1830,46 +1854,39 @@ public async Task ReadBuildInfoAsync()
[Combinatorial]
[Order(10100)]
public async Task OpenSessionECCUserNamePwdIdentityTokenAsync(
- [Values(
- SecurityPolicies.ECC_nistP256,
- SecurityPolicies.ECC_nistP384,
- SecurityPolicies.ECC_brainpoolP256r1,
- SecurityPolicies.ECC_brainpoolP384r1
- )] string securityPolicy)
- {
- if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ||
- (securityPolicy != SecurityPolicies.ECC_brainpoolP256r1 &&
- securityPolicy != SecurityPolicies.ECC_brainpoolP384r1))
- {
- var userIdentity = new UserIdentity("user1", "password"u8);
-
- // the first channel determines the endpoint
- ConfiguredEndpoint endpoint = await ClientFixture
- .GetEndpointAsync(ServerUrl, securityPolicy, Endpoints)
- .ConfigureAwait(false);
- Assert.NotNull(endpoint);
+ [ValueSource(nameof(SupportedEccPolicies))] string securityPolicy)
+ {
+ IgnoreUnsupportedBrainpoolOnMacOs(securityPolicy);
+ await IgnoreIfPolicyNotAdvertisedAsync(securityPolicy).ConfigureAwait(false);
- UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(
- userIdentity.TokenType,
- userIdentity.IssuedTokenType,
- endpoint.Description.SecurityPolicyUri);
- if (identityPolicy == null)
- {
- NUnit.Framework.Assert.Ignore(
- $"No UserTokenPolicy found for {userIdentity.TokenType}" +
- $" / {userIdentity.IssuedTokenType}");
- }
+ var userIdentity = new UserIdentity("user1", "password"u8);
- // the active channel
- ISession session1 = await ClientFixture.ConnectAsync(endpoint, userIdentity)
- .ConfigureAwait(false);
- Assert.NotNull(session1);
+ // the first channel determines the endpoint
+ ConfiguredEndpoint endpoint = await ClientFixture
+ .GetEndpointAsync(ServerUrl, securityPolicy, Endpoints)
+ .ConfigureAwait(false);
+ Assert.NotNull(endpoint);
- ServerStatusDataType value1 =
- await session1.ReadValueAsync(
- VariableIds.Server_ServerStatus).ConfigureAwait(false);
- Assert.NotNull(value1);
+ UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(
+ userIdentity.TokenType,
+ userIdentity.IssuedTokenType,
+ endpoint.Description.SecurityPolicyUri);
+ if (identityPolicy == null)
+ {
+ NUnit.Framework.Assert.Ignore(
+ $"No UserTokenPolicy found for {userIdentity.TokenType}" +
+ $" / {userIdentity.IssuedTokenType}");
}
+
+ // the active channel
+ ISession session1 = await ClientFixture.ConnectAsync(endpoint, userIdentity)
+ .ConfigureAwait(false);
+ Assert.NotNull(session1);
+
+ ServerStatusDataType value1 =
+ await session1.ReadValueAsync(
+ VariableIds.Server_ServerStatus).ConfigureAwait(false);
+ Assert.NotNull(value1);
}
///
@@ -1879,51 +1896,44 @@ await session1.ReadValueAsync(
[Combinatorial]
[Order(10200)]
public async Task OpenSessionECCIssuedIdentityTokenAsync(
- [Values(
- SecurityPolicies.ECC_nistP256,
- SecurityPolicies.ECC_nistP384,
- SecurityPolicies.ECC_brainpoolP256r1,
- SecurityPolicies.ECC_brainpoolP384r1
- )] string securityPolicy)
- {
- if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ||
- (securityPolicy != SecurityPolicies.ECC_brainpoolP256r1 &&
- securityPolicy != SecurityPolicies.ECC_brainpoolP384r1))
- {
- const string identityToken = "fakeTokenString";
-
- using var issuedToken = new IssuedIdentityTokenHandler(
- Profiles.JwtUserToken,
- Encoding.UTF8.GetBytes(identityToken));
- var userIdentity = new UserIdentity(issuedToken);
-
- // the first channel determines the endpoint
- ConfiguredEndpoint endpoint = await ClientFixture
- .GetEndpointAsync(ServerUrl, securityPolicy, Endpoints)
- .ConfigureAwait(false);
- Assert.NotNull(endpoint);
+ [ValueSource(nameof(SupportedEccPolicies))] string securityPolicy)
+ {
+ IgnoreUnsupportedBrainpoolOnMacOs(securityPolicy);
+ await IgnoreIfPolicyNotAdvertisedAsync(securityPolicy).ConfigureAwait(false);
- UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(
- userIdentity.TokenType,
- userIdentity.IssuedTokenType,
- securityPolicy);
+ const string identityToken = "fakeTokenString";
- if (identityPolicy == null)
- {
- NUnit.Framework.Assert.Ignore(
- $"No UserTokenPolicy found for {userIdentity.TokenType}" +
- $" / {userIdentity.IssuedTokenType}");
- }
+ using var issuedToken = new IssuedIdentityTokenHandler(
+ Profiles.JwtUserToken,
+ Encoding.UTF8.GetBytes(identityToken));
+ var userIdentity = new UserIdentity(issuedToken);
- // the active channel
- ISession session1 = await ClientFixture.ConnectAsync(endpoint, userIdentity)
- .ConfigureAwait(false);
- Assert.NotNull(session1);
+ // the first channel determines the endpoint
+ ConfiguredEndpoint endpoint = await ClientFixture
+ .GetEndpointAsync(ServerUrl, securityPolicy, Endpoints)
+ .ConfigureAwait(false);
+ Assert.NotNull(endpoint);
- ServerStatusDataType value1 = await session1.ReadValueAsync(
- VariableIds.Server_ServerStatus).ConfigureAwait(false);
- Assert.NotNull(value1);
+ UserTokenPolicy identityPolicy = endpoint.Description.FindUserTokenPolicy(
+ userIdentity.TokenType,
+ userIdentity.IssuedTokenType,
+ securityPolicy);
+
+ if (identityPolicy == null)
+ {
+ NUnit.Framework.Assert.Ignore(
+ $"No UserTokenPolicy found for {userIdentity.TokenType}" +
+ $" / {userIdentity.IssuedTokenType}");
}
+
+ // the active channel
+ ISession session1 = await ClientFixture.ConnectAsync(endpoint, userIdentity)
+ .ConfigureAwait(false);
+ Assert.NotNull(session1);
+
+ ServerStatusDataType value1 = await session1.ReadValueAsync(
+ VariableIds.Server_ServerStatus).ConfigureAwait(false);
+ Assert.NotNull(value1);
}
///
@@ -1933,14 +1943,11 @@ public async Task OpenSessionECCIssuedIdentityTokenAsync(
[Combinatorial]
[Order(10300)]
public async Task OpenSessionECCUserCertIdentityTokenAsync(
- [Values(
- SecurityPolicies.ECC_nistP256,
- SecurityPolicies.ECC_nistP384,
- SecurityPolicies.ECC_brainpoolP256r1,
- SecurityPolicies.ECC_brainpoolP384r1
- )]
- string securityPolicy)
+ [ValueSource(nameof(SupportedEccX509Policies))] string securityPolicy)
{
+ IgnoreUnsupportedBrainpoolOnMacOs(securityPolicy);
+ await IgnoreIfPolicyNotAdvertisedAsync(securityPolicy).ConfigureAwait(false);
+
var eccCurveHashPairs = new ECCurveHashPairCollection
{
{ ECCurve.NamedCurves.nistP256, HashAlgorithmName.SHA256 },
@@ -2326,5 +2333,15 @@ private static void ValidateOperationLimit(uint serverLimit, uint clientLimit)
Assert.NotZero(clientLimit);
}
}
+
+ private static void IgnoreUnsupportedBrainpoolOnMacOs(string securityPolicyUri)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) &&
+ (securityPolicyUri.Contains("ECC_brainpoolP256r1", StringComparison.Ordinal) ||
+ securityPolicyUri.Contains("ECC_brainpoolP384r1", StringComparison.Ordinal)))
+ {
+ NUnit.Framework.Assert.Ignore("Brainpool curve is not supported on Mac OS.");
+ }
+ }
}
}
diff --git a/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs b/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs
index 4fc2683f9e..271841716d 100644
--- a/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs
+++ b/Tests/Opc.Ua.Client.Tests/ClientTestFramework.cs
@@ -105,9 +105,61 @@ public void InitializeSession(ISession session)
[DatapointSource]
public static readonly string[] Policies =
[
- .. SecurityPolicies.GetDisplayNames().Select(SecurityPolicies.GetUri)
+ .. GetPolicyUrisForTests()
];
+ protected static IEnumerable GetSupportedEccPolicyUris(
+ bool includeCurvePolicies = true)
+ {
+ IEnumerable displayNames = SecurityPolicies.GetDisplayNames()
+ .Where(name => name.StartsWith("ECC_", StringComparison.Ordinal));
+
+ if (!includeCurvePolicies)
+ {
+ displayNames = displayNames
+ .Where(name => !name.StartsWith("ECC_curve", StringComparison.Ordinal));
+ }
+
+ return displayNames.Select(SecurityPolicies.GetUri);
+ }
+
+ private static IEnumerable GetPolicyUrisForTests()
+ {
+ IEnumerable displayNames = SecurityPolicies.GetDisplayNames();
+
+#if NETFRAMEWORK
+ displayNames = displayNames.Where(name =>
+ !name.EndsWith("_AesGcm", StringComparison.Ordinal) &&
+ !name.EndsWith("_ChaChaPoly", StringComparison.Ordinal));
+#endif
+
+ return displayNames.Select(SecurityPolicies.GetUri);
+ }
+
+ protected async Task IgnoreIfPolicyNotAdvertisedAsync(string securityPolicyUri)
+ {
+ Endpoints ??= await ClientFixture.GetEndpointsAsync(ServerUrl).ConfigureAwait(false);
+ if (Endpoints?.Any(endpoint =>
+ string.Equals(
+ endpoint.SecurityPolicyUri,
+ securityPolicyUri,
+ StringComparison.Ordinal)) != true)
+ {
+ string advertisedPolicies = Endpoints == null
+ ? ""
+ : string.Join(
+ ", ",
+ Endpoints
+ .Select(endpoint => endpoint.SecurityPolicyUri)
+ .Where(policy => !string.IsNullOrEmpty(policy))
+ .Distinct()
+ .OrderBy(policy => policy, StringComparer.Ordinal));
+ NUnit.Framework.Assert.Ignore(
+ $"SecurityPolicy '{securityPolicyUri}' is not advertised by the server. " +
+ $"Advertised: {advertisedPolicies}");
+ }
+ }
+
///
/// Set up a Server and a Client instance.
///
@@ -240,82 +292,28 @@ public virtual async Task CreateReferenceServerFixtureAsync(
IssuedTokenType = Profiles.JwtUserToken
});
- ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(
- new UserTokenPolicy(UserTokenType.UserName)
- {
- SecurityPolicyUri
- = "http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP256r1"
- });
- ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(
- new UserTokenPolicy(UserTokenType.UserName)
- {
- SecurityPolicyUri
- = "http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP384r1"
- });
- ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(
- new UserTokenPolicy(UserTokenType.UserName)
- {
- SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP256"
- });
- ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(
- new UserTokenPolicy(UserTokenType.UserName)
- {
- SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP384"
- });
+ foreach (string securityPolicyUri in GetSupportedEccPolicyUris())
+ {
+ ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(
+ new UserTokenPolicy(UserTokenType.UserName)
+ {
+ SecurityPolicyUri = securityPolicyUri
+ });
- ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(
- new UserTokenPolicy(UserTokenType.Certificate)
- {
- SecurityPolicyUri
- = "http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP256r1"
- });
- ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(
- new UserTokenPolicy(UserTokenType.Certificate)
- {
- SecurityPolicyUri
- = "http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP384r1"
- });
- ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(
- new UserTokenPolicy(UserTokenType.Certificate)
- {
- SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP256"
- });
- ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(
- new UserTokenPolicy(UserTokenType.Certificate)
- {
- SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP384"
- });
+ ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(
+ new UserTokenPolicy(UserTokenType.Certificate)
+ {
+ SecurityPolicyUri = securityPolicyUri
+ });
- ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(
- new UserTokenPolicy(UserTokenType.IssuedToken)
- {
- IssuedTokenType = Profiles.JwtUserToken,
- PolicyId = Profiles.JwtUserToken,
- SecurityPolicyUri
- = "http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP256r1"
- });
- ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(
- new UserTokenPolicy(UserTokenType.IssuedToken)
- {
- IssuedTokenType = Profiles.JwtUserToken,
- PolicyId = Profiles.JwtUserToken,
- SecurityPolicyUri
- = "http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP384r1"
- });
- ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(
- new UserTokenPolicy(UserTokenType.IssuedToken)
- {
- IssuedTokenType = Profiles.JwtUserToken,
- PolicyId = Profiles.JwtUserToken,
- SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP256"
- });
- ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(
- new UserTokenPolicy(UserTokenType.IssuedToken)
- {
- IssuedTokenType = Profiles.JwtUserToken,
- PolicyId = Profiles.JwtUserToken,
- SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP384"
- });
+ ServerFixture.Config.ServerConfiguration.UserTokenPolicies.Add(
+ new UserTokenPolicy(UserTokenType.IssuedToken)
+ {
+ IssuedTokenType = Profiles.JwtUserToken,
+ PolicyId = Profiles.JwtUserToken,
+ SecurityPolicyUri = securityPolicyUri
+ });
+ }
ServerFixture.Config.ServerConfiguration.MaxChannelCount = MaxChannelCount;
ServerFixture.Config.ServerConfiguration.MaxSubscriptionCount = 1000;
diff --git a/Tests/Opc.Ua.Client.Tests/SecurityPolicyBenchmarks.cs b/Tests/Opc.Ua.Client.Tests/SecurityPolicyBenchmarks.cs
index 6681494424..c65b919481 100644
--- a/Tests/Opc.Ua.Client.Tests/SecurityPolicyBenchmarks.cs
+++ b/Tests/Opc.Ua.Client.Tests/SecurityPolicyBenchmarks.cs
@@ -155,13 +155,12 @@ public SecurityPolicyBenchmarks()
///
/// Override to exclude None policy from benchmarks to avoid CI test failures.
+ /// Uses the base policy list so target-specific filtering is preserved.
///
public new IEnumerable BenchPolicies()
{
- // Return all security policies except None
- foreach (string displayName in SecurityPolicies.GetDisplayNames())
+ foreach (string policyUri in Policies)
{
- string policyUri = SecurityPolicies.GetUri(displayName);
if (policyUri != SecurityPolicies.None)
{
yield return policyUri;
diff --git a/Tests/Opc.Ua.Client.Tests/SessionTests.cs b/Tests/Opc.Ua.Client.Tests/SessionTests.cs
index 703b983d25..c5c19ff9e5 100644
--- a/Tests/Opc.Ua.Client.Tests/SessionTests.cs
+++ b/Tests/Opc.Ua.Client.Tests/SessionTests.cs
@@ -1570,5 +1570,54 @@ public void SaveShouldOnlySaveSpecifiedSubscriptions()
Assert.That(loadedSubscriptions.Count, Is.EqualTo(2), "Only the specified subscriptions should be saved");
Assert.That(loadedSubscriptions.Select(s => s.DisplayName), Is.EquivalentTo(["Subscription1", "Subscription3"]));
}
+
+ [Test]
+ public void SaveAndApplySessionConfigurationShouldPersistClientNonce()
+ {
+ var source = SessionMock.Create();
+ source.SetConnected();
+
+ byte[] clientNonce = [1, 3, 5, 7, 9];
+ byte[] serverNonce = [2, 4, 6, 8];
+
+ SetPrivateByteArrayField(source, "m_clientNonce", clientNonce);
+ SetPrivateByteArrayField(source, "m_serverNonce", serverNonce);
+
+ SessionConfiguration configuration = source.SaveSessionConfiguration();
+
+ Assert.That(configuration.ClientNonce, Is.EquivalentTo(clientNonce));
+ Assert.That(configuration.ServerNonce, Is.EquivalentTo(serverNonce));
+
+ var target = SessionMock.Create();
+
+ bool success = target.ApplySessionConfiguration(configuration);
+
+ Assert.That(success, Is.True);
+ Assert.That(GetPrivateByteArrayField(target, "m_clientNonce"), Is.EquivalentTo(clientNonce));
+ Assert.That(GetPrivateByteArrayField(target, "m_serverNonce"), Is.EquivalentTo(serverNonce));
+ }
+
+ private static byte[] GetPrivateByteArrayField(Session session, string fieldName)
+ {
+ return (byte[])typeof(Session)
+ .GetField(
+ fieldName,
+ System.Reflection.BindingFlags.NonPublic |
+ System.Reflection.BindingFlags.Instance)?
+ .GetValue(session);
+ }
+
+ private static void SetPrivateByteArrayField(
+ Session session,
+ string fieldName,
+ byte[] value)
+ {
+ typeof(Session)
+ .GetField(
+ fieldName,
+ System.Reflection.BindingFlags.NonPublic |
+ System.Reflection.BindingFlags.Instance)?
+ .SetValue(session, value);
+ }
}
}
diff --git a/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs b/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs
index a543f99c9e..3d67660776 100644
--- a/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs
+++ b/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs
@@ -50,6 +50,11 @@ namespace Opc.Ua.Client.Tests
[SetUICulture("en-us")]
public class SubscriptionTest : ClientTestFramework
{
+ public static readonly string[] SupportedEccPolicies =
+ [
+ .. GetSupportedEccPolicyUris(includeCurvePolicies: false)
+ ];
+
private readonly string m_subscriptionTestXml = Path.Combine(
Path.GetTempPath(),
"SubscriptionTest.xml");
@@ -449,13 +454,7 @@ await Session.SetPublishingModeAsync(
[Order(351)]
[Explicit]
public Task ReconnectWithSavedSessionSecretsOnlyECCAsync(
- [Values(
- SecurityPolicies.ECC_nistP256,
- SecurityPolicies.ECC_nistP384,
- SecurityPolicies.ECC_brainpoolP256r1,
- SecurityPolicies.ECC_brainpoolP384r1
- )]
- string securityPolicy,
+ [ValueSource(nameof(SupportedEccPolicies))] string securityPolicy,
[Values(true, false)] bool anonymous,
[Values(true, false)] bool sequentialPublishing,
[Values(true, false)] bool sendInitialValues)
@@ -478,7 +477,9 @@ public Task ReconnectWithSavedSessionSecretsOnlyECCAsync(
public Task ReconnectWithSavedSessionSecretsOnlyAsync(
[Values(SecurityPolicies.None,
SecurityPolicies.ECC_nistP256,
- SecurityPolicies.Basic256Sha256)]
+ SecurityPolicies.Basic256Sha256,
+ SecurityPolicies.RSA_DH_AesGcm,
+ SecurityPolicies.RSA_DH_ChaChaPoly)]
string securityPolicy,
[Values(true, false)] bool anonymous,
[Values(true, false)] bool sequentialPublishing,
@@ -497,6 +498,8 @@ public async Task ReconnectWithSavedSessionSecretsAsync(
bool sequentialPublishing,
bool sendInitialValues)
{
+ await IgnoreIfPolicyNotAdvertisedAsync(securityPolicy).ConfigureAwait(false);
+
const int kTestSubscriptions = 5;
const int kDelay = 2_000;
const int kQueueSize = 10;
@@ -1629,5 +1632,6 @@ public async Task ConcurrentCreateItemsNoDuplicatesAsync()
// Clean up
await subscription.DeleteAsync(true, CancellationToken.None).ConfigureAwait(false);
}
+
}
}
diff --git a/Tests/Opc.Ua.Core.Tests/Stack/Types/X509IdentityTokenHandlerTests.cs b/Tests/Opc.Ua.Core.Tests/Stack/Types/X509IdentityTokenHandlerTests.cs
new file mode 100644
index 0000000000..7563695e09
--- /dev/null
+++ b/Tests/Opc.Ua.Core.Tests/Stack/Types/X509IdentityTokenHandlerTests.cs
@@ -0,0 +1,66 @@
+/* ========================================================================
+ * 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.Security.Cryptography.X509Certificates;
+using NUnit.Framework;
+using Opc.Ua.Security.Certificates;
+using Assert = NUnit.Framework.Legacy.ClassicAssert;
+
+namespace Opc.Ua.Core.Tests.Stack.Types
+{
+ [TestFixture]
+ [Category("X509IdentityTokenHandler")]
+ [SetCulture("en-us")]
+ [SetUICulture("en-us")]
+ [Parallelizable]
+ public class X509IdentityTokenHandlerTests
+ {
+ [Test]
+ public void CopyPreservesPrivateKeyForSigning()
+ {
+ using X509Certificate2 cert = CertificateBuilder
+ .Create("CN=User Identity Test Subject, O=OPC Foundation")
+ .SetRSAKeySize(2048)
+ .CreateForRSA();
+
+ using var tokenHandler = new X509IdentityTokenHandler(cert);
+ using X509IdentityTokenHandler copy = tokenHandler.Copy();
+
+ Assert.IsTrue(copy.Certificate.HasPrivateKey);
+
+ SignatureData signature = copy.Sign(
+ [0x01, 0x02, 0x03, 0x04],
+ SecurityPolicies.Basic256Sha256);
+
+ Assert.NotNull(signature);
+ Assert.NotNull(signature.Signature);
+ Assert.Greater(signature.Signature.Length, 0);
+ }
+ }
+}
diff --git a/Tests/Opc.Ua.Core.Tests/Types/Nonce/NonceTests.cs b/Tests/Opc.Ua.Core.Tests/Types/Nonce/NonceTests.cs
index 9517ceb487..f7534c9f6f 100644
--- a/Tests/Opc.Ua.Core.Tests/Types/Nonce/NonceTests.cs
+++ b/Tests/Opc.Ua.Core.Tests/Types/Nonce/NonceTests.cs
@@ -28,6 +28,8 @@
* ======================================================================*/
using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Runtime.InteropServices;
using NUnit.Framework;
using Assert = NUnit.Framework.Legacy.ClassicAssert;
@@ -42,23 +44,29 @@ namespace Opc.Ua.Core.Tests.Types.Nonce
[Parallelizable]
public class NonceTests
{
+ public static readonly string[] SupportedNoncePolicies =
+ [
+ .. SecurityPolicies.GetDisplayNames()
+ .Where(name => !name.Equals(nameof(SecurityPolicies.None), StringComparison.Ordinal))
+ .Select(SecurityPolicies.GetUri)
+ ];
+
+ private static readonly HashSet s_supportedPolicyUris =
+ [
+ .. SecurityPolicies.GetDisplayNames().Select(SecurityPolicies.GetUri)
+ ];
+
///
/// Test the CreateNonce - securitypolicy and valid nonceLength
///
[Theory]
- [TestCase(SecurityPolicies.ECC_nistP256)]
- [TestCase(SecurityPolicies.ECC_nistP384)]
- [TestCase(SecurityPolicies.ECC_brainpoolP256r1)]
- [TestCase(SecurityPolicies.ECC_brainpoolP384r1)]
- [TestCase(SecurityPolicies.Basic256)]
- [TestCase(SecurityPolicies.Basic256Sha256)]
- [TestCase(SecurityPolicies.Aes128_Sha256_RsaOaep)]
- [TestCase(SecurityPolicies.Aes256_Sha256_RsaPss)]
+ [TestCaseSource(nameof(SupportedNoncePolicies))]
public void ValidateCreateNoncePolicyLength(string securityPolicyUri)
{
if (IsSupportedByPlatform(securityPolicyUri))
{
- uint nonceLength = Ua.Nonce.GetNonceLength(securityPolicyUri);
+ var info = Ua.SecurityPolicies.GetInfo(securityPolicyUri);
+ var nonceLength = info.SecureChannelNonceLength;
var nonce = Ua.Nonce.CreateNonce(securityPolicyUri);
@@ -72,22 +80,16 @@ public void ValidateCreateNoncePolicyLength(string securityPolicyUri)
/// Test the CreateEccNonce - securitypolicy and nonceData
///
[Theory]
- [TestCase(SecurityPolicies.ECC_nistP256)]
- [TestCase(SecurityPolicies.ECC_nistP384)]
- [TestCase(SecurityPolicies.ECC_brainpoolP256r1)]
- [TestCase(SecurityPolicies.ECC_brainpoolP384r1)]
- [TestCase(SecurityPolicies.Basic256)]
- [TestCase(SecurityPolicies.Basic256Sha256)]
- [TestCase(SecurityPolicies.Aes128_Sha256_RsaOaep)]
- [TestCase(SecurityPolicies.Aes256_Sha256_RsaPss)]
+ [TestCaseSource(nameof(SupportedNoncePolicies))]
public void ValidateCreateNoncePolicyNonceData(string securityPolicyUri)
{
if (IsSupportedByPlatform(securityPolicyUri))
{
- uint nonceLength = Ua.Nonce.GetNonceLength(securityPolicyUri);
+ var info = Ua.SecurityPolicies.GetInfo(securityPolicyUri);
+ var nonceLength = info.SecureChannelNonceLength;
var nonceByLen = Ua.Nonce.CreateNonce(securityPolicyUri);
- var nonceByData = Ua.Nonce.CreateNonce(securityPolicyUri, nonceByLen.Data);
+ var nonceByData = Ua.Nonce.CreateNonce(info, nonceByLen.Data);
Assert.IsNotNull(nonceByData);
Assert.IsNotNull(nonceByData.Data);
@@ -100,39 +102,33 @@ public void ValidateCreateNoncePolicyNonceData(string securityPolicyUri)
/// Test the CreateEccNonce - securitypolicy and invalid nonceData
///
[Theory]
- [TestCase(SecurityPolicies.ECC_nistP256)]
- [TestCase(SecurityPolicies.ECC_nistP384)]
- [TestCase(SecurityPolicies.ECC_brainpoolP256r1)]
- [TestCase(SecurityPolicies.ECC_brainpoolP384r1)]
- [TestCase(SecurityPolicies.Basic256)]
- [TestCase(SecurityPolicies.Basic256Sha256)]
- [TestCase(SecurityPolicies.Aes128_Sha256_RsaOaep)]
- [TestCase(SecurityPolicies.Aes256_Sha256_RsaPss)]
+ [TestCaseSource(nameof(SupportedNoncePolicies))]
public void ValidateCreateEccNoncePolicyInvalidNonceDataCorrectLength(
string securityPolicyUri)
{
if (IsSupportedByPlatform(securityPolicyUri))
{
- uint nonceLength = Ua.Nonce.GetNonceLength(securityPolicyUri);
+ var info = Ua.SecurityPolicies.GetInfo(securityPolicyUri);
+ var nonceLength = info.SecureChannelNonceLength;
byte[] randomValue = Ua.Nonce.CreateRandomNonceData(nonceLength);
- if (securityPolicyUri.Contains("ECC_", StringComparison.Ordinal))
+ if (info.CertificateKeyFamily == CertificateKeyFamily.ECC)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) &&
(
- securityPolicyUri == SecurityPolicies.ECC_nistP256 ||
- securityPolicyUri == SecurityPolicies.ECC_nistP384))
+ securityPolicyUri.Contains("ECC_nistP256", StringComparison.Ordinal) ||
+ securityPolicyUri.Contains("ECC_nistP384", StringComparison.Ordinal)))
{
NUnit.Framework.Assert
.Ignore("No exception is thrown on OSX with NIST curves");
}
NUnit.Framework.Assert.Throws(() =>
- Ua.Nonce.CreateNonce(securityPolicyUri, randomValue));
+ Ua.Nonce.CreateNonce(info, randomValue));
}
else
{
- var rsaNonce = Ua.Nonce.CreateNonce(securityPolicyUri, randomValue);
+ var rsaNonce = Ua.Nonce.CreateNonce(info, randomValue);
Assert.AreEqual(rsaNonce.Data, randomValue);
}
}
@@ -143,31 +139,11 @@ public void ValidateCreateEccNoncePolicyInvalidNonceDataCorrectLength(
///
private static bool IsSupportedByPlatform(string securityPolicyUri)
{
- if (securityPolicyUri.Equals(SecurityPolicies.ECC_nistP256, StringComparison.Ordinal))
- {
- return Utils.IsSupportedCertificateType(
- ObjectTypeIds.EccNistP256ApplicationCertificateType);
- }
- else if (securityPolicyUri.Equals(
- SecurityPolicies.ECC_nistP384,
- StringComparison.Ordinal))
- {
- return Utils.IsSupportedCertificateType(
- ObjectTypeIds.EccNistP384ApplicationCertificateType);
- }
- else if (securityPolicyUri.Equals(
- SecurityPolicies.ECC_brainpoolP256r1,
- StringComparison.Ordinal))
- {
- return Utils.IsSupportedCertificateType(
- ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType);
- }
- else if (securityPolicyUri.Equals(
- SecurityPolicies.ECC_brainpoolP384r1,
+ if (securityPolicyUri.StartsWith(
+ SecurityPolicies.BaseUri,
StringComparison.Ordinal))
{
- return Utils.IsSupportedCertificateType(
- ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType);
+ return s_supportedPolicyUris.Contains(securityPolicyUri);
}
return true;
diff --git a/Tests/Opc.Ua.Gds.Tests/Opc.Ua.GlobalDiscoveryTestServer.Config.xml b/Tests/Opc.Ua.Gds.Tests/Opc.Ua.GlobalDiscoveryTestServer.Config.xml
index a8e65fb8b9..ce7b462efd 100644
--- a/Tests/Opc.Ua.Gds.Tests/Opc.Ua.GlobalDiscoveryTestServer.Config.xml
+++ b/Tests/Opc.Ua.Gds.Tests/Opc.Ua.GlobalDiscoveryTestServer.Config.xml
@@ -90,18 +90,50 @@
SignAndEncrypt_3
http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP256
+
+ SignAndEncrypt_3
+ http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP256_AesGcm
+
+
+ SignAndEncrypt_3
+ http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP256_ChaChaPoly
+
SignAndEncrypt_3
http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP384
+
+ SignAndEncrypt_3
+ http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP384_AesGcm
+
+
+ SignAndEncrypt_3
+ http://opcfoundation.org/UA/SecurityPolicy#ECC_nistP384_ChaChaPoly
+
SignAndEncrypt_3
http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP256r1
+
+ SignAndEncrypt_3
+ http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP256r1_AesGcm
+
+
+ SignAndEncrypt_3
+ http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP256r1_ChaChaPoly
+
SignAndEncrypt_3
http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP384r1
+
+ SignAndEncrypt_3
+ http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP384r1_AesGcm
+
+
+ SignAndEncrypt_3
+ http://opcfoundation.org/UA/SecurityPolicy#ECC_brainpoolP384r1_ChaChaPoly
+
diff --git a/Tests/Opc.Ua.Gds.Tests/PushTest.cs b/Tests/Opc.Ua.Gds.Tests/PushTest.cs
index f19a1563fa..beb3f97960 100644
--- a/Tests/Opc.Ua.Gds.Tests/PushTest.cs
+++ b/Tests/Opc.Ua.Gds.Tests/PushTest.cs
@@ -28,6 +28,7 @@
* ======================================================================*/
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
@@ -57,6 +58,11 @@ namespace Opc.Ua.Gds.Tests
[NonParallelizable]
public class PushTest
{
+ private static readonly HashSet s_supportedPolicyUris =
+ [
+ .. SecurityPolicies.GetDisplayNames().Select(SecurityPolicies.GetUri)
+ ];
+
///
/// CertificateTypes to run the Test with.
/// For ECC types, the additional fourth element is the expected curve friendly name.
@@ -78,6 +84,20 @@ public class PushTest
ECCurve.NamedCurves.nistP256
},
new object[]
+ {
+ nameof(OpcUa.ObjectTypeIds.EccNistP256ApplicationCertificateType),
+ OpcUa.ObjectTypeIds.EccNistP256ApplicationCertificateType,
+ SecurityPolicies.ECC_nistP256_AesGcm,
+ ECCurve.NamedCurves.nistP256
+ },
+ new object[]
+ {
+ nameof(OpcUa.ObjectTypeIds.EccNistP256ApplicationCertificateType),
+ OpcUa.ObjectTypeIds.EccNistP256ApplicationCertificateType,
+ SecurityPolicies.ECC_nistP256_ChaChaPoly,
+ ECCurve.NamedCurves.nistP256
+ },
+ new object[]
{
nameof(OpcUa.ObjectTypeIds.EccNistP384ApplicationCertificateType),
OpcUa.ObjectTypeIds.EccNistP384ApplicationCertificateType,
@@ -85,6 +105,20 @@ public class PushTest
ECCurve.NamedCurves.nistP384
},
new object[]
+ {
+ nameof(OpcUa.ObjectTypeIds.EccNistP384ApplicationCertificateType),
+ OpcUa.ObjectTypeIds.EccNistP384ApplicationCertificateType,
+ SecurityPolicies.ECC_nistP384_AesGcm,
+ ECCurve.NamedCurves.nistP384
+ },
+ new object[]
+ {
+ nameof(OpcUa.ObjectTypeIds.EccNistP384ApplicationCertificateType),
+ OpcUa.ObjectTypeIds.EccNistP384ApplicationCertificateType,
+ SecurityPolicies.ECC_nistP384_ChaChaPoly,
+ ECCurve.NamedCurves.nistP384
+ },
+ new object[]
{
nameof(OpcUa.ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType),
OpcUa.ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType,
@@ -92,16 +126,50 @@ public class PushTest
ECCurve.NamedCurves.brainpoolP256r1
},
new object[]
+ {
+ nameof(OpcUa.ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType),
+ OpcUa.ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType,
+ SecurityPolicies.ECC_brainpoolP256r1_AesGcm,
+ ECCurve.NamedCurves.brainpoolP256r1
+ },
+ new object[]
+ {
+ nameof(OpcUa.ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType),
+ OpcUa.ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType,
+ SecurityPolicies.ECC_brainpoolP256r1_ChaChaPoly,
+ ECCurve.NamedCurves.brainpoolP256r1
+ },
+ new object[]
{
nameof(OpcUa.ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType),
OpcUa.ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType,
SecurityPolicies.ECC_brainpoolP384r1,
ECCurve.NamedCurves.brainpoolP384r1
+ },
+ new object[]
+ {
+ nameof(OpcUa.ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType),
+ OpcUa.ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType,
+ SecurityPolicies.ECC_brainpoolP384r1_AesGcm,
+ ECCurve.NamedCurves.brainpoolP384r1
+ },
+ new object[]
+ {
+ nameof(OpcUa.ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType),
+ OpcUa.ObjectTypeIds.EccBrainpoolP384r1ApplicationCertificateType,
+ SecurityPolicies.ECC_brainpoolP384r1_ChaChaPoly,
+ ECCurve.NamedCurves.brainpoolP384r1
}
];
public PushTest(string certificateTypeString, NodeId certificateType, string securityPolicyUri, ECCurve? curve)
{
+ if (!s_supportedPolicyUris.Contains(securityPolicyUri))
+ {
+ NUnit.Framework.Assert.Ignore(
+ $"Security policy {securityPolicyUri} is not supported on this runtime.");
+ }
+
if (!Utils.IsSupportedCertificateType(certificateType))
{
NUnit.Framework.Assert.Ignore(
@@ -110,7 +178,8 @@ public PushTest(string certificateTypeString, NodeId certificateType, string sec
// Skip brainpool curves on Mac OS
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) &&
- (securityPolicyUri == SecurityPolicies.ECC_brainpoolP256r1 || securityPolicyUri == SecurityPolicies.ECC_brainpoolP384r1))
+ (securityPolicyUri.Contains("ECC_brainpoolP256r1", StringComparison.Ordinal) ||
+ securityPolicyUri.Contains("ECC_brainpoolP384r1", StringComparison.Ordinal)))
{
NUnit.Framework.Assert.Ignore("Brainpool curve is not supported on Mac OS.");
}
@@ -179,8 +248,17 @@ await m_pushClient.LoadClientConfigurationAsync(m_server.BasePort)
await m_gdsClient.GDSClient.ConnectAsync(m_gdsClient.GDSClient.EndpointUrl)
.ConfigureAwait(false);
- await m_pushClient.ConnectAsync(m_securityPolicyUri)
- .ConfigureAwait(false);
+ try
+ {
+ await m_pushClient.ConnectAsync(m_securityPolicyUri)
+ .ConfigureAwait(false);
+ }
+ catch (ArgumentException ex) when (
+ ex.Message.Contains("No endpoint found for SecurityPolicyUri", StringComparison.Ordinal))
+ {
+ NUnit.Framework.Assert.Ignore(
+ $"Security policy {m_securityPolicyUri} is not advertised by the GDS test server.");
+ }
await ConnectGDSClientAsync(true).ConfigureAwait(false);
@@ -764,7 +842,7 @@ public async Task UpdateCertificateSelfSignedAsync(string keyFormat)
X509Certificate2 newCert;
- ECCurve? curve = EccUtils.GetCurveFromCertificateTypeId(m_certificateType);
+ ECCurve? curve = CryptoUtils.GetCurveFromCertificateTypeId(m_certificateType);
if (curve != null)
{
@@ -1287,7 +1365,7 @@ private async Task CreateCATestCertsAsync(string tempStorePath, ITelemetryContex
var certificateStoreIdentifier = new CertificateStoreIdentifier(tempStorePath, false);
Assert.IsTrue(EraseStore(certificateStoreIdentifier, telemetry));
const string subjectName = "CN=CA Test Cert, O=OPC Foundation";
- ECCurve? curve = EccUtils.GetCurveFromCertificateTypeId(m_certificateType);
+ ECCurve? curve = CryptoUtils.GetCurveFromCertificateTypeId(m_certificateType);
if (curve != null)
{
diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/CRLTests.cs b/Tests/Opc.Ua.Security.Certificates.Tests/CRLTests.cs
index d04e798ce6..1f668787e2 100644
--- a/Tests/Opc.Ua.Security.Certificates.Tests/CRLTests.cs
+++ b/Tests/Opc.Ua.Security.Certificates.Tests/CRLTests.cs
@@ -111,7 +111,7 @@ public CRLTests(string certificateTypeString, NodeId certificateType)
[OneTimeSetUp]
protected void OneTimeSetUp()
{
- ECCurve? curve = EccUtils.GetCurveFromCertificateTypeId(m_certificateType);
+ ECCurve? curve = CryptoUtils.GetCurveFromCertificateTypeId(m_certificateType);
if (curve != null)
{
diff --git a/Tests/Opc.Ua.Server.Tests/ClientLockoutTests.cs b/Tests/Opc.Ua.Server.Tests/ClientLockoutTests.cs
index 15dff4d53b..fba58efb08 100644
--- a/Tests/Opc.Ua.Server.Tests/ClientLockoutTests.cs
+++ b/Tests/Opc.Ua.Server.Tests/ClientLockoutTests.cs
@@ -66,10 +66,7 @@ public async Task FailedAuthenticationAttemptsAreTrackedAsync()
EndpointDescriptionCollection endpoints = m_server.GetEndpoints();
EndpointDescription endpoint = FindTcpEndpoint(endpoints);
- var secureChannelContext = new SecureChannelContext(
- sessionName,
- endpoint,
- RequestEncoding.Binary);
+ SecureChannelContext secureChannelContext = CreateSecureChannelContext(sessionName, endpoint);
var requestHeader = new RequestHeader();
CreateSessionResponse createResponse = await m_server.CreateSessionAsync(
@@ -130,10 +127,7 @@ public async Task ClientIsLockedOutAfterFiveFailedAttemptsAsync()
EndpointDescriptionCollection endpoints = m_server.GetEndpoints();
EndpointDescription endpoint = FindTcpEndpoint(endpoints);
- var secureChannelContext = new SecureChannelContext(
- sessionName,
- endpoint,
- RequestEncoding.Binary);
+ SecureChannelContext secureChannelContext = CreateSecureChannelContext(sessionName, endpoint);
var requestHeader = new RequestHeader();
CreateSessionResponse createResponse = await m_server.CreateSessionAsync(
@@ -228,10 +222,7 @@ public async Task SuccessfulAuthenticationClearsFailedAttemptsAsync()
EndpointDescriptionCollection endpoints = m_server.GetEndpoints();
EndpointDescription endpoint = FindTcpEndpoint(endpoints);
- var secureChannelContext = new SecureChannelContext(
- sessionName,
- endpoint,
- RequestEncoding.Binary);
+ SecureChannelContext secureChannelContext = CreateSecureChannelContext(sessionName, endpoint);
var requestHeader = new RequestHeader();
CreateSessionResponse createResponse = await m_server.CreateSessionAsync(
@@ -337,5 +328,16 @@ private static EndpointDescription FindTcpEndpoint(EndpointDescriptionCollection
endpoint.SecurityPolicyUri = SecurityPolicies.None;
return endpoint;
}
+
+ private static SecureChannelContext CreateSecureChannelContext(string sessionName, EndpointDescription endpoint)
+ {
+ return new SecureChannelContext(
+ sessionName,
+ endpoint,
+ RequestEncoding.Binary,
+ clientChannelCertificate: null,
+ serverChannelCertificate: null,
+ channelThumbprint: null);
+ }
}
}
diff --git a/Tests/Opc.Ua.Server.Tests/ServerFixture.cs b/Tests/Opc.Ua.Server.Tests/ServerFixture.cs
index 3554974699..0aa119eaaa 100644
--- a/Tests/Opc.Ua.Server.Tests/ServerFixture.cs
+++ b/Tests/Opc.Ua.Server.Tests/ServerFixture.cs
@@ -124,14 +124,109 @@ public async Task LoadConfigurationAsync(string pkiRoot = null)
{
// add deprecated policies for opc.tcp tests
serverConfig
- .AddPolicy(MessageSecurityMode.Sign, SecurityPolicies.Basic128Rsa15)
- .AddPolicy(MessageSecurityMode.Sign, SecurityPolicies.Basic256)
- .AddPolicy(MessageSecurityMode.SignAndEncrypt, SecurityPolicies.Basic128Rsa15)
- .AddPolicy(MessageSecurityMode.SignAndEncrypt, SecurityPolicies.Basic256)
.AddSignPolicies()
.AddSignAndEncryptPolicies()
.AddEccSignPolicies()
.AddEccSignAndEncryptPolicies();
+
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.Sign,
+ SecurityPolicies.Basic128Rsa15);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.SignAndEncrypt,
+ SecurityPolicies.Basic128Rsa15);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.Sign,
+ SecurityPolicies.Basic256);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.SignAndEncrypt,
+ SecurityPolicies.Basic256);
+
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.Sign,
+ SecurityPolicies.RSA_DH_AesGcm);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.SignAndEncrypt,
+ SecurityPolicies.RSA_DH_AesGcm);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.Sign,
+ SecurityPolicies.RSA_DH_ChaChaPoly);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.SignAndEncrypt,
+ SecurityPolicies.RSA_DH_ChaChaPoly);
+
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.Sign,
+ SecurityPolicies.ECC_nistP256_AesGcm);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.SignAndEncrypt,
+ SecurityPolicies.ECC_nistP256_AesGcm);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.Sign,
+ SecurityPolicies.ECC_nistP256_ChaChaPoly);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.SignAndEncrypt,
+ SecurityPolicies.ECC_nistP256_ChaChaPoly);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.Sign,
+ SecurityPolicies.ECC_nistP384_AesGcm);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.SignAndEncrypt,
+ SecurityPolicies.ECC_nistP384_AesGcm);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.Sign,
+ SecurityPolicies.ECC_nistP384_ChaChaPoly);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.SignAndEncrypt,
+ SecurityPolicies.ECC_nistP384_ChaChaPoly);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.Sign,
+ SecurityPolicies.ECC_brainpoolP256r1_AesGcm);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.SignAndEncrypt,
+ SecurityPolicies.ECC_brainpoolP256r1_AesGcm);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.Sign,
+ SecurityPolicies.ECC_brainpoolP256r1_ChaChaPoly);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.SignAndEncrypt,
+ SecurityPolicies.ECC_brainpoolP256r1_ChaChaPoly);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.Sign,
+ SecurityPolicies.ECC_brainpoolP384r1_AesGcm);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.SignAndEncrypt,
+ SecurityPolicies.ECC_brainpoolP384r1_AesGcm);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.Sign,
+ SecurityPolicies.ECC_brainpoolP384r1_ChaChaPoly);
+ AddPolicyIfSupported(
+ serverConfig,
+ MessageSecurityMode.SignAndEncrypt,
+ SecurityPolicies.ECC_brainpoolP384r1_ChaChaPoly);
}
if (OperationLimits)
@@ -342,6 +437,17 @@ public async Task StopAsync()
await Task.Delay(100).ConfigureAwait(false);
}
+ private static void AddPolicyIfSupported(
+ IApplicationConfigurationBuilderServerSelected serverConfig,
+ MessageSecurityMode securityMode,
+ string securityPolicyUri)
+ {
+ if (SecurityPolicies.GetInfo(securityPolicyUri) != null)
+ {
+ serverConfig.AddPolicy(securityMode, securityPolicyUri);
+ }
+ }
+
private readonly Func m_factory;
private readonly ITelemetryContext m_telemetry;
private readonly ILogger m_logger;
diff --git a/Tests/Opc.Ua.Server.Tests/ServerFixtureUtils.cs b/Tests/Opc.Ua.Server.Tests/ServerFixtureUtils.cs
index 53f777e313..225013cc9a 100644
--- a/Tests/Opc.Ua.Server.Tests/ServerFixtureUtils.cs
+++ b/Tests/Opc.Ua.Server.Tests/ServerFixtureUtils.cs
@@ -94,7 +94,12 @@ public static class ServerFixtureUtils
// set security context
var secureChannelContext
- = new SecureChannelContext(sessionName, endpoint, RequestEncoding.Binary);
+ = new SecureChannelContext(
+ sessionName,
+ endpoint, RequestEncoding.Binary,
+ null,
+ null,
+ null);
var requestHeader = new RequestHeader();
// Create session