Skip to content

Incorrect UserTokenPolicyId generation on servers listening on multiple IP addresses #3522

@raserle

Description

@raserle

Type of issue

  • Bug
  • Enhancement
  • Compliance
  • Question
  • Help wanted

Current Behavior

From my analysis, the issue appears to originate in ServerBase.cs, specifically in the method GetUserTokenPolicies, where the PolicyId is generated using a global counter:

clone.PolicyId = Utils.Format("{0}", ++m_userTokenPolicyId);

Since this counter is incremented globally, the generated UserTokenPolicyId values become dependent on the order in which endpoints (or base addresses) are processed.

In combination with the method TranslateEndpointDescriptions in ServerBase.cs, the UserTokenPolicyIds of the first configured server address are reused for subsequent addresses.

As a result, GetEndpoints returns UserTokenPolicyIds that do not match the internal configuration of the endpoint actually used by the client, causing ActivateSession to fail with BadIdentityTokenInvalid.

Expected Behavior

UserTokenPolicyIds should be stable across all endpoints of the same server instance.
For semantically identical UserTokenPolicy definitions, the same policy ID should be returned, independent of the IP address used to access the server.

Generating policy IDs using a simple counter can lead to endpoint-order-dependent behavior.
Reusing existing policy IDs for equivalent UserTokenPolicy definitions would avoid this and allow clients to reliably activate sessions using the IDs returned by GetEndpoints.

One possible approach would be to introduce a semantic equality comparison for UserTokenPolicy and reuse existing policy IDs instead of generating new ones unconditionally.

Proposed equality method on UserTokenPolicy

{
    if (ReferenceEquals(null, other)) return false;
    if (ReferenceEquals(this, other)) return true;

    return
        this.TokenType == other.TokenType &&
        string.Equals(this.SecurityPolicyUri, other.SecurityPolicyUri, StringComparison.Ordinal) &&
        string.Equals(this.IssuedTokenType, other.IssuedTokenType, StringComparison.Ordinal) &&
        string.Equals(this.IssuerEndpointUrl, other.IssuerEndpointUrl, StringComparison.Ordinal);
}

Server-wide storage of generated policies in ServerBase

private readonly IList<UserTokenPolicy> m_userTokenPolicys = new List<UserTokenPolicy>();

logic inside GetUserTokenPolicies in ServerBase

var existingPolicy =
    m_userTokenPolicys.FirstOrDefault(o => o.TokenPolicyEquals(policy));

if (existingPolicy == null)
{
    // Ensure each policy has a unique ID within the context of the Server
    clone.PolicyId = Utils.Format("{0}", m_userTokenPolicys.Count + 1);
    policies.Add(clone);
    m_userTokenPolicys.Add(clone);
}
else
{
    policies.Add(existingPolicy);
}

Steps To Reproduce

  1. Configure an OPC UA server to listen on multiple network interfaces (not localhost),
    e.g. 127.0.0.1 and 192.168.138.x (or any additional IP address assigned to the host).

  2. Configure a user that is allowed to authenticate using Username/Password
    (e.g. test / test).

  3. Configure the server with the following security settings:

  1. Start server
  2. Connect a client to the server using the first IP address
    → Connection succeeds.
  3. Connect the same client to the server using the second IP address
    → Connection fails during ActivateSession with the error: "Error 'BadIdentityTokenInvalid' was returned during ActivateSession"

Metadata

Metadata

Assignees

Labels

bugA bug was identified and should be fixed.server

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions