OPCFoundation / UA-.NETStandard

OPC Unified Architecture .NET Standard
Other
1.96k stars 948 forks source link

Client can connect without security to secure-only endpoint #2202

Closed backspacer80 closed 1 year ago

backspacer80 commented 1 year ago

Type of issue

Current Behavior

I have a device with an Opc Ua Server running on it. There are clients both inside the device and outside the device. I have the requirement that the clients that connect from outside are forced to use encryption (for security reasons) and clients from inside are forced to NOT use enryption (for performance reasons). First question: Is this achievable at all? I have tried different configurations to achieve that (more on that below) but still I can make a client from outside connect to the server without security. I have tried so far:

  1. Setup two security policies: One with SecurityPolicy#None and one with SecurityPolicy#Basic256Sha256. The server will create two endpoints accordingly. The client from outside can however still decide to use the one without encryption.
  2. Setup two base addresses with different ports: One port for secure and one for insecure communication. The server will then create four endpoints: One per combination of base address and security policy. Then I block the insecure port through a firewall. The client however uses endpoint discovery and chooses to connect on the non-blocked port but still with SecurityPolicy#None !
  3. Same as 2 but override the GetEndpoints method on the server so that it only exposes the endpoint with the secure port. When the client uses endpoint discovery it finally manages to connect with SecurityPolicy#Basic256Sha256. However I can still setup my client to connect without endpoint discovery and then it can still connect to the non-blocked port with SecurityPolicy#None !

So why does the server not reject the insecure connections? Is this by design or is this a bug? Am I using the right approach or is there another approach that does fulfill the requirement of internal/insecure vs. external/secure connections?

Expected Behavior

If the exposed endpoints don't contain policies with security mode None then client connection attempts with security mode None should be rejected.

Steps To Reproduce

Here is the server config file:

<?xml version="1.0" encoding="utf-8"?>
<ApplicationConfiguration
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:ua="http://opcfoundation.org/UA/2008/02/Types.xsd"
  xmlns="http://opcfoundation.org/UA/SDK/Configuration.xsd"
