Seldomly, I'm getting this exception:
javax.crypto.AEADBadTagException: Tag mismatch!
That's my http client configuration:
SSLContext sslContext = SSLContextBuilder.create()
.loadTrustMaterial(
new URL(visorApiProperties.getTrustStore()),
visorApiProperties.getTrustStorePassword().toCharArray()
)
.build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
.register("https", sslsf)
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.build();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
httpClientBuilder.setConnectionManager(cm);
I don't quite figure out what does it mean.
I've took a look on other stackoverflow questions, but I don't quite figure out what would I do in order to solve this problem.
Any ideas?
For me problem was in sertificate.
If you try execute this curl result will be the same.
curl -I -vv --cert sertificate_location --cert-type p12 --pass password url
OpenSSL SSL_read: error:0A000119:SSL routines::decryption failed or bad record mac, errno 0
The exception javax.crypto.AEADBadTagException means that the data could not be encoded and decoded using authenticated encryption with additional data (AEAD). "Tag mismatch!" indicates that the tag value (the value added to the encrypted data to allow for data integrity verification after decryption) is inconsistent between the sending and receiving sides.
To solve this problem, the following should be verified:
Is the encryption key the same on both sides?
Are there any issues with the trust certificates in the configured trust store?
Is the version of the javax.crypto library compatible on both sides?
Are the sending side and receiving side using the same encryption mode and encryption settings?
Related
I know my way around Java but I'm fairly inexperienced when it comes to the topic of SSL certificates. So my whole approach may be complete and utter nonsense.
What I'm trying to achieve is the following: I have a webservice build with Apache CXF in a Spring Boot application. That webservice is called a x509 client certificate and I want to use that certificate to get a JWT from a Keycloak instance which I have already configured. Getting the token from keycloak works when I do it like this:
var keystore = KeyStore.getInstance("PKCS12");
keystore.load(new ClassPathResource("keystore.pfx").getInputStream(), "myPassphrase".toCharArray());
HttpClient client = HttpClients.custom().setSSLContext(new SSLContextBuilder()
.loadTrustMaterial(new ClassPathResource("truststore.jks").getFile(), "trustStorePW".toCharArray())
.loadKeyMaterial(keystore, "myPassphrase".toCharArray())
.build()
).setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build();
var postRequest = new HttpPost("https://localhost:8443/realms/master/protocol/openid-connect/token");
postRequest.addHeader("Content-Type", "application/x-www-form-urlencoded");
var entity = new StringEntity("grant_type=password&client_id=my-client&client_secret=my-secret");
postRequest.setEntity(entity);
var response = client.execute(postRequest);
That's fine and tells me that my keycloak setup is correct. What I'm trying to do now is to extract the certificate from the SOAP request and forward it to the keycloak call. I've done this with an interceptor and pull the certificate from the request like this:
if (httpRequest instanceof HttpServletRequest request
&& request.getAttribute("javax.servlet.request.ssl_session_mgr") instanceof SSLSupport sslSupport
&& sslSupport.getLocalCertificateChain() != null
) {
for (var cert : sslSupport.getLocalCertificateChain()) {
var keystore = KeyStore.getInstance("PKCS12");
keystore.load(null, "passphrase".toCharArray());
keystore.setCertificateEntry("1", cert);
// then use the same code as before for calling keycloak
}
}
But now I get {"error_description":"X509 client certificate is missing.","error":"invalid_request"}. Looking into it I realize that the main difference between the two keystores is that the one created from the HttpRequest does not contain the private key, so I suspect that's the reasons it doesn't work. The keystore.pfx I used in the first example contains only the client certificate btw.
Is there a way to make it work like this or is my whole approach completely wrong? Because that's what I'm starting to suspect. And if so, how could I solve this?
I want to make a HTTP call to server that uses TLS to authenticate. Moreover server needs my IP to be whitelisted, so with AWS Lambda I need to use proxy. What I want to achieve is HTTP POST request with TLS that goes through proxy.
To achieve TLS protocol I use KeyStore with loaded certs and private key.
Making a call without proxy (locally from whitelisted IP) works, so I assume keyStore is configured correctly.
Here is how I build httpClient (it's java.net.http.HttpClient):
var keyManagerFactory = KeyManagerFactory.getInstance("PKIX");
keyManagerFactory.init(keyStore, null);
var trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
trustManagerFactory.init(keyStore, null);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
URI proxyUri = config.getProxyUri(); // this is injected object with preloaded config parameters
HttpClient httpClient = HttpClient.newBuilder()
.sslContext(sslContext)
.proxy(
ProxySelector.of(
InetSocketAddress.createUnresolved(proxyUri.getHost(), proxyUri.getPort())))
.build();
Now making a request:
String body = createRequestBody(); // creates string with JSON
HttpRequest request = HttpRequest.newBuilder()
.uri(config.getServiceUri()) // same config as in example above
.header("Content-Type", "application/json")
.POST(BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = httpClient.send(request, BodyHandlers.ofString());
Calling .send(...) causes
java.io.IOException: Tunnel failed, got: 403
# java.net.http/jdk.internal.net.http.HttpClientImpl.send(Unknown Source)
# java.net.http/jdk.internal.net.http.HttpClientFacade.send(Unknown Source)
# (method we are in above example)
Proxy doesn't need any authentication and in other AWS Lambda I've seen this proxy working with builder using only .proxy(...) method just like in the example above. So the only thing that is different is this .sslContext(...).
Do I need some more sslContext configuration? I've been searching for some examples with TLS through proxy, but I've not managed to find anything.
HttpClient.Builder Docs doesn't say anything about proxy with sslContext either.
Thanks for help!
As daniel wrote in a comment
It would seem that you have insufficient permission to access the service you're trying to use
It turned out to be proxy config that was blocking traffic to that specific host and port.
There is nothing wrong with the code above. After a change in proxy settings to it works as expected.
Thanks for help!
Since I need to work with some legacy server, and since RC4 was removed from the Java 8, I need to re-enable some RC4 based ciphers. As described in the release note we have to use SSLSocket/SSLEngine.setEnabledCipherSuites(). Since I'm using Apache HTTP Client I was not able to find a way to do this. Thanks in advance! (I also found quite semitrailer problem with out an answer so thought of posting a new one)
I was facing the same problem and I was able to figure this out.
SecureProtocolSocketFactoryImpl protFactory = new SecureProtocolSocketFactoryImpl();
httpsClient.getHostConfiguration().setHost(host, port, httpsProtocol);
In the "SecureProtocolSocketFactoryImpl" class you have to override the method public Socket createSocket() for SecureProtocolSocketFactory class.
In that method you will get a socket like this
SSLSocket soc = (SSLSocket) getSSLContext().getSocketFactory().createSocket(
socket,
host,
port,
autoClose
);
So there you will be able to do something like below.
ciphersToBeEnabled[0] = "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA";
soc.setEnabledCipherSuites(ciphersToBeEnabled);
hope you get the idea. If you have any problems please comment below. Note that doing this only will not enable RC4 related ciphers. You will need to modify java "java.security" file in jre/lib/security/ file and remove CR4 form the disabled algorithm list.
The recommended way to get the HttpClient is by using HttpClientBuilder. In this builder, you can set the HttpClientConnectionManager which in turn can take a Registry<ConnectionSocketFactory>. In this ConnectionSocketFactory, you can configure ciphers and protocols that the client want to restrict.
Sample Code:
Registry<ConnectionSocketFactory> socketFactoryRegistry;
{
SSLContext sslcontext = <your SSLContext>;
socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", new PlainConnectionSocketFactory())
.register("https", new SSLConnectionSocketFactory(sslcontext,
<your supported protocols, could be null>,
<your supported ciphers, could be null>,
<your HostnameVerifier>
.build();
}
HttpClientBuilder b = HttpClientBuilder.create()
.setConnectionManager(new BasicHttpClientConnectionManager(socketFactoryRegistry))
.set<anything else you want>(<with what you want>);
HttpClient client = b.build();
I have been attempting to have my jersey client do a ssl client authentication with my Jersey/Grizzly Rest api. Other clients are successful handshaking with this server, but I am having trouble with my java client using Jersey client. When I run the code below, the keystore is successfully loaded and when the SslConfigurator's createSSLContext() is called, the ssl debug output shows this keystore properly being accessed and my private keys found.
However, when the Client's WebTarget is used, the ssl debug output shows the handshake is happening with the default keystore JKS. Why isn't the ClientBuilder using this keystore from the SSLContext?
File tmpConfigFile = File.createTempFile("pkcs11-", "conf");
tmpConfigFile.deleteOnExit();
PrintWriter configWriter = new PrintWriter(new FileOutputStream(tmpConfigFile), true);
configWriter.println("name=ActiveClient");
configWriter.println("library=\"C:\\\\Program Files\\\\ActivIdentity\\\\ActivClient\\\\acpkcs211.dll\"");
configWriter.println("slotListIndex=0");
SunPKCS11 provider = new SunPKCS11(tmpConfigFile.getAbsolutePath());
Security.addProvider(provider);
// KeyStore keyStore = KeyStore.getInstance("PKCS11", provider);
KeyStore keyStore = KeyStore.getInstance("PKCS11");
keyStore.load(null, null);
ClientConfig config = new ClientConfig();
SslConfigurator sslConfig = SslConfigurator.newInstance()
.keyStore(keyStore)
.keyStorePassword("mypin")
.keyStoreType("PKCS11")
.trustStoreFile(TRUSTORE_CLIENT_FILE)
.trustStorePassword(TRUSTSTORE_CLIENT_PWD)
.securityProtocol("TLS");
final SSLContext sslContext = sslConfig.createSSLContext();
Client client = ClientBuilder
.newBuilder().hostnameVerifier(new MyHostnNameVerifier())
.sslContext(sslContext)
.build();
WebTarget target = client.target("https://localhost:8443/appname/resources/employees?qparam=something");
Response res = target.request().accept(MediaType.APPLICATION_JSON).get();
This code actually worked. The problem was that my server's trust certificate wasn't available for the smart card cert that it needed to trust. I added the correct certs to the truststore on the server and then it worked. The ssl debug messages weren't very clear.
I've run into many issues this time and I found a way to achieve my goals. In your example I can not see any use of ClientConfig config instance. This worked for me:
ClientConfig config = new ClientConfig();
config.connectorProvider(new ApacheConnectorProvider());
Client client = ClientBuilder.newBuilder().hostnameVerifier(new MyHostnNameVerifier())
.sslContext(sslContext).withConfig(config).build();
I found ApacheConnectorProvider more suitable for connections using secure layers or proxies (witch was another huge problem I solved).
I have been unable to find a solution to this problem elsewhere so I am hoping someone here can provide some insight. My setup below:
keystore, myKeys.jks:
mine-private, 3/6/2014, PrivateKeyEntry
mine-trusted, 3/6/2014, trustedCertEntry
trust store, myTrust.jks:
trusted-cert-1, 3/6/2014, trusterCertEntry
trusted-cert-2, 3/6/2014, trusterCertEntry
mine-trusted, 3/6/2014, trustedCertEntry <-- this is mine
What ends up happening is I get a message stating that my client has not been authenticated. Let me know if there is more information necessary
Responses to questions:
First off: what classes/library are you using? Simply the default https in java?
Apache HTTP Client, code below:
HttpClient client = new HttpClient();
GetMethod method = new GetMethod("https://foo.bar.baz/rest");
client.executeMethod(method);
Secondly: how exactly are you registering the keystore/truststore? You need a custom SSLContext for this.
Don't think so, but could be wrong
-Djavax.net.ssl.trustStore="path/to/myTrust.jks"
-Djavax.net.ssl.trustStorePassword="password"
-Djavax.net.ssl.keyStore="path/to/myKeys.jks"
-Djavax.net.ssl.keyStorePassword="password"
First off: what classes/library are you using? Simply the default https in java?
Secondly: how exactly are you registering the keystore/truststore? You need a custom SSLContext for this.
Initial example:
SSLContext context = SSLContext.getInstance();
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyFactory.init(keyStore, password);
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(trustStore);
context.init(keyFactory.getKeyManagers(), trustFactory.getTrustManagers(), null);
Most libraries that I know support setting a custom SSLContext or SSLSocketFactory which can be obtained from the context.
I have written a sample that does the exact same thing. You can find the particular code in [1].
[1] https://github.com/wso2/carbon-identity/blob/v5.0.7/components/authentication-framework/org.wso2.carbon.identity.application.authentication.endpoint.util/src/main/java/org/wso2/carbon/identity/application/authentication/endpoint/util/TenantMgtAdminServiceClient.java#L155