Eclipse Milo中是否有可能客户端使用此身份验证参数连接到服务器:“certificate + private key”? 还有参数“安全策略”和“消息安全模式”?
(如在UAExpert客户端中:http://documentation.unified-automation.com/uaexpert/1.4.0/html/connect.html)
如果是,那怎么样?
我随时可以使用:
答案 0 :(得分:1)
是的,它目前是可能的,虽然现在不像使用用户名/密码那样“轻松”。
客户端SDK公开了一个名为IdentityProvider
的接口,该接口在客户端连接时被委派给它,并被赋予端点和服务器随机数。它返回一个包含UserIdentityToken
和SignatureData
的2元组。
您需要为X509IdentityToken
案例实施此界面并返回您的证书(在X509IdentityToken
中),并证明您拥有密钥(在SignatureData
中) )。
获得此IdentityProvider
后,您只需在构建setIdentityProvider
对象时调用OpcUaClientConfig
,即可告知客户端使用它。
由于这有点麻烦,而且SDK的目的是减轻用户的负担,我也会把它作为Milo的功能票。如果你不能自己实现它,我可以在本周开始实现它。
答案 1 :(得分:0)
我已经使用他的班级 X509IdentityProvider 在Kevin Herron的帮助下解决了我的问题。
以下是解决方案代码:
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);
}