>
  <ApplicationName>My OPC-UA Server</ApplicationName>
  <ApplicationUri>urn:localhost:UA:my:OpcUaServer</ApplicationUri>
  <ProductUri>uri:my.com:my:OpcUaServer</ProductUri>
  <ApplicationType>Server_0</ApplicationType>

  <SecurityConfiguration>

    <!-- Where the application instance certificate is stored (MachineDefault) -->
    <ApplicationCertificate>
      <StoreType>Directory</StoreType>
      <StorePath>pki/own</StorePath>
      <SubjectName>CN=My OPC-UA Server, O=My Company, C=DE</SubjectName>
    </ApplicationCertificate>
  </SecurityConfiguration>

  <TransportConfigurations></TransportConfigurations>
  <TransportQuotas>
    <OperationTimeout>600000</OperationTimeout>
    <MaxStringLength>1048576</MaxStringLength>
    <MaxByteStringLength>1048576</MaxByteStringLength>
    <MaxArrayLength>65535</MaxArrayLength>
    <MaxMessageSize>4194304</MaxMessageSize>
    <MaxBufferSize>65535</MaxBufferSize>
    <ChannelLifetime>300000</ChannelLifetime>
    <SecurityTokenLifetime>3600000</SecurityTokenLifetime>
  </TransportQuotas>
  <ServerConfiguration>
    <BaseAddresses>
        <ua:String>opc.tcp://localhost:62541/OpcUaServer</ua:String>
        <ua:String>opc.tcp://localhost:62542/OpcUaServer</ua:String>
    </BaseAddresses>
    <SecurityPolicies>
      <ServerSecurityPolicy>
        <SecurityMode>None_1</SecurityMode>
        <SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#None</SecurityPolicyUri>
      </ServerSecurityPolicy>
      <ServerSecurityPolicy>
        <SecurityMode>SignAndEncrypt_3</SecurityMode>
        <SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256</SecurityPolicyUri>
      </ServerSecurityPolicy>
    </SecurityPolicies>

    <MinRequestThreadCount>5</MinRequestThreadCount>
    <MaxRequestThreadCount>100</MaxRequestThreadCount>
    <MaxQueuedRequestCount>2000</MaxQueuedRequestCount>

    <!-- The SDK expects the server to support the same set of user tokens for every endpoint. -->
    <UserTokenPolicies>
      <!-- Allows anonymous users -->
      <ua:UserTokenPolicy>
        <ua:TokenType>Anonymous_0</ua:TokenType>
        <ua:SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#None</ua:SecurityPolicyUri>
      </ua:UserTokenPolicy>

      <!-- Allows username/password -->
      <ua:UserTokenPolicy>
        <ua:TokenType>UserName_1</ua:TokenType>
        <!-- passwords must be encrypted - this specifies what algorithm to use -->
        <ua:SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256</ua:SecurityPolicyUri>
      </ua:UserTokenPolicy>

      <!-- Allows user certificates -->
      <ua:UserTokenPolicy>
        <ua:TokenType>Certificate_2</ua:TokenType>
        <!-- certificate possession must be proven with a digital signature - this specifies what algorithm to use -->
        <ua:SecurityPolicyUri>http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256</ua:SecurityPolicyUri>
      </ua:UserTokenPolicy>
    </UserTokenPolicies>
    <DiagnosticsEnabled>true</DiagnosticsEnabled>
    <MaxSessionCount>100</MaxSessionCount>
    <MinSessionTimeout>10000</MinSessionTimeout>
    <MaxSessionTimeout>3600000</MaxSessionTimeout>
    <MaxBrowseContinuationPoints>10</MaxBrowseContinuationPoints>
    <MaxQueryContinuationPoints>10</MaxQueryContinuationPoints>
    <MaxHistoryContinuationPoints>100</MaxHistoryContinuationPoints>
    <MaxRequestAge>600000</MaxRequestAge>
    <MinPublishingInterval>100</MinPublishingInterval>
    <MaxPublishingInterval>3600000</MaxPublishingInterval>
    <PublishingResolution>50</PublishingResolution>
    <MaxSubscriptionLifetime>3600000</MaxSubscriptionLifetime>
    <MaxMessageQueueSize>100</MaxMessageQueueSize>
    <MaxNotificationQueueSize>100</MaxNotificationQueueSize>
    <MaxNotificationsPerPublish>1000</MaxNotificationsPerPublish>
    <MinMetadataSamplingInterval>1000</MinMetadataSamplingInterval>
    <AvailableSamplingRates>
      <SamplingRateGroup>
        <Start>5</Start>
        <Increment>5</Increment>
        <Count>20</Count>
      </SamplingRateGroup>
      <SamplingRateGroup>
        <Start>100</Start>
        <Increment>100</Increment>
        <Count>4</Count>
      </SamplingRateGroup>
      <SamplingRateGroup>
        <Start>500</Start>
        <Increment>250</Increment>
        <Count>2</Count>
      </SamplingRateGroup>
      <SamplingRateGroup>
        <Start>1000</Start>
        <Increment>500</Increment>
        <Count>20</Count>
      </SamplingRateGroup>
    </AvailableSamplingRates>

    <RegistrationEndpoint>
      <ua:EndpointUrl>opc.tcp://localhost:4840</ua:EndpointUrl>
      <ua:Server>
        <ua:ApplicationUri>opc.tcp://localhost:4840</ua:ApplicationUri>
        <ua:ApplicationType>DiscoveryServer_3</ua:ApplicationType>
        <ua:DiscoveryUrls>
          <ua:String>opc.tcp://localhost:4840</ua:String>
        </ua:DiscoveryUrls>
      </ua:Server>
      <ua:SecurityMode>SignAndEncrypt_3</ua:SecurityMode>
      <ua:SecurityPolicyUri />
      <ua:UserIdentityTokens />
    </RegistrationEndpoint>

    <!--A value of 0 turns off the discovery service which we don't need:-->
    <MaxRegistrationInterval>0</MaxRegistrationInterval>
    <NodeManagerSaveFile>Quickstarts.ReferenceServer.nodes.xml</NodeManagerSaveFile>
    <MinSubscriptionLifetime>10000</MinSubscriptionLifetime>
    <MaxPublishRequestCount>20</MaxPublishRequestCount>
    <MaxSubscriptionCount>100</MaxSubscriptionCount>
    <MaxEventQueueSize>10000</MaxEventQueueSize>

    <!-- see https://opcfoundation-onlineapplications.org/profilereporting/ for list of available profiles -->
    <ServerProfileArray>
      <ua:String>http://opcfoundation.org/UA-Profile/Server/StandardUA2017</ua:String>
      <ua:String>http://opcfoundation.org/UA-Profile/Server/DataAccess</ua:String>
      <ua:String>http://opcfoundation.org/UA-Profile/Server/Methods</ua:String>
    </ServerProfileArray>

    <ShutdownDelay>5</ShutdownDelay>
    <ServerCapabilities>
      <ua:String>DA</ua:String>
    </ServerCapabilities>
    <SupportedPrivateKeyFormats>
      <ua:String>PFX</ua:String>
      <ua:String>PEM</ua:String>
    </SupportedPrivateKeyFormats>
    <MaxTrustListSize>0</MaxTrustListSize>
    <MultiCastDnsEnabled>false</MultiCastDnsEnabled>
  </ServerConfiguration>

  <Extensions>
    <ua:XmlElement>
      <ReferenceServerConfiguration xmlns="http://opcfoundation.org/Quickstarts/ReferenceApplications">
        <ShowCertificateValidationDialog>false</ShowCertificateValidationDialog>
      </ReferenceServerConfiguration>
    </ua:XmlElement>
  </Extensions>

