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.
Related
I have a code which calls a rest api protected with certificate, and that code was working for some time without issues, until I migrate application from Wildfly 10 to Wildfly 16.
The code itself is straight forward, it creates http request and set custom socket factory: `
private SSLSocketFactory getSSLSocketFactory() {
char[] certPassword = {}; // password hidden from you
try {
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
final SSLContext sslContext = SSLContext.getInstance("TLS");
final KeyStore keyStore = KeyStore.getInstance("PKCS12");
try (FileInputStream fileInputStream = new FileInputStream(new File("cert.pk12"))) {
keyStore.load(fileInputStream, certPassword);
} catch (final Exception e) {
logger.error("....", e);
}
this.readLoadCertificateFile(keyStore);
kmf.init(keyStore, certPassword);
sslContext.init(kmf.getKeyManagers(), new TrustManager[]{new AnyTrust()}, null);
return sslContext.getSocketFactory();
} catch (Exception e) {
logger.error(".....", e);
}
throw new IllegalStateException("....");
}
HTTPRequest req = ....
req.setSSLSocketFactory(getSSLSocketFactory());
tokenHttpResp = req.send();`
`
All seems good but when I run this code from within WF16 it throws
IOException: Failed to load .p12 keystore:C:\Cert\cert.p12; error
constructing MAC: java.lang.SecurityException: JCE cannot authenticate
the provider BC;
org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi.engineLoad::-1
java.security.KeyStore.load in KeyStore.java::1445
I checked for the bouncy castle lib, and I don't have it in the application itself...
If any one knows what the issue could be I would appreciate any input.
Alternatively, I'm looking to move this socket factory creation in to the container itself, seems like Wildfly Elytron subsystem specifically designed for this, is it a good idea?
Answering my own question.
The error message like "JCE cannot authenticate the provider BC" indicating that the jar file, the Security Provider is loaded from, cannot be verified by JVM. Either the jar is not signed or signature cannot be verified. In my case, the newer Wildfly version has a newer version of bouncy castle library, which for some reason, cannot be verified by Java 8. Interesting enough, that it is fine with Java 10. Some people on the Internet says that this issue only occurs in Oracle's JVM and does not exist for Open JDK, I haven't tested it, just think it worth to mention.
To overcome issue you need to tell JVM to trust the security provider, for that, make sure that the Security Provider you want to use/JVM decided to use, mentioned in jre/lib/security/java.security file, it should have line like:
security.provider.11=org.bouncycastle.jce.provider.BouncyCastleProvider
Then copy Jars with Security Provider into /jre/lib/ext folder
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 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!
In Java Network Programming 4th Edition, Chapter 10 about Secure Socket, there is an example of build Secure Server. The code can be find here.
I'm trying to make more simple version of the code. Here is my code:
try {
SSLContext context = SSLContext.getInstance(algorithm);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
KeyStore ks = KeyStore.getInstance("JKS");
//char[] password = System.console().readPassword("Password: "); // open the .class with command line
char[] password = {'t', 'h', 'i', 's', 'i', 's', 't', 'i', 'a', 'n'};
ks.load(new FileInputStream("src/jnp4e.keys"), password);
kmf.init(ks, password);
context.init(kmf.getKeyManagers(), null, null); // null = accept the default
// wipe the password
Arrays.fill(password, '0');
SSLServerSocketFactory factory
= context.getServerSocketFactory();
SSLServerSocket server
= (SSLServerSocket) factory.createServerSocket(PORT);
// add anonymous (non-authenticated) cipher suites
String[] supported = server.getSupportedCipherSuites();
String[] anonCipherSuitesSupported = new String[supported.length];
int numAnonCipherSuitesSupported = 0;
for (int i = 0; i < supported.length; i++) {
if (supported[i].indexOf("_anon_") > 0) {
anonCipherSuitesSupported[numAnonCipherSuitesSupported++]
= supported[i];
}
}
String[] oldEnabled = server.getEnabledCipherSuites();
String[] newEnabled = new String[oldEnabled.length
+ numAnonCipherSuitesSupported];
System.arraycopy(oldEnabled, 0, newEnabled, 0, oldEnabled.length);
System.arraycopy(anonCipherSuitesSupported, 0, newEnabled,
oldEnabled.length, numAnonCipherSuitesSupported);
server.setEnabledCipherSuites(newEnabled);
System.out.println("OK..");
// Now all the set up is complete and we can focus
// on the actual communication.
while (true) {
// This socket will be secure,
// but there's no indication of that in the code!
try (Socket theConnection = server.accept()) {
InputStream in = theConnection.getInputStream();
Reader r = new InputStreamReader(in, "UTF-8");
int c;
while ((c = r.read()) != -1) {
System.out.write(c);
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
} catch (IOException | KeyManagementException | KeyStoreException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException ex) {
ex.printStackTrace();
}
The only diffence is in my code I create a Reader so the server can read characters.
I tried this server with simple client that send text. Here is the Client:
int port = 7000; // default https port
String host = "localhost";
SSLSocketFactory factory
= (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket socket = null;
try {
socket = (SSLSocket) factory.createSocket(host, port);
// enable all the suites
String[] supported = socket.getSupportedCipherSuites();
socket.setEnabledCipherSuites(supported);
Writer out = new OutputStreamWriter(socket.getOutputStream(), "UTF-8");
out.write("Hello");
}catch(Exception e){
}
I run the Server first and then the Cient. But when the Server accept input from Client it throws this exception:
java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(SocketInputStream.java:189)
at java.net.SocketInputStream.read(SocketInputStream.java:121)
...
UPDATE CLIENT
Based on dave answer, I add 2 lines of code flush() and close()
...
out.write("Hello");
out.flush();
socket.close();
...
But another exception arrive:
javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown
OutputStreamWriter on a socket stream apparently buffers and your client didn't .flush() or .close() so your data isn't actually sent.
If your Java program (or more exactly JVM) exits without doing .close() on a socket stream (including closing a Writer which passes through to the Stream) handling depends on the platform; on Windows it sends a RST which causes the "Connection reset" exception you see at the peer. Unix closes the connection normally at the TCP level, which is actually not fully normal for SSL/TLS, but "close enough" (as it were) that Java treats it as EOF.
Edit for followon question:
Server getting SSLHandshakeException "received alert bad_certificatecertificate_unknown" which theoretically could mean a few things but almost always means that the certificate the server is using (from the keystore you loaded, along with matching privatekey) is not signed by a CA (Certificate Authority) trusted by the client.
The code you show for the client doesn't do anything to set or alter its truststore; if there isn't code elsewhere that does so, or external settings like the java commandline option -Dx=y to set system properties, the client will use the JSSE default truststore, which is the file JRE/lib/security/jssecacerts if it exists otherwise the file JRE/lib/security/cacerts (where JRE is the directory where your JRE is installed; if you use a JDK, the JRE is a subdirectory of the JDK directory). If you (and anyone else on your system) haven't modified these files since the JRE was installed, jssecacerts doesn't exist and cacerts contains a set of "well-known root" CAs determined by Oracle, like Verisign and Equifax etc.
Thus, you need to either:
use a certificate issued by a well-known CA; if you don't already have such a cert you have to obtain it from the CA by proving (at least) your control of the domain name(s) certified and depending on the CA possibly paying a fee; if you do have or get such a cert, install it in your keystore, in the privatekey entry, with any chain certs (for well-known CAs there almost always is at least one chain cert).
use a certificate issued by any other CA, including an adhoc CA you make up, and including as the limit case a selfsigned certificate which is implicitly its own CA, such as the one keytool -genkeypair generates automatically; and put the CA certificate for that CA (or that selfsigned cert) into the truststore used by the client. For that there are two ways:
put the server's CA cert (or selfsigned cert) in the default truststore file of the JRE used by the client. This does affect any other programs sharing that default truststore, which is potentially all other programs using that JRE. If you use jssecacerts it only affects JSSE, if you use cacerts it also affects the verification of signed code (if any), plus it gets wiped out if you upgrade your JRE in place, as usually is automatic on Windows.
create (or reuse) another truststore, put the server's CA cert in there, and have the client use that nondefault truststore. There are several options for that: set the system properties for the default truststore externally, set them explicitly in your program (before first use of JSSE!), explicitly load a "keystore" file (actually containing the cert(s)) and use its trustmanager in a nondefault SSLSocketFactory much like your server code does for keymanager, or even write your own trustmanager with whatever store(s?) you like and use that similarly.
Edit#2 Simple Example
Covering all these options in detail would be much too long, but one simple option is as follows.
Generate keypair and (default) selfsigned certificate:
keytool -genkeypair -keystore ksfile -keyalg RSA
For the prompt "first and last name" (which is actually the CommonName attribute in the cert) enter the name of the server, in particular the name the client(s) will use to connect to the server; in the question this is "localhost". The other name fields don't matter; fill or omit them as you like, except that Country if used must be 2 letters as the prompt says. Instead of answering the prompts, you can add on the command line -dname "CN=common_name_value". If you have more than one name for the server(s) there are some options omitted here.
For some other applications you may need to specify the entry name with -alias name; for this question it isn't needed.
Get a copy of the certificate:
keytool -exportcert -rfc -keystore ksfile [-alias name] -file certfile
In this example the client is on the same machine as the server. In another case it would be necessary to copy the contents of this file from the server to the client; this is often done most conveniently by copying the file.
Put cert in client truststore. As above there are many options, but the one you will probably see suggested the most often because it is usually the quickest to start is to just put it in the JRE default file, cacerts:
keytool -importcert -keystore JRE/lib/security/cacerts -file certfile
where JRE is the directory where your JRE (Java Runtime Environment) is installed. This depends on your OS and how you installed your JRE (or JDK, which includes a JRE) such as with a package manager or not. If you have more than one JRE and/or JDK installed, it depends which one you are using.
On Unix if you invoke java without specifying a path, which java (or in bash and perhaps other shells, type java) will tell you the full pathname that is run. Note however this is often a symbolic link to the real location, which should be in the form /somewhere/java[version]/bin/java; change the bin/java part to lib/security/cacerts.
On Windows, if you install a normal "system-wide" Java, the program you run is %windir%\system32\java.exe where windir is usually c:\windows but the actual code and files for JRE are in c:\program files\java\jreN or c:\program files (x86)\java\jreN depending on your architecture (32-bit or 64-bit) and jreN is currently jre7 or jre8 as applicable, but likely to expand in the future. Or run the Java Control Panel; in the Java tab the View button shows location(s) of the installed JRE(s).
I have a JCEKS keystore to hold my AES keys.
This has been working in the dev environment and in the GAE runtime for a while.
Last night I deployed an update (nothing to do with the crypto cases) and now loading the keystore throws an IOException: com.sun.crypto.provider.SealedObjectForKeyProtector and subsequently none of my crypto works (as you'd expect given I can't get to the keys).
I've Googled the exception - one lead looked promising:
Convert a key of JCEKS of a provider into another store for another provider
... which suggests that a keytore created with one provider cannot be read with another provider, but that doesn't seem to be the case here as it was working yesterday! Also https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Application_Platform/6.2/html/Administration_and_Configuration_Guide/sect-Password_Vaults_for_Sensitive_Strings.html suggests incompatible providers.
I rolled back my app to the previous (working) version, but I get the same error.
Has GAE changed its default provider? Should I explicitly declare the required provider in my code?
Thanks
Steve
Update 20/05/2015 - root cause identified
The problem was the IOException thrown by ks.load() below:
final KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE_JCEKS);
try {
InputStream is = this.getClass().getClassLoader().getResourceAsStream("squirrol.keystore");
ks.load(is, getKeystorePassword().toCharArray()); // IOException thrown here
...
} catch (NoSuchAlgorithmException | CertificateException | IOException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
throw new KeyStoreException("Failed to load KeyStore: " + e.getLocalizedMessage());
}
The stack trace output leads off with:
at com.sun.crypto.provider.JceKeyStore.engineLoad(JceKeyStore.java:844)
Googling the class name has found the source (possibly not the actual source, but the line number in the stack trace aligns). Line 844 suggests this IOException is thrown as the result of a ClassNotFoundException with the message being the name of the class that wasn't found - in this case com.sun.crypto.provider.SealedObjectForKeyProtector:
http://www.docjar.com/html/api/com/sun/crypto/provider/JceKeyStore.java.html
So, root cause is that the Google App Engine runtime v1.9.21 cannot load the keystore because it cannot load the class com.sun.crypto.provider.SealedObjectForKeyProtector which Google admit is a whitelisting issue.
Temporary resolution
As the result of a support ticket, Google have reverted my runtime back to 1.9.20 which doesn't have this problem. I'm awaiting a fix that allows me to get back onto automated engine updates.
Update 04/06/15 - Resolved
Google will have a fix in the v1.9.22 runtime.
Update 11/06/15 - Not Resolved after all
The problem persists in the v1.9.22 runtime :(
Update 30/06/15 - Really resolved & proven
Google fixed it in the v1.9.23 runtime. Answer updated to reflect.
This is confirmed fixed in the GAE runtime 1.9.23 (not 1.9.22 - it missed the cut).
The problem was with the runtime whitelisting, which omitted one or more classes needed to load the JCEKS keystore.
This problem only affects the GAE runtimes 1.9.21 & 1.9.22. Note this is the server engine version, not the SDK version. You can check the server version in the console.