How can I enable certificate pinning using OkHttp for my Android / Java application?
The OkHttp documentation gives us a clear way to do this complete with sample code. In case it goes away, here it is pasted in below:
1. Add a broken CertificatePinner and make a request.
Any request will do, even if it doesn't exist. You can do this in your Android application, or just create a dummy Java application and run this as well.
For example, to pin https://publicobject.com, start with a broken
configuration:
String hostname = "publicobject.com";
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build();
OkHttpClient client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build();
Request request = new Request.Builder()
.url("https://" + hostname)
.build();
client.newCall(request).execute();
As expected, this fails with a certificate pinning exception:
javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
Peer certificate chain:
sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=: CN=publicobject.com, OU=PositiveSSL
sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=: CN=COMODO RSA Secure Server CA
sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=: CN=COMODO RSA Certification Authority
sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=: CN=AddTrust External CA Root
Pinned certificates for publicobject.com:
sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
at okhttp3.CertificatePinner.check(CertificatePinner.java)
at okhttp3.Connection.upgradeToTls(Connection.java)
at okhttp3.Connection.connect(Connection.java)
at okhttp3.Connection.connectAndSetOwner(Connection.java)
2. Configure your OkHttp Client Correctly:
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=")
.add("publicobject.com", "sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=")
.add("publicobject.com", "sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=")
.add("publicobject.com", "sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=")
.build();
That's all there is to it!
This method will give you all your certificates in the entire chain. This is advantageous since it's safer as only one certificate in the chain has to match for the request to succeed. It's likely at some point in the future, your certificates will be updated, but as long as the entire chain isn't updated, your application shouldn't break.
Related
There is already a valid cert in my keystore and handshake process with server is working. When another cert with different CN is added in the same keystore and connect with server, get the error from server "Access denied, invalid endpoint". I think when connecting with server, the second cert is used in ssl connection when there are two valid certs for client certs usage (the existing one and the newly imported one). What I want to know is which one is used if there are one than more valid certs in keystore. Is it related with certs alias?
The following is code snippet for socket connection.
try {
SSLSocketFactory factory = null;
try {
SSLContext ctx;
KeyManagerFactory kmf;
KeyStore ks;
char[] passphrase = "*****".toCharArray();
ctx = SSLContext.getInstance("TLSv1.2");
kmf = KeyManagerFactory.getInstance("SunX509");
ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("testkeys"), passphrase);
kmf.init(ks, passphrase);
ctx.init(kmf.getKeyManagers(), null, null);
factory = ctx.getSocketFactory();
} catch (Exception e) {
throw new IOException(e.getMessage());
}
SSLSocket socket = (SSLSocket)factory.createSocket(host, port);
String[] cipherSuites = socket.getSupportedCipherSuites();
socket.setEnabledCipherSuites( cipherSuites );
socket.setNeedClientAuth(false);
socket.startHandshake();
Similar Which key and certificate from keystore and truststore is used when there are many? for server.
For SunX509 KeyManagerFactory:
at initialization it creates a HashMap containing all the privatekey entries from the keystore, keyed by alias;
handshake calls chooseClientAlias which, for each <=1.2 certificate_type or 1.3 sigalg specified/requested by the server (either called keyType in the code) checks each HashMap entry in HashIterator order (which is determined by a variable number of low bits of a value derived from the hashcode of the alias) to see if the leaf cert has that keytype, and any cert in the chain is issued by one of the CAs specified by the server unless the server left the CA list empty in which case this part of the check is skipped.
If you're getting the wrong cert-and-key selected, check if the server is specifying a correct CA-list in its CertificateRequest message. You can do this on the Java client side by running with sysprop javax.net.debug=ssl:handshake, or except in 1.3 a network level tool like wireshark or tcpdump.
I'm trying to secure a REST API using TLS/SSL, to do so I needed to update my client to use the public key and certificate.
The client is written in dart and here's how I implemented the SecurityContext :
SecurityContext clientContext = SecurityContext.defaultContext;
var certificate = (await rootBundle.load("assets/ssl/coastr.crt")).buffer.asInt8List();
print(certificate.toString());
clientContext.setTrustedCertificatesBytes(certificate);
/*var authorities = (await rootBundle.load('assets/ssl/coastr.ca-bundle')).buffer.asUint8List();
print(authorities.toString());
clientContext.setClientAuthoritiesBytes(authorities);*/
var key = (await rootBundle.load("assets/ssl/coastr_public.key")).buffer.asInt8List();
print(key.toString());
clientContext.usePrivateKeyBytes(key);
HttpClient client = HttpClient(context: clientContext);
HttpClientRequest request = await client.getUrl(Uri.parse(url));
HttpClientResponse response = await request.close();
The certificate (.crt file) is added without issue to the clientContext but adding the key to it returns me this error :
[ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception:
TlsException: Failure in usePrivateKeyBytes (OS Error:
BAD_PKCS12_DATA(pkcs8_x509.c:606)
passed a null parameter(ssl_privkey.cc:375), errno = 0)
The files I'm using are :
coastr.crt with this as a header : -----BEGIN CERTIFICATE-----
coastr_public.key with header : -----BEGIN PUBLIC KEY-----
I have no idea if I'm providing the wrong files to the client or if the error comes from elsewhere.
The files where generated using openssl.
Thank you for your help.
In general, you shouldn't have to add anything to the client to allow it to connect to a correctly configured HTTPS server. Hopefully, the server has a signed server side certificate. If that certificate is signed by a reputable CA, the client should automatically trust it. If it is signed by an in house CA, or self signed, you need to do some work. You need to provide the client with the signing certificate. In the former case that would be the root CA's certificate. In the latter case, supplying the server's certificate may work, though it's probably easier to disable the client check altogether.
The signing certificate is likely to be in CRT form as you've found. And you need to supply that exactly as you are doing. There's no need to supply any public keys as the are distributed in the certificates sent with the server hello.
Unless you want to use a client side certificate, there's no need to supply a private key, so you can skip the step that is failing. And supplying a public key to it is definitely not going to work, anyway.
From client (eg: https://localhost:8080/) we are passing the certificate related values and calling the rest services ( hosted on different port - https://localhost:446/serviceName).
The issue is like, when we try to pass the certificate , SSL handshake is happening correctly (no error on debug) , but the certificate value is not passed to the service hosted on another port. Certificate value is accessed in server code by referring to (X509Certificate)httpReq.getAttribute("javax.servlet.request.X509Certificate");
Note : We use Spring boot application which intenally runs on tomcat server.And desired CA authorised certificates, keystore and truststore are present in resource path in both the projects (client and service hosted). In rest service project config file, the client-auth is set to false.
Sample code snippet used to call rest service:
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(restserviceTruststore)
.loadKeyMaterial(restserviceKeyStore, password).build();
HttpClient client = HttpClients.custom() .setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
.setSslcontext(sslContext).build();
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(client));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
HttpEntity<String> request = new HttpEntity<>(XML, headers);
response = restTemplate.postForObject(endpointURL, request, String.class);
Question:
1) From client what keystore and trustore should we need to pass to SSLContext? Is it server's keystore /truststore or clients?
2)What are the exact steps to be followed to resolve this issue.
This is the process I followed :-`
var certX = Fiddler.CertMaker.oCertProvider.GetCertificateForHost("<Machine Name>");
File.WriteAllBytes(#"D:\PFX.pfx", certX.Export(X509ContentType.SerializedCert));
Once done with this. I restarted the Demo application and tried to load certificate from disk
X509Certificate2 certTry = new X509Certificate2(#"D:\PFX.PFX", "1", X509KeyStorageFlags.UserKeySet |
X509KeyStorageFlags.PersistKeySet |
X509KeyStorageFlags.Exportable);
oSecureEndpoint = FiddlerApplication.CreateProxyEndpoint(iSecureEndpointPort, true, certTry);
This works but when I do.
WriteCommandResponse("Result: " + Fiddler.CertMaker.trustRootCert().ToString());
It fails with error saying cannot trust root certification; Not Found
What am I doing wrong here?
My intent is the Decrypt HTTPS traffic using a custom certificate.
Let's step back a bit-- what do you hope to accomplish by storing the certificate Fiddler generates to disk, then reloading it later?
The likely problem here is that your method doesn't write the private key to the target PFX file, so you can't subsequently use the PFX to encrypt traffic.
As #EricLaw pointed out issue was with the PFX. Certificate that
Fiddler.CertMaker.GetRootCertificate();
generates does not have the private key of the certificate. So to save the certificate just writing the above certificate won't be enough. The way around it is to open user's root cert store and then get the certificate out of it along with it's private key (code example below). This certificate can then be used in future sessions.
X509Store certStore = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
// Try to open the store.
certStore.Open(OpenFlags.ReadOnly);
// Find the certificate that matches the name.
X509Certificate2Collection certCollection = certStore.Certificates.Find(X509FindType.FindBySubjectName, "DO_NOT_TRUST_FiddlerRoot", false);
X509Certificate2 certTry = new X509Certificate2(#"D:\PFX.PFX", "1", X509KeyStorageFlags.UserKeySet |
X509KeyStorageFlags.PersistKeySet |
X509KeyStorageFlags.Exportable);
Exportable is optional, but PersistKeySet is required otherwise the certificate won't contain the private key.
I'm using several RESTful webservice in JAVA based web-application. I'm using the RESTeasy client to access my webservice. Here all communication between the client and service is through XML(JAX-B xml annotated detail classes). Here are the following codes
String serviceURL = "https://service.company.com/Service/getService"
ServiceRequestDetail serviceRequestDetail = getServiceRequestAsDetailClass();
ServiceResponseDetail serviceResponseDetail = new ServiceResponseDetail();
ClientRequest clientRequest = new ClientRequest(serviceURL);
clientRequest.accept(MediaType.APPLICATION_XML);
clientRequest.body(MediaType.APPLICATION_XML, serviceRequestDetail);
ClientResponse<ServiceRequestDetail> response =
clientRequest.post(ServiceRequestDetail.class);
if (response.getStatus() != 200) {
throw new RuntimeException("Failed : HTTP error code : " +
response.getStatus());
}
ServiceResponseDetail serviceResponseDetail =
response.getEntity(ServiceResponseDetail.class);
and when I try to access my service I get the "Peer not Authenticated" error
javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
...
Is there any way to add the SSL configuration details in the RESTeasy client? any other suggestions for solving this issue is also welcome
Thanks in advance
I found out the answer but I'm really sorry for the late response.
To answer my question, RESTeasy client does support TLS/SSL. Infact the problem was I missed to install the certificate into the JVM.
keytool -import -alias <Replace certificate Alias name> -keystore $JAVA_HOME\jre\lib\security\cacerts -file <Replace your Certificate file location>
This solved the issue of "Peer Not Authenticated". Hope it helps. Kudos
If you don't want to add certificate to JVM and keep this cert separate. You can load the cert as part of your code like below.
// load the certificate
InputStream fis = this.getClass().getResourceAsStream("file/path/to/your/certificate.crt");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(fis);
// load the keystore that includes self-signed cert as a "trusted" entry
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
keyStore.setCertificateEntry("cert-alias", cert);
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tmf.getTrustManagers(), null);`
then attach to rest easy builder like
resteasyClientBuilder.sslContext(sslContext)