I am trying to load & use a self-signed, dynamically generated PEM (or DER) X.509 certificate from a file and use that to load content via HTTPClient or HttpsURLConnection in order to inject it into a WebView.
I have searched for how to use a WebView with a custom certificate, but apparently there is only the possibility to ignore the SSL error completely (which is not what I want to do - I am looking for certificate validation).
Coding along the lines of this Android developers training I always get exceptions from the key functions: TrustManagerFactory, KeyStore and CertificateFactory.
For TrustManagerFactory:
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
-> Unhandled exception type NoSuchAlgorithmException in getInstance()
For KeyStore I tried the syntax from the tutorial as well as syntax as described in the documentation:
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
-> Unhandled exception type KeyStoreException in getInstance()
and
//Init with empty KeyStore
KeyStore keyStoreData;
//Use no protection for key store since it contains public cert
KeyStore.ProtectionParameter keyStoreProtect = null;
//Use default type
String keyStoreType = KeyStore.getDefaultType();
KeyStore.Builder builder = KeyStore.Builder.newInstance(keyStoreType, null, keyStoreProtect);
keyStoreData = builder.getKeyStore();
-> Unhandled exception type KeyStoreException in getKeyStore()
For CertificateFactory:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
-> Unhandled exception type CertificateException
In conclusion, I cannot use any of the functions required for the job because they all fail with exceptions even though I am using default tutorial code (which should be still valid according to documentation).
Switching the IDE (tried Android Studio and Eclipse with ADT), switching from OpenJDK to Oracle JDK, nothing seems to help here.
Also, similar questions on StackOverflow and on the Net are all answered with the use of one or more of these functions - so they don't help me.
If anybody knows what I am doing wrong, I would be glad for any help!
Related
I am receiving the following String from a certificate stored in Azure Key Vault. I am using the Secret API in order to retrieve both the certificate and the private key related to this cert.
Initially the certificate was uploaded using a .pfx file to Azure Key vault. Now I need to create a Certificate and a PrivateKey to allow client authentication to a 3rd party system and I am using the given String retrieved from the API, however I am note sure how to get around that in Java.
I took some hints from this link in C# however I am pretty certain that this method doesn't work like that in Java. In particular an X509Certificate or a Certificate in general doesn't hold any information about the PrivateKey in Java, unlike C#, and I am not sure how to extract that information from given String in Java.
This works as expected to retrieve the certificate from the String retrieved from the API
String secret = azureSecret.getValue();
byte[] certkey = Base64.getDecoder().decode(secret);
ByteArrayInputStream inputStream = new ByteArrayInputStream(certkey);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(inputStream);
The azureSecret.getValue() format is like the following however I am not sure how to get PrivateKey out of the given String
MIIKvgIBaaZd6Euf3EYwYdHrIIKYzCC...
YES, Java X509Certificate and Certificate is only the certificate. Instead use KeyStore which can contain multiple entries each of which is either a 'trusted' certificate (for someone else), or a privatekey plus certificate plus other chain cert(s) (if applicable) for yourself, or (not relevant here) a 'secret' (symmetric) key. PKCS12 is supported as one type of KeyStore along with others not relevant here, so after the base64-decoding you already have do something like:
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(inputstreamfromvaultvalue, password);
// then
PrivateKey pkey = (PrivateKey) ks.getKey(alias, password);
// and
Certificate cert = ks.getCertificate(alias); // if you only need the leaf cert
// or
Certificate[] chain = ks.getCertificateChain(alias); // usually
But if you want to do client authentication in TLS/SSL (including HTTPS), you give the JSSE KeyManager the whole keystore object not the individual pieces (privatekey and certificates). Similarly to verify the peer in TLS/SSL, you give TrustManager a keystore containing trusted certificates, usually root CAs and often defaulted to a built-in set of public root CAs.
Im trying to validate self-signed certificate against self-signed root CA (given by service provider who will make calls to my service). Now, if i enable two way ssl all works fine but SSL requirement is globally enforced. I need to use it just for one request, and for one user. Other services should not be affected and since there is no way (unless im mistaken) of enabling it for specific paths, i deactivated two ssl and instead, in my controller im doing this:
String auth = request.getHeader("Authorization");
X509Certificate[] cert = (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
ResourceLoader resourceLoader = new DefaultResourceLoader();
try {
KeyStore trustStore = KeyStore.getInstance("JKS");
InputStream certInStream = resourceLoader.getClassLoader().getResourceAsStream("keystore/CaCertificate.jks");
trustStore.load(certInStream, "changeit".toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
trustManagerFactory.init(trustStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
for (TrustManager tm: trustManagerFactory.getTrustManagers())
((X509TrustManager)tm).checkClientTrusted(cert, "RSA");
}
Thus my question is, is this method of validating certificate same as default?
Another thing is that unless i configure the following correctly:
server.ssl.trust-store=keystore/truststore.jks
server.ssl.trust-store-password=pass
server.ssl.trust-store-type= JKS
server.ssl.client-auth=want
then getAttribute method returns null. And i very much want to know why it happens. So the certificate still goes validation but request doesnt fail and its attrbute(SSL one) is not set if client certificate is not trusted? So essentially the code i wrote is useless and i can just check if following is null?
X509Certificate[] cert = (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
please help im thoroughly lost.
EDIT:
For testing purposes im using .net 4.5 HttpWebRequest. With this:
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) =>
{
return true;
}
I'm integrating cxf webservices,no spring, messages are signed. As a guide I've used this tutorial CXF Security.
I want to store x509 certifications or the keystore in the db.
I've overridden WSS4JOutInterceptor.loadCryptoFromPropertiesFile method.
#Override
protected Crypto loadCryptoFromPropertiesFile(String propFilename, RequestData reqData) throws WSSecurityException {
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(...);
X509Certificate certificate = (X509Certificate) ks.getCertificate("client");
X509Certificate[] certificates = { certificate};
CertificateStore cs = new CertificateStore(certificates);
return cs;
}
But a few issues here:
1. There's an error like
Caused by: java.lang.NullPointerException: provided null name at
javax.security.auth.x500.X500Principal.(X500Principal.java:172)
I've checked and subjectDN is not null
2. I'm not sure if this a way to go and should I attach somehow private key to sign the message
The error is because CertificationStore doesn't support aliases
The better way would be to implement custom provider by extending CryptoBase
I apologize that this is so long. If you are familiar with doing client-auth in Java you can probably skim/skip to the bottom. I took me a long time to find all the relevant bits of information from disparate sources. Maybe this will help someone else.
I'm working on a proof-of-concept to get client-side authentication working from a Java client using the Windows keystore. I have a servlet that I have created that can request or require client certificates. It simply returns an HTML response containing the certificate information including subject DN and the serial number. I've worked through a number of experiments with browsers (mainly Chrome and IE) to verify it is working. I've gotten successful client authentication working with both certs I've generated with SSL and also using certs issued by my companies internal CA.
My next step is to have a Java client that works with the MSCAPI keystore in Java. The reason I went this route is that the certificates we want to use are issued by our internal CA and are automatically added to the Personal keystore in Windows and are marked as non-exportable. I know there are ways to get the private key out of the keystore with some free tools if you are an admin on your workstation. This isn't a viable option in this case for various reasons. Here's the code I ended up with:
private static SSLSocketFactory getFactory() throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException, UnrecoverableKeyException, KeyManagementException
{
KeyStore roots = KeyStore.getInstance("Windows-ROOT", new SunMSCAPI());
KeyStore personal = KeyStore.getInstance("Windows-MY", new SunMSCAPI());
roots.load(null,null);
personal.load(null,null);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(roots);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(personal, "".toCharArray());
SSLContext context = SSLContext.getInstance("TLS");
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return context.getSocketFactory();
}
This works but when I connect, the Java client is authenticating with the client-certs I had previously generated. I don't see an obvious way to force the selection of a specific key so I figured it was just picking the first key it came across that the host trusts. I then removed these certs that I had created from the windows keystore and it would no longer authenticate. I triple-checked that the server-side was still works with the browsers and it does. I then readded these certs to the personal keystore and removed the trust of my pseudo-CA from the server-side. Still works in the browser and not in the client.
I added -Djavax.net.debug=ssl:handshake to my VM parameters and started looking through the output. One thing I see is are lines "found key for : [alias]" for each of the certs that I have added but not for the ones added through the 'self-enrollment' feature of certmgr.msc. Initially I thought it was because they were exportable but I removed them and added one back as non-exportable and java can still use it. I'm at a loss as to why SunMSCAPI won't use these certs. Both the ones created by me and internal CA are sha256RSA. Both are enabled for client authentication. When I add the following code, I see that isKeyEntry() is false for all the key-pairs I want to use:
Enumeration<String> aliases = personal.aliases();
while (aliases.hasMoreElements())
{
String alias = aliases.nextElement();
System.out.println(alias + " " + personal.isKeyEntry(alias));
}
I found someone else with a similar issue here but no definitive answer.
I was trying to open a crt certificate in java and hence thereby get some parameters from the crt. I used the following code:
inStream = new FileInputStream("sbi.crt");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream);
When I run the code, the compiler throws certificate exception at line 2. Is it because of the certificate that I'm using? Or what changes in code should I make?
Or is there any alternative way to access a certificate and extract the details?
When I run the code, the compiler throws certificate exception at line 2.
No it doesn't. The compiler doesn't throw exceptions. It prints compilation errors.
Is it because of the certificate that I'm using?
No.
Or what changes in code should I make?
Catch the exception named in the error message, or declare it to be thrown by the method.