</ApplicationConfiguration>

Here is the client code:

using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Configuration;

namespace GithubOpcUaSample
{
    class InsecureClient
    {
        public void Connect()
        {
            // Discover endpoints. The returned description uses SecurityPolicy#Basic256Sha256:
            var endpointDescription = CoreClientUtils.SelectEndpoint("opc.tcp://localhost:62542/OpcUaServer", useSecurity: true);

            // Disable security on the endpoint description:
            endpointDescription.SecurityMode = MessageSecurityMode.None;
            endpointDescription.SecurityLevel = 0;
            endpointDescription.SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#None";

            ApplicationInstance application = new ApplicationInstance
            {
                ApplicationName = "My OPC-UA Client",
                ApplicationType = ApplicationType.Client,
                ConfigSectionName = "OpcUaClient"
            };

            var configuration = application.LoadApplicationConfiguration(silent: false).Result;
            var endpointConfiguration = EndpointConfiguration.Create(configuration);
            var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);

            // Successfully connects using SecurityPolicy#None:
            Session session = Session.Create(
                configuration,
                endpoint,
                false,
                false,
                configuration.ApplicationName,
                (uint)configuration.ClientConfiguration.DefaultSessionTimeout,
                new UserIdentity(),
                null
            ).Result;

        }
    }
}

Environment

- OS: Windows 10
- Environment: Visual Studio 2022
- Runtime: .net 6.0
- Nuget Version: 1.4.371.91
- Component: Server and client
- Server: Own server derived from Reference Server.
- Client: Own client which uses Opc.Ua.Client. Same behaviour with OPC Watch.

Anything else?

No response

mrsuciu commented 1 year ago

@backspacer80 If you use approach number 2 for example you could further restrict the ActivateSession service call on the server to accept connections on the port reserved for secure communication, only if the SecurityPolicy is different than SecurityPolicies.None. You have the necessary information under the local variable "OperationContext context" instance (OperationContext.ChannelContext.EndpointDescription)

backspacer80 commented 1 year ago

@backspacer80 If you use approach number 2 for example you could further restrict the ActivateSession service call on the server to accept connections on the port reserved for secure communication, only if the SecurityPolicy is different than SecurityPolicies.None. You have the necessary information under the local variable "OperationContext context" instance (OperationContext.ChannelContext.EndpointDescription)

Thank you, that answers my question. I ended up overriding ActivateSession and setting StatusCodes.BadSecurityModeRejected as result in order to reject insecure connections.