I am new to opc ua and not a pro in java. While setting up a client in java I'm having trouble with the certificate dealing. I want to connect to the server via Basic 256, SignAndEncrypt. As I understand, in this stage of security a certificate, created or loaded by the client, is send to the server, where it must be accepted. The server then sends a certificate back to the client, which then needs to be accepted by the client. Please, correct me, if I'm wrong.
Creating/Loading a certificate on the client side and sending it to the server does already work fine (see code below) and I can then accept it on the server side manually. But after that I'm stuck: How can I see this certificate validation in my code and how can I find the server certificate, let alone accept it?
I used the SampleConsoleClient of opc ua for some orientation during implementation. But in contrast to there, I do not use any user input.
Here's some of my code so far.
Initialization:
try {
client = new UaClient(serverUri);
} catch (final URISyntaxException e) {
throw new InitializationException("The server uri has an invalid syntax.", e);
}
try {
client.setApplicationIdentity(createApplicationIdentity());
} catch (final SecureIdentityException e) {
throw new InitializationException(
"Application Identity could not be created due to a Security Identity Exception.", e);
} catch (final IOException e) {
throw new InitializationException("Application Identity could not be created due to an IO Exception.",
e);
}
createApplicationIdentity():
final ApplicationDescription appDescription = new ApplicationDescription();
appDescription.setApplicationName(new LocalizedText(APPLICATION_NAME, Locale.ENGLISH));
appDescription.setApplicationUri(APPLICATION_URI);
appDescription.setProductUri(PRODUCT_URI);
appDescription.setApplicationType(ApplicationType.Client);
// Setting security features
client.setSecurityMode(SecurityMode.BASIC256_SIGN_ENCRYPT);
client.setCertificateValidator(validator);
validator.setValidationListener(myValidationListener); //myValidationListener is similar to most lines in MyCertificateValidationListener in the opc ua samples
final File privatePath = new File(validator.getBaseDir(), "private");
final KeyPair issuerCertificate = null;
final int[] keySizes = null;
final ApplicationIdentity identity = ApplicationIdentity.loadOrCreateCertificate(appDescription,
"Sample Organisation", "opcua", privatePath, issuerCertificate, keySizes, true);
identity.setApplicationDescription(appDescription);
return identity;
After initializing, I try to connect like this (with annotation, how I imagine the connection could work properly):
final String securityPolicy = client.getEndpoint() == null
? client.getSecurityMode().getSecurityPolicy().getPolicyUri()
: client.getEndpoint().getSecurityPolicyUri();
client.setSessionName(String.format("%s#%s/Session%d", APPLICATION_NAME,
ApplicationIdentity.getActualHostNameWithoutDomain(), ++sessionCount));
try {
//Idea: catch the server certificate and accept it. Only if that was possible: connect
client.connect();
} catch (final ServiceException e) {
e.printStackTrace();
}
client.setKeepSubscriptions(false);
// After that resolving namespace index (works fine)
}
And the error, that is thrown:
WARN (?:?): /<IPofServer> Error org.opcfoundation.ua.common.ServiceResultException: Bad_SecurityChecksFailed
(0x80130000) "An error occurred verifying security." at
org.opcfoundation.ua.transport.tcp.io.TcpConnection$ReadThread.run(Unknown Source)
com.prosysopc.ua.client.ConnectException: Failed to create secure channel to server: : opc.tcp://<IPofServer>
[http://opcfoundation.org/UA/SecurityPolicy#Basic256,SignAndEncrypt]
ServiceResult=Bad_SecurityChecksFailed (0x80130000) "An error occurred verifying security."
at com.prosysopc.ua.client.UaClient.n(Unknown Source)
at com.prosysopc.ua.client.UaClient.connect(Unknown Source)
at *lineOfCode*
Caused by: org.opcfoundation.ua.common.ServiceResultException:
Bad_SecurityChecksFailed (0x80130000) "An error occurred verifying security."
at org.opcfoundation.ua.transport.tcp.io.TcpConnection$ReadThread.run(Unknown Source)
With the lineOfCode being client.connect().
Thanks in advance for the help!!
The server will send it's certificate to the client. The client then has to
verify the validity of the certificate. This amounts to verifying the signature of the certificate, checking the validity, whether the hostname in the certificate matches the hostname in the endpoint, checking CRLs and so forth. Usually the SDK (the validator) should do this for you, but you might need to feed some parameters into the validator which checks should actually be performed. The security policy Basic256 imposes some minimal requirements on the certificate which the certificate should meet, of course. You can check the requirements here: http://opcfoundation-onlineapplications.org/profilereporting/ -- go to Security Category -> Facets -> Security policy.
check whether the server certificate is trusted. This usually amounts to checking whether a copy of the (puclic key) certificate has been put into some certicate store chosen as a trust store. If you write the client it's up to you to say which store to choose, but you will need to tell the validator where to look. I don't know that much about OPc UA development in Java, but you should check which certificate stores the validator expects. Maybe there is a default keyfile.
(On server side the same happens with the client certificate).
This asssumes you are starting out with self-signed certificates. If you are using certificates signed by a CA both applications (server and client) will need to be able to verify the whole chain of the other party. It can be stored locally in some store or can be send by the other party. At least one certificate in the chain has to be trustest (has to be put into the trust store).
For a general description on how UA security works have a look at this link:
https://opcfoundation.org/wp-content/uploads/2014/08/11_OPC_UA_Security_How_It_Works.pdf
For a detailed account you should consult the specification, available at GitHub.
Edit: one addtional remark which may help here: you seem to be using some SDK for the purpose in question. While validation of certificates, i.e. doing the signature checks etc, is usually covered by such an SDK the configuration of the application is the task of the application (programmer). This includes the location where you store trusted certificates and where and how you gather together missing parts of certificate chains. You might first try to check how demo clients and servers deal with this task, in other words check out the configuration tasks for such applications by trying to create a secure connection from, say, UA Expert to the sample servers from the OPC foundation. In the .Net SDK of the OPC foundation the location for the trust store defaults to a certain directory in the file system (a subfolder of C:\ProgramData\OpcFoundation, it's Windows only). You can, however, overwrite this when you initialize the validator. Other clients use their own directory structure for the storage of trusted certificates
You are obviously referring to the Prosys OPC UA Java SDK.
What always happens first, when you try to establish a secure connection for the first time, is that the server will deny access to your client application - and returns Bad_SecurityChecksFailed.
Only, after you have told the server to trust (the certificate of) your client application, will you get to the phase where the client application will try to verify the server's certificate - and your 'validationListener' will be triggered.
Thank you all for your answers. In the meantime, I tried basically copy/paste and modifying the connect() and initalize() methods from the SampleConsoleClient in the Prosys SDK samples And that worked. Guess it had something to do with renewing some information, but I'm not quite sure of that... Fact is, my application is now working, but thanks for your efforts!
Related
I challenged myself to create a minecraft server with the proviso that most of the plugins I will use will be just mine. I have already made many different plugins from shops to various minigames. Now I have decided to code my own login registration plugin. Basically everything is already functional and ready, but I would like to add one feature there. This feature is that when a player connects to the server (warez), my plugin checks the player's session to see if it is original (I mean that player have bought game and he is logged throught original mojang launcher with valid mojang session. Players should connecting to server in warez mode (not logged in, using not official launcher)). I don't just mean the name as mentioned here, but his mojang session. In short, whether it is logged in via the original launcher with its e-mail and password.
I absolutely don't know how verifying the originality of players works, I know that mojang has an API but I don't know if it offers such a possibility. I would also like to know if the client is sending a hash from the UUID or sessionID to the server, which can be compared with the mojang API, or I don't know.
UPDATE:
I found these two articles:
1) https://wiki.vg/Protocol#Login
2) https://wiki.vg/Protocol_Encryption#Authentication
From this articles I roughly understood that during the connection of the client I will send the server ID and ciphers together with the server and then the subsequent hash on which they will agree will send the client via POST to the mojang servers and my server should then ask from the mojang if it is logged in on my server the client.
UPDATE2
I thought of the following:
If I programmed my own proxy in the phenomenon to which players would connect and this proxy would reproduce everything to the server. Thus, I would be able to let in who I would like to go and I can also do cross-checks via the mojang page with the server ID and hash. But it would be 3-rd party software, it would not be a plugin.
Modify the spigot itself, by that I mean do the above somewhere at the SSLServerSocket level, where the spigot server receives all the socket and the data from them. There, if I code the bridge over which the data would pass, I am also able to agree with the client SERVER ID, calculate a hash and verify it from the mojang server. But it would still not be within the plugin but in the servers.
Override some of the deep parts of the server mentioned above from the plugin. The plugin would replace some parts of the server after loading.
Now my questions is, how to replace some mentioned parts of the server from plugin? Is good idea to try use reflection (i am noob, with reflection) and replace some functions with my functions, that would calling back to spigot low level code?
Thank you very much for any advice.
Have a nice day.
PS: Sorry for my bad english.
public boolean isCracked(ProxiedPlayer player) {
String name = player.getName();
UUID actualUUID = player.getUniqueId();
String actualUUIDStr = uuid.toString();
String offlineUUIDStr = getMd5("OfflinePlayer:"+name);
if(offlineUUIDStr.equals(actualUUIDStr)) {
return true;
}
return false;
}
// Taken from https://www.geeksforgeeks.org/md5-hash-in-java/
public static String getMd5(String input)
{
try {
// Static getInstance method is called with hashing MD5
MessageDigest md = MessageDigest.getInstance("MD5");
// digest() method is called to calculate message digest
// of an input digest() return array of byte
byte[] messageDigest = md.digest(input.getBytes());
// Convert byte array into signum representation
BigInteger no = new BigInteger(1, messageDigest);
// Convert message digest into hex value
String hashtext = no.toString(16);
while (hashtext.length() < 32) {
hashtext = "0" + hashtext;
}
return hashtext;
}
// For specifying wrong message digest algorithms
catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
I have a java TLS based client/server applications I wrote 2-3 years ago and have been using almost daily since then on a number of different linux installations. I don't do a lot of work in java -- writing this was really an exercise.
Anyway, it's never caused me grief since then, until yesterday, when all of a sudden I cannot authenticate with a server from a Fedora 31 box. The cause was an update to openjdk the day before. Other machines that have not had the update do not have the problem. However, I tried rebuilding using Oracle JDK 13, and the problem is replicated.
Just to be clear:
The server where the exception is thrown has not been updated.
The only clients which now fail are ones connecting from the system which had the jdk update.
Compiling the whole application and testing with a recent Oracle JDK has the problem -- clients cannot connect to servers. So this is something I have to fix or roll back the JRE and use that everywhere. >_<
Authentication is done using PKIX where there's a private CA backing the set of client and server certs, in pkcs12 files generated with keytool. The SSLContext is initialized with a TrustManagerFactor and KeyStore more or less like this:
KeyStore trusts;
TrustManagerFactory tmf;
String trustPassword("abc123");
try (
FileInputStream trustIn =
new FileInputStream(new File("foobar.pkcs12"))
) {
tmf = TrustManagerFactory.getInstance("PKIX");
trusts = KeyStore.getInstance("pkcs12");
trusts.load(trustIn, trustPassword.toCharArray());
tmf.init(trusts);
}
SSLContext sscon = SSLContext.getInstance("TLSv1.2");
sscon.init (
someKeyManager,
tmf.getTrustManagers(),
new SecureRandom()
);
As mentioned, I am no java wizard. However, I have done TLS network programming in other languages, so the concepts are pretty familiar.
The server socket is created:
SSLServerSocket listenSock = sscon
.getServerSocketFactory()
.createServerSocket(port, backlog);
And a client connection:
SSLSocket client = (SSLSocket)listenSock.accept();
SSLParameters sslp = new SSLParameters();
sslp.setProtocols(new String[]{"TLSv1.2"});
client.setSSLParameters(sslp);
try { client.startHandshake(); }
That try is where an exception is now thrown on the server, "Received fatal alert: certificate_unknown".
A jdb dump of the exception:
ex = {
serialVersionUID: 4511006460650708967
java.io.IOException.serialVersionUID: 7818375828146090155
java.lang.Exception.serialVersionUID: -3387516993124229948
java.lang.Throwable.serialVersionUID: -3042686055658047285
java.lang.Throwable.backtrace: instance of java.lang.Object[5] (id=2005)
java.lang.Throwable.detailMessage: "readHandshakeRecord"
java.lang.Throwable.UNASSIGNED_STACK: instance of java.lang.StackTraceElement[0] (id=2007)
java.lang.Throwable.cause: instance of java.net.SocketException(id=2008)
java.lang.Throwable.stackTrace: instance of java.lang.StackTraceElement[0] (id=2007)
java.lang.Throwable.depth: 5
java.lang.Throwable.SUPPRESSED_SENTINEL: instance of java.util.Collections$EmptyList(id=2009)
java.lang.Throwable.suppressedExceptions: instance of java.util.ArrayList(id=2010)
java.lang.Throwable.NULL_CAUSE_MESSAGE: "Cannot suppress a null exception."
java.lang.Throwable.SELF_SUPPRESSION_MESSAGE: "Self-suppression not permitted"
java.lang.Throwable.CAUSE_CAPTION: "Caused by: "
java.lang.Throwable.SUPPRESSED_CAPTION: "Suppressed: "
java.lang.Throwable.EMPTY_THROWABLE_ARRAY: instance of java.lang.Throwable[0] (id=2015)
java.lang.Throwable.$assertionsDisabled: true
}
None of which is helpful to me, meaning I don't understand it.
Where do I go from here? The certs are fine, at least according to keytool, and as mentioned this code had been running for years, accessed frequently without problem.1 I have searched around about the specific error ("certificate unknown") but the results aren't very helpful -- the cert is there, the cert is not expired, the trust was loaded initially without qualm, etc.
This includes regenerating the PKIX stuff with keytool at regular intervals, as the certs are created with a limited lifespan. The key pairs use 2048 bit RSA and -sigalg SHA256withRSA.
As it turns out, you can get some wonderful debug output during the handshake by using:
java --Djavax.net.debug=all ...
More about that: https://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/ReadDebug.html
Since it was only a client system that had the problem post update, it makes sense that it was the client that was the one rejecting the connection. And the reason:
TrustAnchor with subject "CN=myAuthority, C=CA"
does not have keyCertSign bit set in KeyUsage extension
Which, on inspection, it did not, which is a bit surprising because this was the cert used successfully to sign all the (otherwise working) client and server certs. It's also a bit mysterious why this would matter when authenticating, but a little firmer security is not a bad thing.
The ultimate cause was in a script I've been using all this time to regenerate certs, and which was used to create the CA initially. This is part of the parameters passed to keytool when creating the self-signed CA cert:
-ext KU=keyCertSign -ext KU=cRLSign
The cert did have the latter, meaning the previous value was replaced. The correct way to do this is to list them together:
-ext KU=keyCertSign,cRLSign
Replacing the whole flawed at the root PKIX infrastructure wasn't too hard after all that.
I'm trying to test my SSL implementation in unit tests and have one scenario I can't quite understand.
When I connect to a host once and fail, every following connection will fail also, even if it has the correct certificates. I'm assuming somewhere along the way I'd have to flush a cache.
Here is my code, server and client are running locally. I use one jks-File for both trustStore and keyStore. The error occurrs no matter what the initial error was, I'll always get the first error the next time.
If I don't perform the first request the second one works.
If you're wondering what the use case is here, we have some local servers that use https certificates from an internal PKI, when someone misconfigures a server or a certificate, we'd like to be able to change them obviously, without shutting down the whole VM.
//attempt a connection without certificates, will fail
try (final InputStream stream = new URL("https://localhost:" + port).openStream()){
System.out.println(IOUtils.toString(stream, Charset.defaultCharset()));
} catch (IOException e){
System.out.println("Failed to load: " + StackTraceUtil.getStackTrace(e));
}
//copies the jks file to a temporary location
final File jksFile = copyJKSFile();
//ignore host names, running locally, won't use this in production
HttpsURLConnection.setDefaultHostnameVerifier((hostname, sslSession) -> hostname.equalsIgnoreCase("localhost"));
//set the system properties
System.setProperty("javax.net.ssl.keyStore", jksFile.getAbsolutePath());
System.setProperty("javax.net.ssl.keyStorePassword", password);
System.setProperty("javax.net.ssl.trustStore", jksFile.getAbsolutePath());
System.setProperty("javax.net.ssl.trustStorePassword", password);
//this should work now
try (final InputStream stream = new URL("https://localhost:" + port).openStream()){
System.out.println(IOUtils.toString(stream, Charset.defaultCharset()));
} catch (IOException e){
System.out.println("Failed to load: " + StackTraceUtil.getStackTrace(e));
}
Thanks for any help!
So I found a solution and I thought I'd share it in case someone else would have this problem at some point.
The class javax.net.ssl.HttpsURLConnection uses a javax.net.ssl.SSLSocketFactory to load the Key- and TrustStore, which uses a javax.net.ssl.SSLContext internally. When you don't overwrite anything, it uses the default implementation, which loads the files and can't be reset once loaded.
So what I did was not to use the default implementation, but to set my own SSLContext, when I knew the files would change.
final SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, null, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
If you want to use older versions of TLS, the full list should be here
We are using a JAX-WS client over HTTPS to send messages (backed by CXF, which I think uses SSLSocket).
We wish to log the remote certificate details, together with the message details, if the remote certificate is not trusted/invalid.
Initially I hoped we would get a usefull exception, but the interesting exceptions in the stack trace are internal (like sun.security.validator.ValidatorException and sun.security.provider.certpath.SunCertPathBuilderException), so shouldn't really be used, and regardless don't seem to hold the remote certificate.
So my question is, what would be the most tidy way to get the certificate, at the level where I also have the message details (outside the JAX-WS call)?
So far my best guess is to add my own javax.net.ssl.X509TrustManager, which wraps the currently used one, and puts the Certificate on a ThreadLocal, where the caller can lately pick it up. It doesn't seem very tidy, but it's the best that seems possible so far :)
Many thanks for any suggestions!
The main point is that JSSE is doing and hiding all of the things you are looking for, in your question. But luckily it seems that CXF allows some customization.
The idea is to customize the SSLSocketFactory ( http://cxf.apache.org/docs/tls-configuration.html#TLSConfiguration-ClientTLSParameters ) with your own implementation, and this one must create Sockets that come with your own HandshakeCompletedListener. This is this last object which will dump the information that you are looking for, I give you an implementation example :
class CustomHandshakeCompletedListener implements HandshakeCompletedListener {
private HandshakeCompletedEvent hce;
private String cipher;
private Certificate[] peerCertificates = null;
private Principal peerPrincipal = null;
public void handshakeCompleted(HandshakeCompletedEvent hce) {
this.hce = hce;
// only cipersuites different from DH_anon* will return a server certificate
if(!cipher.toLowerCase().contains("dh_anon")) {
try {
cipher = hce.getCipherSuite();
peerCertificates = hce.getPeerCertificates();
peerPrincipal = hce.getPeerPrincipal();
// do anything you want with these certificates and ciphersuite
}
catch(SSLPeerUnverifiedException spue) {
System.err.println("unexpected exception :");
spue.printStackTrace();
}
}
}
There is still some work to achieve your goal, let us know if it's working well this clue.
I'm writing client-side code for Windows Kerberos authentication with a service (logging code omitted):
System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
// System.setProperty("sun.security.krb5.debug", "true");
Package thisPkg = AuthHelper.class.getPackage();
String configPath = Util.getConfigPath(thisPkg, "jaas.conf");
System.setProperty("java.security.auth.login.config", "=" + configPath);
GSSManager manager = GSSManager.getInstance();
GSSName peerName = manager.createName(spn, GSSName.NT_HOSTBASED_SERVICE);
GSSContext context = manager.createContext(peerName, null, null,
GSSContext.DEFAULT_LIFETIME);
context.requestMutualAuth(true); // required
context.requestCredDeleg(true); // required for publish
byte[] serverTokenBytes = new byte[0];
while (!context.isEstablished()) {
byte[] clientTokenBytes = context.initSecContext(serverTokenBytes, 0,
serverTokenBytes.length);
if (clientTokenBytes != null)
socket.send(createClientMessage(clientTokenBytes));
if (context.isEstablished()) break;
Message message = socket.receive();
String serverToken = message.getFirst("SERVERTOKEN").toString();
serverTokenBytes = Base64.decodeBase64(serverToken);
}
Where jaas.conf simply contains:
sp {
com.sun.security.auth.module.Krb5LoginModule required debug=true;
};
I have also set the allowtgtsessionkey registry key as required, and installed JCE Unlimited Strength Jurisdiction Policy Files 7.
The code sometimes works (i.e. mutual authentication is established); however, sometimes it gets stuck for a while at the first call to GSSContext.initSecContext, throwing an exception after about a minute:
Exception in thread "main" GSSException: No valid credentials provided (Mechanism level: Receive timed out)
...
Caused by: java.net.SocketTimeoutException: Receive timed out
...
When I enable Kerberos debugging output (by uncommenting the second line above), I can see that the protocol sometimes gets stuck at line:
getKDCFromDNS using UDP
A Java Kerberos troubleshooting website suggests that this is an issue with the Kerberos authentication server, but I know that the server is up and running, since we have similar code written in C# (using .NET libraries) that never gets stuck.
It seems like the DNS resolution for the Kerberos authentication server is going through some indirection, which is unreliable. If you specify the server explicitly (somewhere at the beginning of your code), it will bypass that redirection:
System.setProperty("java.security.krb5.realm", "<YOUR_KRB_REALM>");
System.setProperty("java.security.krb5.kdc", "<YOUR_KRB_SERVER_ADDR_OR_IP>");
EDIT: It turns out that communication with Kerberos servers was inherently unreliable due to the protocol using UDP, so it had a high chance of failing for servers that are relatively far away. Windows 8 uses TCP by default; to force TCP on previous versions:
XP/2000: In HKLM\System\CurrentControlSet\Control\Lsa\Kerberos, set DWORD MaxPacketSize to 1.
2003/Vista/7: In HKLM\System\CurrentControlSet\Control\Lsa\Kerberos\Parameters, set DWORD MaxPacketSize to 1.
(Note that the same registry directory also needs DWORD AllowTGTSessionKey set to 1 for Kerberos to work at all.)