How to send intermediate cert (in addition to leaf cert) from http client to the server in .net (core) 5? - ssl

I was not able to make http client code in .net 5 to send both intermediate and leaf certificates (in 3 certificate hierarchy) to the server. However I was able to send the leaf certificate from client to the server successfully. Here is my setup:
I have 3 certificates on my windows box:
TestRoot.pem
TestIntermediate.pem
TestLeaf.pem (without private key for server - windows box)
TestLeaf.pfx (with private key for client - windows box)
The none of the above certificates were NOT added to windows certificate manager as I would like to be able to run the same code on non-windows machines eventually. For my testing, I am running following client and server code on the same windows box.
On my windows box, I have following simple client side code using .net 5:
HttpClientHandler handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12;
X509Certificate2 leafCert = new X509Certificate2(File.ReadAllBytes(#"C:\Temp\TestLeaf.pfx"), "<password>");
handler.ClientCertificates.Add(leafCert);
HttpClient httpClient = new HttpClient(handler);
StringContent content = new StringContent("{}"); //Test json string
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(MediaTypeNames.Application.Json);
//With local.TestServer.com resolving to localhost in the host file
HttpResponseMessage response = httpClient.PostAsync("https://local.TestServer.com/...", content).Result;
if (response.IsSuccessStatusCode)
{
var responseString = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(responseString);
}
else
{
Console.WriteLine(x.StatusCode);
Console.WriteLine(x.ReasonPhrase);
}
On same window box, I have following example snippet of server side code using kestrel in .net 5:
services.Configure<KestrelServerOptions>(options =>
{
// Keep track of what certs belong to each port
var certsGroupedByPort = ...;
var certsPerDistinctSslPortMap = ...;
// Listen to each distinct ssl port a cert specifies
foreach (var certsPerDistinctSslPort in certsPerDistinctSslPortMap)
{
options.Listen(IPAddress.Any, certsPerDistinctSslPort.Key, listenOptions =>
{
var httpsConnectionAdapterOptions = new HttpsConnectionAdapterOptions();
httpsConnectionAdapterOptions.ClientCertificateValidation = (clientCertificate, chain, sslPolicyErrors) =>
{
bool trusted = false;
if (sslPolicyErrors == System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors)
{
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
X509Certificate2 certRoot = new X509Certificate2(#"C:\Temp\TestRoot.pem");
X509Certificate2 certIntermdiate = new X509Certificate2(#"C:\Temp\TestIntermediate.pem");
chain.ChainPolicy.CustomTrustStore.Add(certRoot);
chain.ChainPolicy.ExtraStore.Add(certIntermdiate);
trusted = chain.Build(clientCertificate);
}
return trusted;
};
httpsConnectionAdapterOptions.ServerCertificateSelector = (connectionContext, sniName) =>
{
var defaultCert = //Get default cert
return defaultCert;
};
httpsConnectionAdapterOptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
httpsConnectionAdapterOptions.SslProtocols = SslProtocols.Tls12;
listenOptions.UseHttps(httpsConnectionAdapterOptions);
});
}
options.Listen(IPAddress.Any, listeningPort);
});
The above code works as expected because the client code sends the leaf certificate to the server and the server code has access to both intermediate as well as root certificates. The server code can successfully rebuild the certificate hierarchy with received leaf certificate and its configured intermediate and root certs for the leaf certificate.
My following attempt to send the intermediate certificate (along with leaf certificate) to the server (so that it can only use the root certificate and incoming leaf and intermediate certificates in the request to build the certificate hierarchy) failed.
Tried to add the intermediate certificate by doing following in my client code:
X509Certificate2 leafCert = new X509Certificate2(File.ReadAllBytes(#"C:\Temp\TestLeaf.pfx"), "");
X509Certificate2(Convert.FromBase64String(File.ReadAllText(#"C:\Temp\TestIntermediate.pem"));
handler.ClientCertificates.Add(leafCert);
handler.ClientCertificates.Add(intermediateCert);
This did not send the intermediate certificate to the server. I verified this with the code block for httpsConnectionAdapterOptions.ClientCertificateValidation on the server side.
Question:
Is there a way to ensure that intermediate certificate is sent by the client (in addition to the leaf cert) to the server?

Related

Use MbedTLS to read the certificate authority of the client

I'm using MbedTLS v3.1 on an MCU as a server, and I have configured a PKI self-signed with a Root CA, two sub-ca's and a leaf certificate. The server is configured with a certificate chain with the two sub-ca's and the leaf cert. The client for testing, made with Node.js, uses the Root CA.
This is an example of the client, simplified:
var options = {
ca: fs.readFileSync('pe_certs/RootCACert.pem'),
rejectUnauthorized: true,
};
var client = tls.connect(PORT, HOST, options, () => {
console.log('Connected to %s on %s', HOST, PORT)
client.write("...")
});
The private key and certificate chain on the server is loaded as follows:
// SERVER_CRT is a string with a chain of three certs in PEM
// two sub-ca's and the leaf cert
int ccs_mbedtls_load_certs()
{
int ret = 0;
ret = mbedtls_x509_crt_parse( &tls_lv.cacert, SERVER_CRT, SERVER_CRT_len );
if ( ret == RET_SUCCESS )
{
ret = mbedtls_pk_parse_key( &tls_lv.pkey, (const unsigned char *)
SERVER_KEY, SERVER_KEY_len,
(const unsigned char *) SERVER_KEY_PWD,
SERVER_KEY_PWD_LEN,
dummy_random, NULL );
}
return ret;
}
The communication is working well, I can connect from the client and transmit data to the server. Wireshark is showing a good trace.
The situation is that sometimes I can find a client that uses a different Root CA than the one used to generate my two sub-ca's, so in this case the communication could not be carried out (the client rejects it).
Is there any way for the server to extract information from the client's CA_CERT? Knowing the issuer, subject nameā€¦ etc.?
I'm playing with the object ssl.session (mbedtls_ssl_context) after the handshake, but I can not find any useful information there.

Xamarin HttpClient adding Client Certificate

I'm trying to send a request to a web api in Xamarin.Forms. The api requires a client certificate. I attempted to add the client certificate in the core project and in the native project as e.g. described here xamarin.android adding client certificate. However, I always get the response "400 No required SSL certificate was sent". If I send the request via e.g. Postman or openSSL, everything works fine. I've tested the request on Android and on iOS, but I always get the 400. Can anyone help?
Note: For Android, I am using the HttpClient implementation 'Android' and the TLS implementation 'Native TLS 1.2+'
The code I am using in the core project:
var handler = new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
SslProtocols = System.Security.Authentication.SslProtocols.Tls12,
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
};
//clientCertificateFileName is the location where the certificate is saved
var clientCertificate = new X509Certificate2(clientCertificateFileName, "password");
handler.ClientCertificates.Add(clientCertificate);
client = new HttpClient(handler);
var response = await client.GetAsync(targetUrl);

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
}
}

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.