Flutter Dio Networking: SSL pinning using certificate's public hashed key string (not a file) - flutter

I currently use the following snippet to include my SSL certificate file into the http client:
final List<int>? _certBytes; //I read it from .cer file included in the project
(_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) {
if (_certBytes != null) {
SecurityContext sc = SecurityContext();
sc.setTrustedCertificatesBytes(_certBytes!);
HttpClient httpClient = HttpClient(context: sc);
return httpClient;
} else {
client.badCertificateCallback =
((X509Certificate cert, String host, int port) => true);
return client;
}
};
while this code works well, it will stop working if the certificate is expired, which means that I need to add a new certificate file into the app and upload again to the app stores, so I decided to host the certificate as a hashed string in Firebase's Remote Config and read it upon app launch, so I can change the certificate remotely without building new versions for the app, but couldn't find a way to set the SecurityContext with a certificate hashed string in the Dio's HTTPClient

Related

Flutter grpc credentials using .pem file

I have a Flutter app that needs to communicate to a server using a secure client channel over grpc.
To try to create the client channel, I am using:
final _channel = ClientChannel(_hostAddress,
port: _port,
options:
const ChannelOptions(credentials: ChannelCredentials.secure(
certificates: ??? ???
)));
For the certificates part, the only I have is a certificate in a .pem file.
The documentation on the puv.dev page here is totally useless since it just points out that the certificates parameter is a List of int
How do I go from a .pem file to a List of int?
Since Dart does not have a byte type, so byte array is List or Uint8List. I suppose if you open you .pem file and read contents of the file as bytes you will get your List of int
Future<Uint8List> readCert() async {
final File f = File('cert.pem');
final bytes = await f.readAsBytes();
return bytes;
}
Even though the answer from #alex_z might be correct for other cases, it looks like the proper way to do this (if the certificate file is somewhere is assets, like in my case) is to do the following:
final cert = await rootBundle.load('assets/certificate.pem');
final certAsList = cert.buffer
.asUint8List(cert.offsetInBytes, cert.lengthInBytes)
.map((uint8) => uint8.toInt())
.toList();
Like this, it can be passed to the ChannelCredentials without problem.
You guys are all wrong, the flutter grpc takes **.crt file than **.pem
openssl req -newkey rsa:2048 -nodes -keyout grpc_call_service.key -x509 -days 365 -out grpc_call_service.crt
Future<ClientChannel> get_a_temprary_channel() async {
final grpc_call_service_key =
await rootBundle.load('resources/keys/grpc_call_service.crt');
return ClientChannel(
GrpcConfig.host_ip_address,
port: GrpcConfig.call_service_port_number,
options: ChannelOptions(
credentials: ChannelCredentials.secure(
certificates: grpc_call_service_key.buffer.asUint8List(),
authority: GrpcConfig.host_ip_address,
onBadCertificate: (certificate, host) {
return true;
// return host ==
// '${GrpcConfig.host_ip_address}:${GrpcConfig.call_service_port_number}';
})),
);
}
https://docs.servicestack.net/grpc-dart#dart-protoc-grpc-ssl-example

CERTIFICATE_VERIFY_FAILED: Hostname mismatch(handshake.cc:352)) for TCP connection on local server

My code is connecting to AWS-END-POINT properly but when I tried connecting to Greengrass core using local network ip. I get this error.
E/flutter (12349): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: HandshakeException: Handshake error in client (OS Error:
E/flutter (12349): CERTIFICATE_VERIFY_FAILED: Hostname mismatch(handshake.cc:352))
I have already checked the greengrass core. it's working fine. It is connecting to web client very well.
I think there might be some issue of using ip address instead of URL address. but i am not sure. Can anyone help please?
The Code I am running is:
import 'dart:async';
import 'dart:io';
import 'package:mqtt_client/mqtt_client.dart';
import 'dart:convert' show utf8;
import 'dart:convert';
Future<int> main() async {
const String url =
'192.168.8.106';
const int port = 8883;
const String clientId =
'MY CLIENT ID';
MqttClient client = MqttClient(url,clientId);
client.port = port;
client.secure = true;
final SecurityContext context = new SecurityContext(withTrustedRoots: true);
context.setTrustedCertificatesBytes(utf8.encode(' CERT '));
context.useCertificateChainBytes(utf8.encode(' CERT '));
context.usePrivateKeyBytes(utf8.encode(' PRIVEATE KEY '));
client.securityContext = context;
client.setProtocolV311();
// logging if you wish
client.logging(on: false);
print('Before Connecting');
try{
await client.connect();
}catch(e){
print('CATCH IS : ');
print (e);
}
print('After Connecting');
if (client.connectionStatus.state == MqttConnectionState.connected) {
print('iotcore client connected');
} else {
client.disconnect();
}
print('Sleeping....');
for (int i=1; i>0; i++)
{
const String topic = '\$aws/things/Pi_tmfacility_0_1/shadow/update';
Map<dynamic, dynamic> payload =
{'state': {
'desired': {
'number' : i
}
}
};
final MqttClientPayloadBuilder builder = MqttClientPayloadBuilder();
builder.addString(json.encode(payload));
print('into the publish to get single device shadow ');
client.publishMessage(topic, MqttQos.atMostOnce, builder.payload);
print('Ready to Sleep');
await MqttUtilities.asyncSleep(10);
print('Loop no = $i');
}
print('Disconnecting');
client.disconnect();
return 0;
}
The problem is that the CN (or SANs) in the certificate presented by the local machine do not include 192.168.8.106.
You can verify this by using the openssl s_client command:
openssl s_client -connect 192.168.8.106:8883 -CAfile /path/to/ca/cert
This means that the SSL/TLS library in flutter will complain that certificate doesn't reliably represent that machine.
This is important as this is what stops Man-in-the-Middle attacks.
You have 2 options to solve this.
reissue the certificate with a CN or SAN entry with 192.168.8.106
See if you can find a way to influence the Certificate verification. There are examples of how to do this with the dart http library (https://stackoverflow.com/a/59303283/504554) but I haven't found this in the MQTT client library (I haven't looked that hard).
You have to be very careful if you go with option 2 to ensure that you do not open up too big a hole for Man-in-the-middle attacks.
I got the same error in my flutter app but my solution and reason was a bit different.
My certificate was "*.xxxxxx.com" (star certificate).
(xxxxxx.com is not for +18 site, just sample :) )
My subdomain name was sub_domain.xxxxxx.com.
Solution was simple, but it take time to solve it.
"_" (underscore) was the main problem in the domain name.
I changed it to subdoman.xxxxxx.com and it worked.

Unable to connect to MongoDB server using x509 certificate authentication from c#

I have configured MongoDB server as given in document https://docs.mongodb.com/manual/core/security-x.509/
and I connected using mongo shell it is working fine.
Next I tried to connect to same server form c# driver, but Time out exception raised.
Below is my code
var cert = new X509Certificate2(#"C:\Program Files\MongoDB\Server\3.2\ssl\client.pfx", "secretkey");
var sslcrd = MongoCredential.CreateMongoX509Credential("CN=Client1,O=School,ST=Some-State,C=IN");
settings.SslSettings = new SslSettings() ;
settings.UseSsl = true;
settings.SslSettings.ClientCertificates = new List<X509Certificate>()
{
cert
};
settings.SslSettings.EnabledSslProtocols = SslProtocols.Default;
settings.SslSettings.ClientCertificateSelectionCallback =
(sender, host, certificates, certificate, issuers) => settings.SslSettings.ClientCertificates.ToList()[0];
settings.SslSettings.ServerCertificateValidationCallback = (sender, certificate, chain, errors) => true;
settings.SslSettings.CheckCertificateRevocation = false;
settings.VerifySslCertificate = false;
settings.Credentials = new[] { sslcrd };
MongoClient client = new MongoClient(settings);
var db = client.ListDatabases().ToList();
I went through MongoDB server logs and I am able to see below error
2017-04-10T11:18:21.559+0530 I NETWORK [initandlisten] connection
accepted from
127.0.0.1:53901 #64 (1 connection now open) 2017-04-10T11:18:21.559+0530 E NETWORK [conn64] no SSL certificate
provided by peer; connection rejected 2017-04-10T11:18:21.560+0530 I
NETWORK [conn64] end connection 127.0.0.1:53901 (0 connections now
open)
I am using c# MongoDB.Driver version 2.3.0
and MongoDB package is of version 3.2.
If you have solution for the above please do reply.
Set the value of allowConnectionsWithoutCertificates to false. Also, you need to combine the certificate and the key file.
In despite of this thread be a little bit old, I faced this exactly same issue today and found a solution at MongoDB.com site (Setup Connection Security code example), so,sharing the solution here for future searchs.
Basically the settings should be created from a connection string and follow the next steps straightforward:
using MongoDB.Bson;
using MongoDB.Driver;
using System;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
class Program
{
static void Main(string[] args)
{
MainAsync().Wait();
}
static async Task MainAsync()
{
var connectionString = "mongodb+srv://<your-server.mongodb.net>/?authSource=%24external&authMechanism=MONGODB-X509&retryWrites=true&w=majority";
var settings = MongoClientSettings.FromConnectionString(connectionString);
settings.ServerApi = new ServerApi(ServerApiVersion.V1);
// You will need to convert your Atlas-provided PEM containing the cert/private keys into a PFX
// use openssl and the following line to create a PFX from your PEM:
// openssl pkcs12 -export -in <x509>.pem -inkey <x509>.pem -out <x509>.pfx
// and provide a password, which should match the second argument you pass to X509Certificate2
var cert = new X509Certificate2("<path_to_pfx>", "<pfx_passphrase>");
settings.SslSettings = new SslSettings
{
ClientCertificates = new List<X509Certificate>(){ cert }
};
var client = new MongoClient(settings);
var database = client.GetDatabase("testDB");
var collection = database.GetCollection<BsonDocument>("testCol");
var docCount = collection.CountDocuments("{}");
Console.WriteLine(docCount);
}
}

How to create a truststore.jks with one certificate?

(I'm a newby in cryptographic things.)
I have an setup program written in C#. This asks the user to input the server URL. Then it connects to this server and stores this server certificate into a truststore file that is used by the installed Java REST service.
The truststore file is created by keytool.exe:
keytool.exe -alias anAlias -import -file cert.cer -noprompt -keystore truststore.jks -storepass aPassword
Now we don't want to use keytool.exe. We want to create the keystore by C#. My first tries are as follows:
class AddCertToTruststore
{
public static void Do()
{
ServicePointManager.ServerCertificateValidationCallback += Validate;
X509Certificate2 cert = new X509Certificate2("cert.cer");
cert.Archived = true;
bool ok = cert.Verify(); // always false
X509Certificate2Collection certs = new X509Certificate2Collection();
certs.Add(cert);
byte[] bytes = certs.Export(X509ContentType.Pkcs12);
File.WriteAllBytes("truststore.jks", bytes);
ServicePointManager.ServerCertificateValidationCallback -= Validate;
}
private static bool Validate(object sender, X509Certificate certificate, X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
return true;
}
}
This code creates a truststore but I'm missing the certificate in it. If I open the truststore.jks with KeyStore Explorer 5.1 there is not any certificate in it. What am I doing wrong?
The certificate is a self-signed certificate. cert.Verify() returns always false.
It's just one line that is missing:
cert.FriendlyName = "anAlias";
It works also without the validation handler and without setting Archived property. So the shortest code is:
X509Certificate2 cert = new X509Certificate2(#"cert.cer");
cert.FriendlyName = "anAlias";
X509Certificate2Collection certs = new X509Certificate2Collection();
certs.Add(cert);
byte[] bytes = certs.Export(X509ContentType.Pkcs12);
File.WriteAllBytes(#"truststore.jks", bytes);

grails - RestClientBuilder

I am using the current version of rest client builder plugin. I tested out the uri via curl:
curl --user username:password https://localhost:8085/rest/api/latest/plan.json?os_authType=basic
I get the expected json in return. When I try to translate this to grails using the plugin like this:
RestBuilder rb = new RestBuilder()
def response = rb.get("https://localhost:8085/rest/api/latest/plan.json?os_authType=basic"){
auth 'username', 'password'
}
response.json instanceof JSONObject
I get this error:
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Why does it work in curl and not with the plugin? How do I get this to work?
Thanks!
You need to add the root certificate to the store of the trusted ones.
http://docs.oracle.com/javase/tutorial/security/toolsign/rstep2.html
Import the Certificate as a Trusted Certificate
Before you can grant the signed code permission to read a specified file, you need to import Susan's certificate as a trusted certificate in your keystore.
Suppose that you have received from Susan
the signed JAR file sCount.jar, which contains the Count.class file, and
the file Example.cer, which contains the public key certificate for the public key corresponding to the private key used to sign the JAR file.
Even though you created these files and they haven't actually been transported anywhere, you can simulate being someone other than the creater and sender, Susan. Pretend that you are now Ray. Acting as Ray, you will create a keystore named exampleraystore and will use it to import the certificate into an entry with an alias of susan.
A keystore is created whenever you use a keytool command specifying a keystore that doesn't yet exist. Thus we can create the exampleraystore and import the certificate via a single keytool command. Do the following in your command window.
Go to the directory containing the public key certificate file Example.cer. (You should actually already be there, since this lesson assumes that you stay in a single directory throughout.)
Type the following command on one line:
keytool -import -alias susan
-file Example.cer -keystore exampleraystore
Since the keystore doesn't yet exist, it will be created, and you will be prompted for a keystore password; type whatever password you want.
The keytool command will print out the certificate information and ask you to verify it, for example, by comparing the displayed certificate fingerprints with those obtained from another (trusted) source of information. (Each fingerprint is a relatively short number that uniquely and reliably identifies the certificate.) For example, in the real world you might call up Susan and ask her what the fingerprints should be. She can get the fingerprints of the Example.cer file she created by executing the command
keytool -printcert -file Example.cer
If the fingerprints she sees are the same as the ones reported to you by keytool, the certificate has not been modified in transit. In that case you let keytool proceed with placing a trusted certificate entry in the keystore. The entry contains the public key certificate data from the file Example.cer and is assigned the alias susan.
You can just disable SSL check for RestBuilder.
See an example of code:
static Scheme disableSSLCheck() {
def sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, [new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
#Override
X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0]
}
}] as TrustManager[], new SecureRandom())
def sf = new SSLSocketFactory(sslContext, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
def httpsScheme = new Scheme("https", sf, 443)
httpsScheme
}
And register this Scheme to the RestClient:
Scheme httpsScheme = disableSSLCheck()
restClient.client.connectionManager.schemeRegistry.register(httpsScheme)
Mb too late but have a look here.
https://gist.github.com/thomastaylor312/80fcb016020e4115aa64320b98fb0017
I do have it as separate method in my Integration test
def static disableSSLCheck() {
def nullTrustManager = [
checkClientTrusted: { chain, authType -> },
checkServerTrusted: { chain, authType -> },
getAcceptedIssuers: { null }
]
def nullHostnameVerifier = [
verify: { hostname, session -> true }
]
SSLContext sc = SSLContext.getInstance("SSL")
sc.init(null, [nullTrustManager as X509TrustManager] as TrustManager[], null)
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory())
HttpsURLConnection.setDefaultHostnameVerifier(nullHostnameVerifier as HostnameVerifier)
}
And then just
void "test authentication"(){
given:
String url = "j_spring_security_check"
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>()
form.add("grant_type", "password")
form.add("j_username", "vadim#ondeviceresearch.com")
form.add("j_password", "notSecure")
form.add("_spring_security_remember_me", "true")
//TODO SET username and pass
//todo get token back
disableSSLCheck()
when:
RestResponse response = rest.post(host + url){
accept("application/json")
contentType("application/x-www-form-urlencoded")
body(form)
}
response
then:
response.status == 200
}