GRPC Okhttp android client channel with self signed ssl certificate - kotlin

I have a grpc-js server using self signed ssl certificates.
var credentials = grpc.ServerCredentials.createSsl(
fs.readFileSync('./node/grpc/ssl/ca.crt'),
[{
cert_chain: fs.readFileSync('./node/grpc/ssl/server.crt'),
private_key: fs.readFileSync('./node/grpc/ssl/server.key')
}],
true
);
I then tested this setup with a grpc-js client with the following credential setup and this works.
var credentials = grpc.credentials.createSsl(
fs.readFileSync('./node/grpc/ssl/ca.crt'),
fs.readFileSync('./node/grpc/ssl/client.key'),
fs.readFileSync('./node/grpc/ssl/client.crt')
);
I want to replicate this in Android using OkHttpChannelBuilder but it is a bit more complicated. This is what I have so far.
private val mChannel : ManagedChannel
init {
/**
* Server certificate to make it trusted.
*/
val serverCrtFile = applicationContext.resources.openRawResource(R.raw.server)
val serverCertificate: X509Certificate =
CertificateFactory.getInstance("X.509").generateCertificate(serverCrtFile) as X509Certificate
val caKeyStore: KeyStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
load(null, null)
setCertificateEntry("server", serverCertificate)
}
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply {
init(caKeyStore)
}
val sslContext = SSLContext.getInstance("TLS").apply {
init(null, trustManagerFactory.trustManagers, null)
}
mChannel = OkHttpChannelBuilder
.forAddress(BuildConfig.GRPC_HOST_ADDRESS, BuildConfig.GRPC_HOST_PORT)
.sslSocketFactory(sslContext.socketFactory)
.keepAliveTime(10, TimeUnit.SECONDS)
.useTransportSecurity()
.keepAliveWithoutCalls(true)
.build()
}
Everything worked before implementing ssl (so using plaintext() on the channel builder).
The error I get now is io.grpc.StatusRuntimeException: UNAVAILABLE: End of stream or IOException.
Can someone please tell me if I am doing something wrong and how I can get a successful connection like between the js server and client.

Looks like the SSL handshake failed on the server side so it will be helpful to get server side detailed logs to see what went wrong.
One possibility is using KeyStore.getInstance. Can you try using "PKCS12"?
KeyStore.getInstance("PKCS12")

Related

Bind certificate to a micro service in pod (mTLS)

I am trying to implement the mTLS in cluster across micro service for secured communication. I know that there are service meshes are available for this purpose. But we would like to stay away from service mesh and implement the mTLS in cluster.
So, after going through several posts, then I am able to create the tls secret and mount the volume as part of the service deployment. This certificate i can retrieve from X509Store:
using var certificateStore = new X509Store(StoreName.Root, StoreLocation.LocalMachine, OpenFlags.ReadOnly);
if (certificateStore.Certificates.Any())
{
var certs = certificateStore.Certificates.Find(X509FindType.FindByIssuerName, issuerName, true);
if (certs.Any())
{
return certs.First();
}
}
return null;
But, now, when i am trying to assign this certificate as part of the
kestrelServerOptions.ConfigureHttpsDefaults(listenOptions =>
{
Log.Information($"Configuring the https defaults.");
if (serverCertificate == null)
{
return;
}
// self signed certificate
Log.Information($"Before : Private key: {serverCertificate?.HasPrivateKey}");
Log.Information($"After : Server certificate: {listenOptions.ServerCertificate?.Issuer}");
listenOptions.ServerCertificate = serverCertificate; // throws exception saying that the serer certificate should have the private key.
....
my secret volume has both .crt(pem) and .key files stored as part of the tls secret. But service is not able to attach this private .key to it.
I am really lost here... and not able to proceed further.
I really appreciate if someone help me to work with this certificate and mTLS.
Thanks in advance.

unable to find valid certification path to requested target - signed cert

I am working on a code that connects to slack through a proxy which act as a MITM and replaces slack cert with its own self signed cert. I added proxy's cert into a trust store and configured my RestTemplate to use the trust store:
def sslContext = new SslContextBuilder().withTrustStore(trustStoreResource, trustStorePassword).build()
def proxy = proxyEnabled ? new HttpHost(proxyHost, proxyPort) : null
def httpClient = HttpClients.custom().setProxy(proxy).setSSLContext(sslContext).build()
def result = new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient))
That works fine. However, on my local I don't go through the proxy and connect to slack directly. In other words, the httpClient in the above code would be configured with SSLContext but not proxy. I was expecting this to be fine since Slack's cert is signed with a valid root CA but my code fails to verify Slack's cert.
I am assuming this is because my trustore but I am confused as why this is happening. Is it happening because root CAs are not imported in my trustsore? If so, how would I do that without having to maintain the root CAs?
I understand that locally I can refrain from setting up a trust store but I would like to avoid adding branches in the code if possible.
What I finally ended up doing was to use the implementation in https://gist.github.com/JensRantil/9b7fecb3647ecf1e3076 to combine system's default trust store with mine and then used the following class to build my SSL context. It's a shame HttpClient doesn't offer this but there might be a good reason for it.
import org.springframework.core.io.Resource
import javax.net.ssl.KeyManager
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import java.security.KeyStore
class SslContextBuilder {
private KeyManager[] keyManagers = []
private TrustManager[] trustManagers = []
SslContextBuilder withKeyStore(Resource resource, String password) {
def keyStore = KeyStore.getInstance('JKS')
keyStore.load(resource.getInputStream(), password.chars)
KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
kmfactory.init(keyStore, password.chars)
KeyManager[] kms = kmfactory.getKeyManagers()
keyManagers += kms ? kms : []
this
}
SslContextBuilder withTrustStore(Resource resource, String password) {
def trustStore = KeyStore.getInstance('JKS')
trustStore.load(resource.getInputStream(), password.chars)
def tss = CompositeX509TrustManager.getTrustManagers(trustStore)
trustManagers += tss ? tss : []
this
}
SSLContext build() {
def sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null)
sslContext
}
}

Https through proxy with OkHttp got handshake error

I'm going to download image with Glide library that needs https and proxy config.
I implemented all anonymous certificates and proxy settings for unsafe client (in my dev environment) but get handshake error. This is my OkHttpClient passed to Glide
val unsafeOkHttpClient: OkHttpClient
get() {
try {
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
#SuppressLint("TrustAllX509TrustManager")
#Throws(CertificateException::class)
override fun checkClientTrusted(
chain: Array<java.security.cert.X509Certificate>,
authType: String
) {
}
#SuppressLint("TrustAllX509TrustManager")
#Throws(CertificateException::class)
override fun checkServerTrusted(
chain: Array<java.security.cert.X509Certificate>,
authType: String
) {
}
override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> {
return arrayOf()
}
})
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
val sslSocketFactory = sslContext.socketFactory
val builder = OkHttpClient.Builder()
val proxy = Proxy(
Proxy.Type.HTTP,
InetSocketAddress.createUnresolved(PROXY_URL, PROXY_PORT)
)
builder.proxy(proxy)
builder.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
builder.hostnameVerifier(HostnameVerifier { _, _ -> true })
val connectionSpecs = ConnectionSpec.Builder(ConnectionSpec.COMPATIBLE_TLS)
.tlsVersions(TlsVersion.TLS_1_2)
.cipherSuites(
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
).build()
builder.connectionSpecs(listOf(connectionSpecs))
return builder.build()
} catch (e: Exception) {
throw RuntimeException(e)
}
}
I should mention that ConnectionSpec is get from my server configurations. And always i get this error:
Even i used very simple client but result is same.
Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xbe2b3c68: Failure in SSL library, usually a protocol error
error:10000410:SSL routines:OPENSSL_internal:SSLV3_ALERT_HANDSHAKE_FAILURE (external/boringssl/src/ssl/tls_record.cc:587 0xbe5d2a88:0x00000001)
error:1000009a:SSL routines:OPENSSL_internal:HANDSHAKE_FAILURE_ON_CLIENT_HELLO (external/boringssl/src/ssl/handshake.cc:580 0xd084f543:0x00000000)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
at com.android.org.conscrypt.NativeSsl.doHandshake(NativeSsl.java:387)
at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:226)
... 23 more
I tried too many ways for example exclude okHttp from glide and use OkHttp itself, downgrade okHttp, upgrade all libs ( Retrofit , Glide ) .I found some posts here but cloud not make it works.
https://github.com/square/okhttp/issues/3787
https://github.com/Microsoft/cpprestsdk/issues/650
UPDATED
As i mentioned all images are open in browser ( with proxy extension) and also i got 200 with Curl like this:
curl --insecure -x http://myProxy:9052 -i https://myimage.png
But i find out that TLS version of main server and proxy server are not same. One uses TLS1.2 and other is TLS1.1. So i'm thinking about may this configuration lead to handshake failure cause my request will do not know to handshake with which version! This is my guess and asked the network admin already : "Why we have two different confines for server and proxy!" I'm waitings for their response. If you have any idea please feel free to add comment or post any answer.
After strugle with many thing from client side, backed team set a valid certificate that make my problem solved.
I mean they did not use self-sigend certificate but they used an invalid certificate! That is why i got hand shake error and in browser we can passed this error by accept responcibility of danger and click proceed button.
So if you see the same problem: Handshake error but you can proceed it in browser with my situation lets chech SSL certificate first to save time!

Self-signed cert for gRPC on Flutter

I have a Flutter app that communicates with a server using gRPC. The server is using a self-signed certificate for TLS. I have added the certificate to my Flutter app, and this works on Android. However on iOS I get CERTIFICATE_VERIFY_FAILED error. Does iOS just not allow self-signed certificates?
I am setting up my gRPC client as follows:
var cert = await rootBundle.load('assets/cert.crt');
var creds = ChannelCredentials.secure(
certificates: cert.buffer.asUint8List().toList()
);
var channel = ClientChannel(
host,
port: port,
options: new ChannelOptions(credentials: creds));
return GrpcClient(channel);
There doesn't seem to be an obvious solution on iOS for adding a trusted, self-signed root CA. Since production will likely have a publically trusted CA, you can work around by disabling TLS verification for development only.
Here's the relevant snippet of my full example repo:
Future<ClientChannel> makeChannel() async {
final caCert = await rootBundle.loadString('assets/pki/ca/ca.crt');
return ClientChannel(
'localhost',
port: 13100,
options: ChannelOptions(
credentials: ChannelCredentials.secure(
certificates: utf8.encode(caCert),
// --- WORKAROUND FOR SELF-SIGNED DEVELOPMENT CA ---
onBadCertificate: (certificate, host) => host == 'localhost:13100',
),
),
);
}
In this case, my server is listening on localhost:13100.
The following was adapted from:
https://github.com/grpc/grpc-dart/issues/134
It allows for specifying a custom (or self-signed) CA cert, client certificates, and/or a custom domain:
import 'dart:convert';
import 'dart:io';
import 'package:grpc/grpc.dart';
class CustomChannelCredentials extends ChannelCredentials {
final String caCert;
final String? clientCert;
final String? clientKey;
CustomChannelCredentials({
required this.caCert,
this.clientCert,
this.clientKey,
String? authority, // Custom domain used by server cert
}) : super.secure(
authority: authority,
onBadCertificate: (cert, host) {
// This is a work-around for iOS, it seems self-signed certs are not being properly verified;
return host == '<the common name used self-signed CA>';
},
);
#override
SecurityContext get securityContext {
final context = SecurityContext(
withTrustedRoots: false, // We want to specify a custom CA cert
);
context.setTrustedCertificatesBytes(utf8.encode(caCert));
context.setAlpnProtocols(supportedAlpnProtocols, false);
if (clientCert != null) {
context.useCertificateChainBytes(utf8.encode(clientCert!));
}
if (clientKey != null) {
context.usePrivateKeyBytes(utf8.encode(clientKey!));
}
return context;
}
}
Example usage:
final channel = ClientChannel(
serverAddress,
port: serverPort,
options: ChannelOptions(
credentials: CustomChannelCredentials(
caCert: selfSignedCaCertPem,
// clientCert: clientCertPem,
// clientKey: clientKeyPem,
authority: 'localhost',
),
),
);

vaultsharp tls auth failed - client certificate must be supplied

Vaultsharp is not able to authenticate with vault for TLS AUTH method
C# code on windows 10, cert and key in personal store
environment windows
X509Certificate2 clientCertificate = null;
X509Store store = new X509Store(StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificateList =
store.Certificates.Find(X509FindType.FindBySubjectName, "subject name", false);
if (certificateList.Count > 0)
{
clientCertificate = certificateList[0];
};
store.Close();
// got clientCertificate here, it has private key as well
try
{
IAuthMethodInfo authMethod = new CertAuthMethodInfo(clientCertificate);
var vaultClientSettings = new VaultClientSettings("endpoint:8200", authMethod);
IVaultClient vaultClient = new VaultClient(vaultClientSettings);
Secret<Dictionary<string, object>> secret = null;
Task.Run(async () =>
{
secret = await vaultClient.V1.Secrets.KeyValue.V1.ReadSecretAsync("dummy_app/dev/connection_strings");
}).GetAwaiter().GetResult();
Above code is throwing error
{"errors":["client certificate must be supplied"]}
It should return the secret instead of throwing exception
Please check the following.
That the certificate really has a private key. (HasPrivateKey check on the object) Typically you read a private key from a store using a passphrase. I don't see that above, so it maybe that what you have is a public key.
Please ensure that the certificate is a valid cert with the full chain. The Vault API (not VaultSharp) throws an error if it cannot find the parent chain.
Please inspect the http or tcp connection to see if the cert is truly attached.