问题
I'm getting a handshake_failed exception when making a RESTful post call to an API that requires mutual authentication. I have verified with an engineer on the other side who is watching a stack trace that the server-side certs are exchanged and accepted by my client without issue, but when the server requests the client-side cert my side doesn't provide that and the handshake terminates.
The thing is I have no idea how to verify whether SSLSocketFactory is unable to identify the right certificate or just isn't looking for it in the first place.
Here is my code, which is js leveraging the Apache Httpclient library.
importPackage(Packages.org.apache.http.client);
importPackage(Packages.org.apache.http.client.methods);
importPackage(Packages.org.apache.http.impl.client);
importPackage(Packages.org.apache.http.message);
importPackage(Packages.org.apache.http.client.entity);
importPackage(Packages.org.apache.http.util);
importPackage(Packages.org.apache.commons.httpclient);
importPackage(Packages.org.apache.http.params);
importPackage(Packages.org.apache.http.ssl);
importPackage(Packages.org.apache.http.ssl.SSLSocketFactory);
importPackage(Packages.java.security.Keystore);
importPackage(Packages.org.apache.http.conn.ssl);
importPackage(Packages.javax.net.ssl);
importPackage(Packages.java.io);
importPackage(Packages.org.apache.http.impl.conn);
importPackage(Packages.org.apache.http.conn.scheme);
//Benchmark 1
var healthMessageStatus = "0";
var xtn_startTime = DateUtil.getCurrentDate('yyyyMMddHHmmss');
channelMap.put('xtn_startTime',xtn_startTime);
var url = 'https://smr.prodposturl.net/AuthenticatingXmlServer.aspx'
//HTTP Connection Template
var httpParams = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParams, 3600000);
HttpConnectionParams.setSoTimeout(httpParams, 3600000);
//APPROACH 3
var KEY_STORE_PATH = "C:\\PHIA\\Certs\\mykeystore";
var KEY_STORE_PASSWORD = "changeit";
var TRUST_STORE_PATH = "C:\\PHIA\\Certs\\cacerts";
var TRUST_STORE_PASSWORD = "changeit";
var keystore = new KeyStore.getInstance(KeyStore.getDefaultType());
channelMap.put('keystore',keystore);
var keystoreInput = new FileInputStream(KEY_STORE_PATH);
keystore.load(keystoreInput, KEY_STORE_PASSWORD.split(''));
var keystoreline = "Keystore has " + keystore.size() + " keys";
// load the truststore
var truststore = new KeyStore.getInstance(KeyStore.getDefaultType());
var truststoreInput = new FileInputStream(TRUST_STORE_PATH);
truststore.load(truststoreInput, TRUST_STORE_PASSWORD.split(''));
var truststoreline = "Truststore has " + truststore.size() + " keys";
channelMap.put('keystoreline', keystoreline);
channelMap.put('truststoreline', truststoreline);
var schemeRegistry = new SchemeRegistry();
var lSchemeSocketFactory = new org.apache.http.conn.ssl.SSLSocketFactory("TLSv1", keystore, KEY_STORE_PASSWORD, truststore, null, org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);//, keystore);
var lSchemeSocketFactoryString = String(lSchemeSocketFactory);
channelMap.put('lSchemeSocketFactoryString',lSchemeSocketFactoryString);
schemeRegistry.register(new Scheme("https", lSchemeSocketFactory, 443));
var httpclient = new DefaultHttpClient(new org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager(httpParams, schemeRegistry), httpParams);
httpclient.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(10, false));
//Benchmark 2
var xtn_postTime = DateUtil.getCurrentDate('yyyyMMddHHmmss');
channelMap.put('xtn_postTime',xtn_startTime);
var httpPost = new HttpPost(url);
httpPost.addHeader("Authorization", "Basic RiF2ZSR0YXI2UzpQUk4icV9APE0tdigj")
httpPost.addHeader("Content-Type", "text/xml")
//passes the results to a string builder/entity
var se = new org.apache.http.entity.StringEntity(connectorMessage.getEncodedData().toString());
channelMap.put('se', se);
//sets the post request as the resulting string
httpPost.setEntity(se);
var httpContents = String(httpPost);
channelMap.put('httpContents',httpContents);
var keystoreContents = String(keystore);
channelMap.put('keystoreContents',keystoreContents);
var response = httpclient.execute(httpPost);
try {
var statusCode = response.getStatusLine().getStatusCode();
var entity = response.getEntity();
var responseString = EntityUtils.toString(entity, "UTF-8");
channelMap.put('statusCode', statusCode);
channelMap.put('responseString', responseString);
} finally {
response.close();
}
//Benchmark 3
var xtn_finishTime = DateUtil.getCurrentDate('yyyyMMddHHmmss');
channelMap.put('xtn_finishTime',xtn_finishTime);
//Response Calculations
//var xtn_totalProcessing = (xtn_finishTimeMs - xtn_startTimeMs)/1000;
var xtn_totalProcessing = xtn_finishTime - xtn_startTime;
channelMap.put('xtn_totalProcessing',xtn_totalProcessing);
//var xtn_totalPostTime = (xtn_finishTimeMs - xtn_postTimeMs)/1000;
var xtn_totalPostTime = xtn_finishTime - xtn_postTime;
channelMap.put('xtn_totalPostTime', xtn_totalPostTime);
var finalStatusCheck = responseString.indexOf("<Code>010</Code>")
var pendingStatusCheck = responseString.indexOf("<Code>000</Code>")
var followUpMessage = false;
channelMap.put('finalStatusCheck',finalStatusCheck);
if (finalStatusCheck == -1 & pendingStatusCheck == -1) {
healthMessageStatus = "2";
channelMap.put('healthMessageStatus', healthMessageStatus);
throw('Bad HTTP Response Code: ' + statusCode + ' For GUID File ID: ' + $('GUID') + '. Response String: ' + responseString);
}
else if (pendingStatusCheck > -1) {
channelMap.put('healthMessageStatus', healthMessageStatus);
channelMap.put('followUpMessage', followUpMessage)
followUpMessage = true;
healthMessageStatus = "2";
throw('Bad HTTP Response Code: ' + statusCode + ' For GUID File ID: ' + $('GUID') + '. Response String: ' + responseString);
}
else
{
healthMessageStatus = "1";
channelMap.put('healthMessageStatus', healthMessageStatus);
channelMap.put('followUpMessage', followUpMessage)
}
Here's the exception I get:
JavaScript Writer error
ERROR MESSAGE: Error evaluating JavaScript Writer
com.mirth.connect.server.MirthJavascriptTransformerException:
CHANNEL: 0 - Inbound Clinical PDF PROD
CONNECTOR: SURESCRIPTS PROD API
SCRIPT SOURCE: JavaScript Writer
SOURCE CODE:
541: var httpContents = String(httpPost);
542: channelMap.put('httpContents',httpContents);
543: var keystoreContents = String(keystore);
544: channelMap.put('keystoreContents',keystoreContents);
545:
546: var response = httpclient.execute(httpPost);
547:
548: try {
549: var statusCode = response.getStatusLine().getStatusCode();
550: var entity = response.getEntity();
LINE NUMBER: 546
DETAILS: Wrapped javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at 3f792644-5e4b-4bbf-af2d-3ac8ef00b5db:546 (doScript)
at 3f792644-5e4b-4bbf-af2d-3ac8ef00b5db:598
at com.mirth.connect.connectors.js.JavaScriptDispatcher$JavaScriptDispatcherTask.call(JavaScriptDispatcher.java:184)
at com.mirth.connect.connectors.js.JavaScriptDispatcher$JavaScriptDispatcherTask.call(JavaScriptDispatcher.java:122)
at java.util.concurrent.FutureTask.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
at sun.security.ssl.Alerts.getSSLException(Unknown Source)
at sun.security.ssl.Alerts.getSSLException(Unknown Source)
at sun.security.ssl.SSLSocketImpl.recvAlert(Unknown Source)
at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:533)
at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:401)
at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:470)
at org.apache.http.conn.scheme.SchemeSocketFactoryAdaptor.connectSocket(SchemeSocketFactoryAdaptor.java:65)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:177)
at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:144)
at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:131)
at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:611)
回答1:
The decision what private key to use when authenticating with the server is made by the JRE (JSSE provider to be exact), not HttpClient. However one can override the default behavior by employing a custom PrivateKeyStrategy
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(myKeyStore, "mypassword".toCharArray(), new PrivateKeyStrategy() {
@Override
public String chooseAlias(Map<String, PrivateKeyDetails> aliases, Socket socket) {
// Pick a cert alias based on socket endpoint, SSL session or private key details
return "vip";
}
})
.build();
CloseableHttpClient client = HttpClients.custom()
.setSslcontext(sslContext)
.build();
来源:https://stackoverflow.com/questions/37757544/apache-httpclient-sends-no-client-cert-during-mutual-authentication