Dart Add SSL certificate and key to HttpClient - rest

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.

Related

Is there a way to check if a certificate is client cert or server cert?

I received a new keystore .jks file for ssl connection to replace an old, but working, .jks keystore file, but I got "unexpected handshake message: serve_hello" error. I was told to make sure the keystore contains a client cert, so I used keytool to export its cert to a pem file, then use openssl to check the purpose. The result shows
Certificate purposes:
SSL client : No
SSL client CA : No
SSL server : Yes
SSL server CA : No
...
However when I applied the same process to check the old but working jks file I got the same result. Wonder if this is the right way to verify the certificate? And how to troubleshooting this handshake error with the new jks file?
Thanks!
The extended key usage extension contains OIDs which define the purpose:
id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 }
-- TLS WWW server authentication
-- Key usage bits that may be consistent: digitalSignature,
-- keyEncipherment or keyAgreement
id-kp-clientAuth OBJECT IDENTIFIER ::= { id-kp 2 }
-- TLS WWW client authentication
-- Key usage bits that may be consistent: digitalSignature
-- and/or keyAgreement
https://datatracker.ietf.org/doc/html/rfc5280 Page 44
See: https://oidref.com/1.3.6.1.5.5.7.3.1 and https://oidref.com/1.3.6.1.5.5.7.3.2
When opening a certificate on Windows you can see the extension here:

Which cert is used in SSL connection if there are two valid certs in client certificates in socket handshake

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.

How to obtain the ADFS Public Key and validate the signature on a JWT Token?

I need to validate the signature of a JWT token which was signed on an ADFS server. I can validate a self-signed JWT token, but not a token received from ADFS. How should I be obtaining the public key?
To get the public key from the ADFS server I asked my colleague to export
the certificate from the ADFS server. On the ADFS console He looked in
"Services" > "Edit Federation Service Properties",
"General" tab where he found three entires. They were under the headings
"Token Signing", "Token Decrypting" and "Service Communication".
For each he viewed the certificate and then exported the DER certificate
(without private key). They were then transformed to PEM (using
openssl x509 -inform der -in cert.cer -out cert.pem) but none of these keys
allow my code to validate the sniffed JWT tokens.
My Java code takes a JWT Token and a Public Key, and validates that the token was signed with the Public Key. If I use a self-signed key pair and a self generated JWT Token then the code appears to work, and reports the signature is OK. When the token is copied from an HTTP Header in a message from ADFS the same code reports the signature is invalid.
Here is the code I use to load the PEM public key:
FileInputStream fis = new FileInputStream(publicKeyFile)
Reader keyReader = new InputStreamReader(publicKeyStream);
PemReader pemReader = new PemReader(keyReader);
PEMParser pemParser = new PEMParser(pemReader);
Object pemObject = pemParser.readObject();
if (pemObject instanceof X509CertificateHolder) {
X509CertificateHolder x509CertificateHolder = (X509CertificateHolder) pemObject;
X509Certificate x509Certificate = new JcaX509CertificateConverter()
.setProvider("BC").getCertificate(x509CertificateHolder);
return x509Certificate.getPublicKey();
}
Here is the code I use to validate the signature:
String signatureAlgorithm = publicKey.getAlgorithm();
Signature signatureInstance = Signature.getInstance(signatureAlgorithm);
signatureInstance.initVerify(publicKey);
byte[] messageBytes = Base64.encodeBase64(message.getBytes(UTF_8));
signatureInstance.update(messageBytes);
byte[] receivedSignature = Base64.decodeBase64URLSafe(signature);
return signatureInstance.verify(receivedSignature);
(I've removed the exception handling and resource closing for brevity above.
I'm using tomcat's Base64 class.)
The code runs without error but indicates that the signature is not valid. It seems to me that any of the below could be wrong:
None of the public keys extracted from the ADFS server are the relevant public key.
The conversion of the public keys from .cer to .pem may be incorrect.
The code to validate the signature, or load the public key, may be wrong.
Refer this and this.
The keys you refer to:
"Token Signing" - used for a SAML token derived from a CP or RP trust e.g. federation via SAML or WS-Fed
"Token Decrypting" - likewise
"Service Communication" - used for server SSL communication
For a JWT, I assume you are using OpenId Connect?
You use:
https://[Your ADFS hostname]/adfs/.well-known/openid-configuration
This has a pointer to the keys.

How to enable certificate pinning with OkHttp

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.

Using custom certificate in FiddlerCore

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.