Skip to content

Cannot connect to Secured OPC Server #1249

@JaeWonyH

Description

@JaeWonyH

I'm using eclipse milo version 0.3.6
And OPC UA Server is KEPServerEX6

This is the KEPServerEX6 Configuration.
image

And This is the OPC UA Client Code.

** KeyStoreLoader.java
`/*

package com.sit.stdplf.aqst.agent.opcua01.milo.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.regex.Pattern;

import org.eclipse.milo.opcua.sdk.server.util.HostnameUtil;
import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateBuilder;
import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sit.stdplf.aqst.agent.opcua01.common.config.Props;
import com.sit.stdplf.aqst.agent.opcua01.common.constant.Consts;

public class KeyStoreLoader
{
private static Logger LOGGER = LoggerFactory.getLogger ( Consts.LOGGER_NAME );

private static final String CLIENT_ALIAS = "client-ai";
private static final char [] PASSWORD = "password".toCharArray ();

private X509Certificate clientCertificate;
private KeyPair clientKeyPair;

private static final Pattern IP_ADDR_PATTERN = Pattern.compile (
                "^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$" );

private final String agentId = Props.AGENT_ID;

public KeyStoreLoader load ( final File baseDir ) throws Exception
{
    final KeyStore keyStore = KeyStore.getInstance ( "PKCS12" );

    final File serverKeyStore = baseDir.toPath ().resolve ( "example-client.pfx" ).toFile ();
    

    LOGGER.info ( "Loading KeyStore at {}", serverKeyStore );

    if ( !serverKeyStore.exists () )
    {
        keyStore.load ( null, PASSWORD );

        final KeyPair keyPair = SelfSignedCertificateGenerator.generateRsaKeyPair ( 2048 );

        final SelfSignedCertificateBuilder builder = new SelfSignedCertificateBuilder ( keyPair )
                        .setCommonName ( "InterSysLink_Client["+this.agentId+"]" )
                        .setOrganization ( "hanwhaconvergence" )
                        .setCountryCode ( "KO" )
                        .setApplicationUri ( "urn:hwcv:agent:opcua:client")
                        .addDnsName ( "localhost" )
                        .addIpAddress ( "127.0.0.1" )
                        ;


        // Get as many hostnames and IP addresses as we can listed in the certificate.
        for ( final String hostname : HostnameUtil.getHostnames ( "0.0.0.0" ) )
        {
            if ( IP_ADDR_PATTERN.matcher ( hostname ).matches () )
            {
                builder.addIpAddress ( hostname );
            } else
            {
                builder.addDnsName ( hostname );
            }
        }

        final X509Certificate certificate = builder.build ();

        keyStore.setKeyEntry ( CLIENT_ALIAS, keyPair.getPrivate (), PASSWORD, new X509Certificate [] { certificate } );
        keyStore.store ( new FileOutputStream ( serverKeyStore ), PASSWORD );
    } else
    {
        keyStore.load ( new FileInputStream ( serverKeyStore ), PASSWORD );
    }

    final Key serverPrivateKey = keyStore.getKey ( CLIENT_ALIAS, PASSWORD );
    if ( serverPrivateKey instanceof PrivateKey )
    {
        this.clientCertificate = ( X509Certificate ) keyStore.getCertificate ( CLIENT_ALIAS );
        final PublicKey serverPublicKey = this.clientCertificate.getPublicKey ();
        this.clientKeyPair = new KeyPair ( serverPublicKey, ( PrivateKey ) serverPrivateKey );
    }

    return this;
}

public X509Certificate getClientCertificate ()
{
    return this.clientCertificate;
}

public KeyPair getClientKeyPair ()
{
    return this.clientKeyPair;
}

}
`

