Skip to content

Commit

Permalink
Implement validation for duplicate nonces (#893)
Browse files Browse the repository at this point in the history
* - Add Server nonce validation for duplicate nonces.
- Add configuration flag to bypass validation exceptions

* Add ITransportChannel.CurrentToken interface for nonce validation.
  • Loading branch information
AlinMoldovean committed Jan 30, 2020
1 parent a1faa0f commit 636f5c0
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 5 deletions.
29 changes: 25 additions & 4 deletions SampleApplications/SDK/Opc.Ua.Client/Session.cs
Expand Up @@ -40,6 +40,7 @@
using System.Xml;
using System.Threading.Tasks;
using System.Reflection;
using Opc.Ua.Bindings;

namespace Opc.Ua.Client
{
Expand Down Expand Up @@ -311,7 +312,7 @@ private void ValidateClientConfiguration(ApplicationConfiguration configuration)
/// <summary>
/// Validates the server nonce and security parameters of user identity.
/// </summary>
private void ValidateServerNonce(IUserIdentity identity, byte[] serverNonce, string securityPolicyUri)
private void ValidateServerNonce(IUserIdentity identity, byte[] serverNonce, string securityPolicyUri, byte[] previousServerNonce)
{
// skip validation if server nonce is not used for encryption.
if (String.IsNullOrEmpty(securityPolicyUri) || securityPolicyUri == SecurityPolicies.None)
Expand All @@ -326,6 +327,15 @@ private void ValidateServerNonce(IUserIdentity identity, byte[] serverNonce, str
{
throw ServiceResultException.Create(StatusCodes.BadNonceInvalid, "Server nonce is not the correct length or not random enough.");
}

// check that new nonce is different from the previously returned server nonce.
if (previousServerNonce!= null && Utils.CompareNonce(serverNonce, previousServerNonce))
{
if (!m_configuration.SecurityConfiguration.SuppressNonceValidationErrors)
{
throw ServiceResultException.Create(StatusCodes.BadNonceInvalid, "Server nonce is equal with previously returned nonce.");
}
}
}
}

Expand Down Expand Up @@ -1082,7 +1092,7 @@ public void Reconnect()
}

// validate server nonce and security parameters for user identity.
ValidateServerNonce(m_identity, m_serverNonce, securityPolicyUri);
ValidateServerNonce(m_identity, m_serverNonce, securityPolicyUri, m_previousServerNonce);

// sign data with user token.
UserIdentityToken identityToken = m_identity.GetIdentityToken();
Expand Down Expand Up @@ -1150,6 +1160,7 @@ public void Reconnect()
lock (SyncRoot)
{
Utils.Trace("Session RECONNECT completed successfully.");
m_previousServerNonce = m_serverNonce;
m_serverNonce = serverNonce;
m_reconnecting = false;
publishCount = m_subscriptions.Count;
Expand Down Expand Up @@ -2571,8 +2582,15 @@ public ReferenceDescriptionCollection FetchReferences(NodeId nodeId)
securityPolicyUri = m_endpoint.Description.SecurityPolicyUri;
}

byte[] previousServerNonce = null;

if (TransportChannel.CurrentToken!= null)
{
previousServerNonce = TransportChannel.CurrentToken.ServerNonce;
}

// validate server nonce and security parameters for user identity.
ValidateServerNonce(identity, serverNonce, securityPolicyUri);
ValidateServerNonce(identity, serverNonce, securityPolicyUri, previousServerNonce);

// sign data with user token.
SignatureData userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri);
Expand Down Expand Up @@ -2625,6 +2643,7 @@ public ReferenceDescriptionCollection FetchReferences(NodeId nodeId)
// save nonces.
m_sessionName = sessionName;
m_identity = identity;
m_previousServerNonce = previousServerNonce;
m_serverNonce = serverNonce;
m_serverCertificate = serverCertificate;

Expand Down Expand Up @@ -2735,7 +2754,7 @@ public void UpdateSession(IUserIdentity identity, StringCollection preferredLoca
}

// validate server nonce and security parameters for user identity.
ValidateServerNonce(identity, serverNonce, securityPolicyUri);
ValidateServerNonce(identity, serverNonce, securityPolicyUri, m_previousServerNonce);

// sign data with user token.
identityToken = identity.GetIdentityToken();
Expand Down Expand Up @@ -2771,6 +2790,7 @@ public void UpdateSession(IUserIdentity identity, StringCollection preferredLoca
m_identity = identity;
}

