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
}
}
Related
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?
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")
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 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.
I have a Java Key Store where I store certificates for each of my customer's sub-domain. I am planning to use the server alias to differentiate between multiple customers in the key store as suggested here. Play framework 1.2.7 uses Netty's SslHandler to support SSL on the server-side. I tried implementing a custom SslHttpServerContextFactory that uses this solution.
import play.Play;
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyStore;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Properties;
public class CustomSslHttpServerContextFactory {
private static final String PROTOCOL = "SSL";
private static final SSLContext SERVER_CONTEXT;
static {
String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
if (algorithm == null) {
algorithm = "SunX509";
}
SSLContext serverContext = null;
KeyStore ks = null;
try {
final Properties p = Play.configuration;
// Try to load it from the keystore
ks = KeyStore.getInstance(p.getProperty("keystore.algorithm", "JKS"));
// Load the file from the conf
char[] certificatePassword = p.getProperty("keystore.password", "secret").toCharArray();
ks.load(new FileInputStream(Play.getFile(p.getProperty("keystore.file", "conf/certificate.jks"))),
certificatePassword);
// Set up key manager factory to use our key store
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
kmf.init(ks, certificatePassword);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
tmf.init(ks);
final X509KeyManager origKm = (X509KeyManager) kmf.getKeyManagers()[0];
X509KeyManager km = new X509KeyManagerWrapper(origKm);
// Initialize the SSLContext to work with our key managers.
serverContext = SSLContext.getInstance(PROTOCOL);
serverContext.init(new KeyManager[]{km}, tmf.getTrustManagers(), null);
} catch (Exception e) {
throw new Error("Failed to initialize the server-side SSLContext", e);
}
SERVER_CONTEXT = serverContext;
}
public static SSLContext getServerContext() {
return SERVER_CONTEXT;
}
public static class X509KeyManagerWrapper implements X509KeyManager {
final X509KeyManager origKm;
public X509KeyManagerWrapper(X509KeyManager origKm) {
this.origKm = origKm;
}
public String chooseServerAlias(String keyType,
Principal[] issuers, Socket socket) {
InetAddress remoteAddress = socket.getInetAddress();
//TODO: Implement alias selection based on remoteAddress
return origKm.chooseServerAlias(keyType, issuers, socket);
}
#Override
public String chooseClientAlias(String[] keyType,
Principal[] issuers, Socket socket) {
return origKm.chooseClientAlias(keyType, issuers, socket);
}
#Override
public String[] getClientAliases(String s, Principal[] principals) {
return origKm.getClientAliases(s, principals);
}
#Override
public String[] getServerAliases(String s, Principal[] principals) {
return origKm.getServerAliases(s, principals);
}
#Override
public X509Certificate[] getCertificateChain(String s) {
return origKm.getCertificateChain(s);
}
#Override
public PrivateKey getPrivateKey(String s) {
return origKm.getPrivateKey(s);
}
}
}
But, this approach did not work for some reason. I get this message in my SSL debug log.
X509KeyManager passed to SSLContext.init(): need an X509ExtendedKeyManager for SSLEngine use
This is the SSL trace, which fails with "no cipher suites in common". Now, I switched the wrapper to:
public static class X509KeyManagerWrapper extends X509ExtendedKeyManager
With this change, I got rid of the warning, but I still see the same error as before "no cipher suites in common" and here is the SSL trace. I am not sure why the delegation of key manager won't work.
Some more information that may be useful in this context.
Netty uses javax.net.ssl.SSLEngine to support SSL in NIO server.
As per the recommendation in this bug report, it is intentional that X509ExtendedKeyManager must be used with an SSLEngine. So, the wrapper must extend X509ExtendedKeyManager.
This is hindering me to move further with the custom alias selection logic in X509KeyManagerWrapper. Any clues on what might be happening here? Is there any other way to implement this in Netty/Play? Appreciate any suggestions.
SSLEngine uses the chooseEngineServerAlias method to pick the certificate to use (in server mode) - not the chooseServerAlias method.
The default chooseEngineServerAlias implementation actually returns null, which is what causes the "no cipher suites in common" message - you need a certificate to know which cipher suites can be used (e.g. ECDSA can only be used for authentication if the certificate has an ECC public key, etc.) There are actually some cipher suites which can be used without a certificate, however, these are typically disabled as they are vulnerable to MITM attacks.
Therefore, you should also override chooseEngineServerAlias, and implement your logic to select the certificate based on the IP address there. As Netty only uses SSLEngine, what chooseServerAlias does doesn't matter - it'll never be called.
Java 8 also has support for server-side SNI, which allows you to use several certificates across many hostnames with a single IP address. Most web browsers support SNI - the notable exceptions are IE running on Windows XP and some old versions of Android, however, usage of these is declining. I have created a small example application demonstrating how to use SNI in Netty on GitHub. The core part of how it works is by overriding chooseEngineServerAlias - which should give you enough hints, even if you want to use the one certificate per IP address technique instead of SNI.
(I posted a similar answer to this on the Netty mailing list, where you also asked this question - however, my post seems to have not yet been approved, so I thought I'd answer here too so you can get an answer sooner.)