** OPCClient.java
`package com.sit.stdplf.aqst.agent.opcua01.milo;

import java.io.File;
import java.net.URI;
import java.util.List;

import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;
import org.eclipse.milo.opcua.stack.client.DiscoveryClient;
import org.eclipse.milo.opcua.stack.core.channel.MessageLimits;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sit.stdplf.aqst.agent.opcua01.common.config.Props;
import com.sit.stdplf.aqst.agent.opcua01.common.constant.Consts;
import com.sit.stdplf.aqst.agent.opcua01.common.constant.ErrCds;
import com.sit.stdplf.aqst.agent.opcua01.common.message.LangMessage;
import com.sit.stdplf.aqst.agent.opcua01.milo.util.KeyStoreLoader;
import com.sit.stdplf.base.common.config.BaseProps;
import com.sit.stdplf.base.common.constant.BaseConsts;
import com.sit.stdplf.base.logmgt.exception.SPExceptionHandler;
import com.sit.stdplf.base.logmgt.exception.network.SPNetworkException;
import com.sit.stdplf.commons.util.LogMsgUtil;

public class OPCClient
{
private static final Logger LOGGER = LoggerFactory.getLogger ( Consts.LOGGER_NAME );

private final KeyStoreLoader loader = new KeyStoreLoader ();

public OpcUaClient opcUAClient;



public static final int DEFAULT_MAX_MESSAGE_SIZE = 4 * 1024 * 1024;

/**
 * The default maximum size of a single chunk.
 */
public static final int DEFAULT_MAX_CHUNK_SIZE = 65535;

/**
 * The default maximum number of chunks that a message can break down into.
 * <p>
 * More than chunks than constitute {@link #DEFAULT_MAX_MESSAGE_SIZE} are needed because of overhead
 * when constructing chunks; not all of the chunk size is dedicated to message bytes.
 */
public static final int DEFAULT_MAX_CHUNK_COUNT = ( DEFAULT_MAX_MESSAGE_SIZE / DEFAULT_MAX_CHUNK_SIZE ) * 2;

public OpcUaClient createClient ( final String connectionUrl,
                                  final String ip,
                                  final int connectionTimeout ) throws Exception
{
    final File securityTempDir = new File ( BaseConsts.CACHE_INFO_FULL_PATH + "driver" + BaseConsts.FILE_SEPER + Props.AGENT_ID,
                    "security" );
    if ( !securityTempDir.exists () && !securityTempDir.mkdirs () )
    {
        throw new Exception ( "unable to create security dir: " + securityTempDir );
    }

    SecurityPolicy tempSecurityPolicy = null;

    if ( Props.SECURITY_POLICY.equals ( Consts.SECURITY_POLICY_0 ) )
    {
        tempSecurityPolicy = SecurityPolicy.None;
    } else if ( Props.SECURITY_POLICY.equals ( Consts.SECURITY_POLICY_1 ) )
    {
        tempSecurityPolicy = SecurityPolicy.Basic256;
    } else if ( Props.SECURITY_POLICY.equals ( Consts.SECURITY_POLICY_2 ) )
    {
        tempSecurityPolicy = SecurityPolicy.Basic256Sha256;
    } else if ( Props.SECURITY_POLICY.equals ( Consts.SECURITY_POLICY_3 ) )
    {
        tempSecurityPolicy = SecurityPolicy.Basic256Sha256;
    }

    final SecurityPolicy securityPolicy = tempSecurityPolicy;

    List<EndpointDescription> endpoints;

    try
    {
        endpoints = DiscoveryClient.getEndpoints ( connectionUrl ).get ();

    } catch ( final Throwable ex )
    {
        // try the explicit discovery endpoint as well
        String discoveryUrl = connectionUrl;

        if ( !discoveryUrl.endsWith ( "/" ) )
        {
            discoveryUrl += "/";
        }
        discoveryUrl += "discovery";

        LOGGER.info ( "Trying explicit discovery URL: {}", discoveryUrl );

        endpoints = DiscoveryClient.getEndpoints ( discoveryUrl ).get ();
    }

    EndpointDescription endpoint = endpoints.stream ()
                    .filter ( e -> e.getSecurityPolicyUri ().equals ( securityPolicy.getUri () ) )
                    .filter ( e -> true )
                    .findFirst ()
                    .orElseThrow ( () -> new Exception ( "no desired endpoints returned" ) );

    final URI uri = new URI ( endpoint.getEndpointUrl () ).parseServerAuthority ();

    final String endpointUrl = String.format (
                    "%s://%s:%s%s",
                    uri.getScheme (),
                    ip,
                    uri.getPort (),
                    uri.getPath () );

    endpoint = new EndpointDescription (
                    endpointUrl,
                    endpoint.getServer (),
                    endpoint.getServerCertificate (),
                    endpoint.getSecurityMode (),
                    endpoint.getSecurityPolicyUri (),
                    endpoint.getUserIdentityTokens (),
                    endpoint.getTransportProfileUri (),
                    endpoint.getSecurityLevel ()
                    );

    LOGGER.info ( LogMsgUtil.infoFormat ( "Using endpoint: {} [{}]" ), endpoint.getEndpointUrl (), endpoint.getSecurityMode () );

    final UInteger timeout = UInteger.valueOf ( connectionTimeout );

    this.loader.load ( securityTempDir );

    final MessageLimits messageLimits = new MessageLimits ( DEFAULT_MAX_CHUNK_SIZE, DEFAULT_MAX_CHUNK_COUNT,
                    DEFAULT_MAX_MESSAGE_SIZE );
    
    OpcUaClientConfig config = null;

    if ( Props.USER_AUTHENTICATION_MODE.equals ( Consts.USER_AUTHENTICATION_MODE_0 ) )
    {
        config = OpcUaClientConfig.builder ()
                        .setApplicationName ( LocalizedText.english ( BaseProps.SYSTEM_SERVICE_NAME + " OPC-UA Client" ) )
                        .setApplicationUri ( "urn:hwcv:agent:opcua:client" )
                        .setCertificate ( this.loader.getClientCertificate () )
                        .setKeyPair ( this.loader.getClientKeyPair () )
                        .setEndpoint ( endpoint )
                        .setIdentityProvider ( new AnonymousProvider () )
                        .setRequestTimeout ( timeout )
                        .setMessageLimits ( messageLimits )
                        .build ();

    } else if ( Props.USER_AUTHENTICATION_MODE.equals ( Consts.USER_AUTHENTICATION_MODE_1 ) )
    {
        config = OpcUaClientConfig.builder ()
                        .setApplicationName ( LocalizedText.english ( BaseProps.SYSTEM_SERVICE_NAME + " OPC-UA Client" ) )
                        .setApplicationUri ( "urn:opcua:" + BaseProps.SYSTEM_SERVICE_NAME + " OPC-UA Client" )
                        .setCertificate ( this.loader.getClientCertificate () )
                        .setKeyPair ( this.loader.getClientKeyPair () )
                        .setEndpoint ( endpoint )
                        .setIdentityProvider ( new UsernameProvider ( Props.USER_ID, Props.USER_PASSWORD ) )
                        .setRequestTimeout ( timeout )
                        .setMessageLimits ( messageLimits )
                        .build ();

    } else if ( Props.USER_AUTHENTICATION_MODE.equals ( Consts.USER_AUTHENTICATION_MODE_2 ) )
    {
        config = OpcUaClientConfig.builder ()
                        .setApplicationName ( LocalizedText.english ( BaseProps.SYSTEM_SERVICE_NAME + " OPC-UA Client" ) )
                        .setApplicationUri ( "urn:opcua:" + BaseProps.SYSTEM_SERVICE_NAME + " OPC-UA Client" )
                        .setCertificate ( this.loader.getClientCertificate () )
                        .setKeyPair ( this.loader.getClientKeyPair () )
                        .setEndpoint ( endpoint )
                        .setIdentityProvider ( new AnonymousProvider () )
                        .setRequestTimeout ( timeout )
                        .setMessageLimits ( messageLimits )
                        .build ();

    } else if ( Props.USER_AUTHENTICATION_MODE.equals ( Consts.USER_AUTHENTICATION_MODE_3 ) )
    {
        config = OpcUaClientConfig.builder ()
                        .setApplicationName ( LocalizedText.english ( BaseProps.SYSTEM_SERVICE_NAME + " OPC-UA Client" ) )
                        .setApplicationUri ( "urn:opcua:" + BaseProps.SYSTEM_SERVICE_NAME + " OPC-UA Client" )
                        .setCertificate ( this.loader.getClientCertificate () )
                        .setKeyPair ( this.loader.getClientKeyPair () )
                        .setEndpoint ( endpoint )
                        .setIdentityProvider ( new AnonymousProvider () )
                        .setRequestTimeout ( timeout )
                        .setMessageLimits ( messageLimits )
                        .build ();
    }

    this.opcUAClient = OpcUaClient.create ( config );

    return this.opcUAClient;
}

public void destroyClient ()
{
    if ( this.opcUAClient != null )
    {
        try
        {
            this.opcUAClient.disconnect ();
        } catch ( final Exception e )
        {
            final String errTraceMsg = LogMsgUtil.makeErrTraceMessage ( this.getClass (),
                            new Throwable ().getStackTrace () [0].getMethodName () );

            final String errUsrMsg = errTraceMsg + "Error disconnecting";
            final SPNetworkException spNetworkException = new SPNetworkException ( Consts.BUNDLE_ID, ErrCds.ERR_02_0106,
                            LangMessage.getMessage ( ErrCds.ERR_02_0106 ), errUsrMsg );

            SPExceptionHandler.exceptionLogging ( spNetworkException, LOGGER );
        }
    }
}

}
`

If I set KEPServerEx6 securitymode to Sign. It works well.
But If I set that to Sign and Encrypt mode, OPC Client can't connect to Server.

This is the error log.
image

It there any problem in my code?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions