Eclipse Milo 中的身份验证客户端-服务器

Authentication client-server in Eclipse Milo

在 Eclipse Milo 中,客户端是否可以使用以下身份验证参数连接到服务器:"certificate + private key"? 还有参数 "Security Policy" 和 "Message Security Mode" ?

(如在 UAExpert 客户端中:http://documentation.unified-automation.com/uaexpert/1.4.0/html/connect.html

如果是,那又如何呢?


我有:

是的,目前是可能的,尽管 "easy" 不像现在使用 username/password 那样。

客户端 SDK 公开了一个名为 IdentityProvider 的接口,它在客户端连接时被委托给它,并被赋予端点和服务器随机数。它 return 是一个二元组,包含一个 UserIdentityToken 和一个 SignatureData

您需要为 X509IdentityToken 案例和 return 您的证书(在 X509IdentityToken 中)实现此接口,并证明您拥有它的密钥(在SignatureData).

一旦有了这个 IdentityProvider,您只需在构建 OpcUaClientConfig 对象时通过调用 setIdentityProvider 告诉客户端在配置它时使用它。

因为这有点累,而 SDK 的目的是减轻用户的负担,所以我也将其作为 Milo 的功能票。如果您不能自己实现它,我可以在本周实现它。

Kevin Herron 的帮助下,我使用他的 class X509IdentityProvider.

解决了我的问题

这里是解决代码:

PemFile.java

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;

import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;

public class PemFile {

    private PemObject pemObject;

    public PemFile(String filename) throws FileNotFoundException, IOException {
        PemReader pemReader = new PemReader(new InputStreamReader(new FileInputStream(filename)));
        try {
            this.pemObject = pemReader.readPemObject();
        } finally {
            pemReader.close();
        }
    }

    public PemObject getPemObject() {
        return pemObject;
    }
}

X509IdentityProvider.java

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString;
import org.eclipse.milo.opcua.stack.core.types.enumerated.UserTokenType;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
import org.eclipse.milo.opcua.stack.core.types.structured.SignatureData;
import org.eclipse.milo.opcua.stack.core.types.structured.UserIdentityToken;
import org.eclipse.milo.opcua.stack.core.types.structured.UserTokenPolicy;
import org.eclipse.milo.opcua.stack.core.types.structured.X509IdentityToken;
import org.eclipse.milo.opcua.stack.core.util.SignatureUtil;
import org.jooq.lambda.tuple.Tuple2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class X509IdentityProvider implements IdentityProvider {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final X509Certificate certificate;
    private final PrivateKey privateKey;

    public X509Certificate getCertificate() {
        return certificate;
    }

    public PrivateKey getPrivateKey() {
        return privateKey;
    }

    public X509IdentityProvider(X509Certificate certificate, PrivateKey privateKey) {
        this.certificate = certificate;
        this.privateKey = privateKey;
    }

    public X509IdentityProvider(String certificate, String privateKey) {
        this.certificate = loadCertificateFromDerFile(certificate);

        Security.addProvider(new BouncyCastleProvider());
        KeyFactory kf;
        PrivateKey privateKeyTmp = null;
        try {
            kf = KeyFactory.getInstance("RSA", "BC");
            privateKeyTmp = loadPrivateKeyFromPemFile(kf, privateKey);
        } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeySpecException | IOException e) {
            e.printStackTrace();
        }
        this.privateKey = privateKeyTmp;

    }

    @Override
    public Tuple2<UserIdentityToken, SignatureData> getIdentityToken(EndpointDescription endpoint,
            ByteString serverNonce) throws Exception {
        UserTokenPolicy tokenPolicy = Arrays.stream(endpoint.getUserIdentityTokens())
                .filter(t -> t.getTokenType() == UserTokenType.Certificate).findFirst()
                .orElseThrow(() -> new Exception("no x509 certificate token policy found"));
        String policyId = tokenPolicy.getPolicyId();
        SecurityPolicy securityPolicy = SecurityPolicy.Basic256;
        String securityPolicyUri = tokenPolicy.getSecurityPolicyUri();
        try {
            if (securityPolicyUri != null && !securityPolicyUri.isEmpty()) {
                securityPolicy = SecurityPolicy.fromUri(securityPolicyUri);
            } else {
                securityPolicyUri = endpoint.getSecurityPolicyUri();
                securityPolicy = SecurityPolicy.fromUri(securityPolicyUri);
            }
        } catch (Throwable t) {
            logger.warn("Error parsing SecurityPolicy for uri={}", securityPolicyUri);
        }
        X509IdentityToken token = new X509IdentityToken(policyId, ByteString.of(certificate.getEncoded()));
        SignatureData signatureData;
        ByteString serverCertificate = endpoint.getServerCertificate();
        byte[] serverCertificateBytes = serverCertificate.isNotNull() ? serverCertificate.bytes() : new byte[0];
        byte[] serverNonceBytes = serverNonce.isNotNull() ? serverNonce.bytes() : new byte[0];
        assert serverCertificateBytes != null;
        assert serverNonceBytes != null;
        byte[] signature = SignatureUtil.sign(securityPolicy.getAsymmetricSignatureAlgorithm(), privateKey,
                ByteBuffer.wrap(serverCertificateBytes), ByteBuffer.wrap(serverNonceBytes));
        signatureData = new SignatureData(securityPolicy.getAsymmetricSignatureAlgorithm().getUri(),
                ByteString.of(signature));
        return new Tuple2<>(token, signatureData);
    }


    private static X509Certificate loadCertificateFromDerFile(String filename) {
        InputStream in;
        X509Certificate cert = null;
        try {
            in = new FileInputStream(filename);

            CertificateFactory factory = CertificateFactory.getInstance("X.509");
             cert = (X509Certificate) factory.generateCertificate(in);
        } catch (FileNotFoundException | CertificateException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return cert;
    }

    private static PrivateKey loadPrivateKeyFromPemFile(KeyFactory factory, String filename)
            throws InvalidKeySpecException, FileNotFoundException, IOException {
        PemFile pemFile = new PemFile(filename);
        byte[] content = pemFile.getPemObject().getContent();
        PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content);
        return factory.generatePrivate(privKeySpec);
    }

}

ClientRunner.java

...
...

private OpcUaClient createClient() throws Exception {
        SecurityPolicy securityPolicy = clientExample.getSecurityPolicy(); // For example : SecurityPolicy.Basic256
        String securityMode = clientExample.getSecurityMode(); // For example : "SignAndEncrypt"

        EndpointDescription[] endpoints = UaTcpStackClient.getEndpoints(endpointUrl).get();

        EndpointDescription endpoint = Arrays.stream(endpoints)
                .filter(e -> e.getSecurityPolicyUri().equals(securityPolicy.getSecurityPolicyUri()))//
                .filter(e -> e.getSecurityMode().toString().compareTo(securityMode) == 0)//
                .findFirst()//
                .orElseThrow(() -> new Exception("no desired endpoints returned"));

        logger.info("Using endpoint: {} [{}]", endpoint.getEndpointUrl(), securityPolicy);

        loader.load();

        // Mode : securityPolicy == SecurityPolicy.Basic256 && securityMode.compareTo("SignAndEncrypt") == 0)
        X509IdentityProvider x509IdentityProvider = new X509IdentityProvider("/certificate.der",
                "/privateKey.pem");
        X509Certificate cert = x509IdentityProvider.getCertificate();
        KeyPair keyPair = new KeyPair(cert.getPublicKey(), x509IdentityProvider.getPrivateKey());
        OpcUaClientConfig config = OpcUaClientConfig.builder().setApplicationName(LocalizedText.english("opc-ua client"))//
                .setApplicationUri("urn:opcua client")//
                .setCertificate(cert)//
                .setKeyPair(keyPair)//
                .setEndpoint(endpoint)//
                .setIdentityProvider(x509IdentityProvider)//
                .setIdentityProvider(clientExample.getIdentityProvider())//
                .setRequestTimeout(uint(5000))//
                .build();


        return new OpcUaClient(config);
    }