m_previousServerNonce = m_serverNonce;
m_serverNonce = serverNonce;
m_preferredLocales = preferredLocales;

Expand Down Expand Up @@ -4390,6 +4410,7 @@ private void OnRaisePublishNotification(object state)
private object m_handle;
private IUserIdentity m_identity;
private byte[] m_serverNonce;
private byte[] m_previousServerNonce;
private X509Certificate2 m_serverCertificate;
private long m_publishCounter;
private DateTime m_lastKeepAliveTime;
Expand Down
14 changes: 14 additions & 0 deletions Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.cs
Expand Up @@ -1037,6 +1037,19 @@ public CertificateTrustList TrustedHttpsCertificates
}
}
}

/// <summary>
/// Gets or sets a value indicating whether the server nonce validation errors should be suppressed.
/// </summary>
/// <remarks>
/// If set to true the server nonce validation errors are suppressed.
/// </remarks>
[DataMember(IsRequired = false, EmitDefaultValue = false, Order = 19)]
public bool SuppressNonceValidationErrors
{
get { return m_suppressNonceValidationErrors; }
set { m_suppressNonceValidationErrors = value; }
}
#endregion

#region Private Fields
Expand All @@ -1056,6 +1069,7 @@ public CertificateTrustList TrustedHttpsCertificates
private ushort m_minCertificateKeySize;
private bool m_addAppCertToTrustedStore;
private bool m_sendCertificateChain;
private bool m_suppressNonceValidationErrors;
#endregion
}
#endregion
Expand Down
1 change: 1 addition & 0 deletions Stack/Opc.Ua.Core/Schema/ApplicationConfiguration.xsd
Expand Up @@ -57,6 +57,7 @@
<xs:element name="TrustedUserCertificates" type="CertificateTrustList" minOccurs="0" />
<xs:element name="HttpsIssuerCertificates" type="CertificateTrustList" minOccurs="0" />
<xs:element name="TrustedHttpsCertificates" type="CertificateTrustList" minOccurs="0" />
<xs:element name="SuppressNonceValidationErrors" type="xs:boolean" minOccurs="0" />
</xs:sequence>
</xs:complexType>

Expand Down
8 changes: 8 additions & 0 deletions Stack/Opc.Ua.Core/Stack/Client/WcfChannelBase.cs
Expand Up @@ -208,6 +208,14 @@ public ServiceMessageContext MessageContext
}
}

/// <summary>
/// Gets the the channel's current security token.
/// </summary>
public ChannelToken CurrentToken
{
get { return null; }
}

/// <summary>
/// Gets or sets the default timeout for requests send via the channel.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions Stack/Opc.Ua.Core/Stack/Https/HttpsTransportChannel.cs
Expand Up @@ -52,6 +52,11 @@ public ServiceMessageContext MessageContext
get { return m_quotas.MessageContext; }
}

public ChannelToken CurrentToken
{
get { return null; }
}

public int OperationTimeout
{
get { return m_operationTimeout; }
Expand Down
2 changes: 1 addition & 1 deletion Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs
Expand Up @@ -23,7 +23,7 @@ public partial class UaSCUaBinaryChannel
/// <summary>
/// Returns the current security token.
/// </summary>
protected ChannelToken CurrentToken => m_currentToken;
protected internal ChannelToken CurrentToken => m_currentToken;

/// <summary>
/// Returns the current security token.
Expand Down
8 changes: 8 additions & 0 deletions Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryTransportChannel.cs
Expand Up @@ -81,6 +81,14 @@ public IMessageSocket Socket
/// </summary>
public ServiceMessageContext MessageContext => m_quotas.MessageContext;

/// <summary>
/// Gets the the channel's current security token.
/// </summary>
public ChannelToken CurrentToken
{
get { lock (m_lock) { return m_channel?.CurrentToken; } }
}

/// <summary>
/// Gets or sets the default timeout for requests send via the channel.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions Stack/Opc.Ua.Core/Stack/Transport/ITransportChannel.cs
Expand Up @@ -13,6 +13,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using Opc.Ua.Bindings;

namespace Opc.Ua
{
Expand Down Expand Up @@ -54,6 +55,11 @@ public interface ITransportChannel : IDisposable
/// </summary>
ServiceMessageContext MessageContext { get; }

/// <summary>
/// Gets the the channel's current security token.
/// </summary>
ChannelToken CurrentToken { get; }

/// <summary>
/// Gets or sets the default timeout for requests send via the channel.
/// </summary>
Expand Down

0 comments on commit 636f5c0

Please sign in to comment.