I have set up TLS in mongod.conf. I need to use spring boot to connect to my mongo which requires tls now. In MongoCompass, I set Certificate Authority, Client Certificate & Client Private Key to root-ca.pem, test.pem & test.pem accordingly and I am able to connect. How can i specify the root-ca.pem & test.pem correctly in mongoclientoptions to connect to my mongo?
This is my mongod.conf
# network interfaces
net:
port: 27017
bindIp: 127.0.0.1
tls:
mode: requireTLS
certificateKeyFile: C:\TLSServerMongo\test.pem
CAFile: C:\TLSServerMongo\root-ca.pem
clusterFile: C:\TLSServerMongo\test.pem
allowInvalidCertificates: true
FIPSMode : false
This is my mongoclientoptions
#Bean
public MongoClientOptions mongoClientOptions() {
MongoClientOptions.Builder mongoClientOptions = MongoClientOptions.builder().sslInvalidHostNameAllowed(true).sslEnabled(true);
try {
// String fileName = directory + RDS_COMBINED_CA_BUNDLE;
String fileName = "C:\\TLSServerMongo\\test.pem";
InputStream is = new FileInputStream(fileName);
// You could get a resource as a stream instead.
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate caCert = (X509Certificate) cf.generateCertificate(is);
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null); // You don't need the KeyStore instance to come from a file.
ks.setCertificateEntry("caCert", caCert);
tmf.init(ks);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
mongoClientOptions.sslContext(sslContext);
} catch (Exception e) {
LOGGER.error(e.getMessage());
}
return mongoClientOptions.build();
}
This is my MongoClient
public #Bean
MongoClient mongoClient() {
List<MongoCredential> allCred = new ArrayList<>();
allCred.add(MongoCredential.createCredential(username, database, password.toCharArray()));
MongoClient client = new MongoClient((new ServerAddress(this.myHost, this.myPort)), allCred, mongoClientOptions());
client.setWriteConcern(WriteConcern.ACKNOWLEDGED);
return client;
}
It will be a Good idea to create one single .jks file as a cert and use it in spring boot mongo client...
pls refre to this for converting your .pem cert into JKS Convert .pem files to .jks
Once you have the .jks in your keystore on your system then we can use it or you may follow this example to connect using jks...
Connecting to MongoDB from spring boot app using ssl
or
https://dba.stackexchange.com/questions/206462/how-to-configure-ssl-mongodb-connection-in-yml-file-spring-boot
Hope it helps!
Related
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
I would create a TLS Session in Scala using Akka with mutual authentication between a client and a server. I have created two CA certificate that have to trust the respective certificates incoming from the other part.
Could you give me an exemple of how implement this?
Thank you.
I created a github project which demonstrates mutual authentication with different kind of clients, including Akka. Please have a look here: https://github.com/Hakky54/mutual-tls-ssl
It contains a full example of loading the ssl material into the client and server
A summary what you need to do is:
For the client
Create a key and a certificate and load it into a keystore
Export the certificate
Create a separate keystore for trusted certificates and import server certificate
Load the two keystores into your http client
For the server
Create a key and a certificate and load it into a keystore
Export the certificate
Create a separate keystore for trusted certificates and import client certificate
Load the two keystores into your server
It is not really clear to me what kind of server you are using, but if you are using spring-boot the example configuration would be:
server:
port: 8443
ssl:
enabled: true
key-store: classpath:identity.jks
key-password: secret
key-store-password: secret
trust-store: classpath:truststore.jks
trust-store-password: secret
client-auth: need
Akka requires a pre-configured instance of SSLContext to be able to configure HTTPS. An example of creating a client with https options would be the code snippet below.
import akka.actor.ActorSystem;
import akka.http.javadsl.ConnectionContext;
import akka.http.javadsl.Http;
import akka.http.javadsl.HttpsConnectionContext;
import com.typesafe.config.ConfigFactory;
import javax.net.ssl.SSLContext;
import java.util.Optional;
class App {
public static void main(String[] args) {
ActorSystem actorSystem = ActorSystem.create(
App.class.getSimpleName(),
ConfigFactory.defaultApplication(App.class.getClassLoader())
);
SSLContext sslContext = ...; //Initialized SSLContext
Http http = Http.get(actorSystem);
HttpsConnectionContext httpsContext = ConnectionContext.https(
sslContext,
Optional.empty(),
Optional.empty(),
Optional.empty(),
Optional.of(sslContext.getDefaultSSLParameters()));
http.setDefaultClientHttpsContext(httpsContext);
}
}
There are couple of libraries which provides easy to use utility/factory/builder classes to help you to create a SSLContext.
Apache SSLContextBuilder
Jetty SslContextFactory
SSLContext-Kickstart
There could be a bunch other libraries which provide similar functionality, but I am only aware of these three. By the way the sslcontext-kickstart is a library which is maintained by me.
Below is an overview of four ways to load the keystores and create an SSLContext. Vanilla Java and by using the three libraries.
import io.netty.handler.ssl.SslContextBuilder;
import nl.altindag.sslcontext.SSLFactory;
import org.apache.http.ssl.SSLContextBuilder;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import javax.net.ssl.*;
import java.io.File;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Objects;
class SslExample {
public static void main(String[] args) throws Exception {
//Traditional flow of creating sslContext
String keyStorePath = "keystore.p12";
String trustStorePath = "truststore.p12";
char[] keyStorePassword = "secret".toCharArray();
char[] trustStorePassword = "secret".toCharArray();
KeyStore keyStore = KeyStore.getInstance("PKCS12");
KeyStore trustStore = KeyStore.getInstance("PKCS12");
try(InputStream keyStoreInputStream = SslExample.class.getClassLoader().getResourceAsStream(keyStorePath);
InputStream trustStoreInputStream = SslExample.class.getClassLoader().getResourceAsStream(trustStorePath)) {
Objects.requireNonNull(keyStoreInputStream);
Objects.requireNonNull(trustStoreInputStream);
keyStore.load(keyStoreInputStream, keyStorePassword);
trustStore.load(trustStoreInputStream, trustStorePassword);
}
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyStorePassword);
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(keyManagers, trustManagers, new SecureRandom());
//creating sslContext with Apache SSLContextBuilder
SSLContext sslContext1 = SSLContextBuilder.create()
.loadKeyMaterial(new File("keystore.p12"), "secret".toCharArray(), "secret".toCharArray())
.loadTrustMaterial(new File("truststore.p12"), "secret".toCharArray())
.build();
//creating sslContext with Jetty SslContextFactory
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
sslContextFactory.setKeyStorePath("keystore.p12");
sslContextFactory.setKeyStorePassword("secret");
sslContextFactory.setTrustStorePath("truststore.p12");
sslContextFactory.setTrustStorePassword("secret");
sslContextFactory.start();
SSLContext sslContext2 = sslContextFactory.getSslContext();
//creating sslContext with sslcontext-kickstart
SSLFactory sslFactory = SSLFactory.builder()
.withIdentity("keystore.p12", "secret".toCharArray())
.withTrustStore("truststore.p12", "secret".toCharArray())
.build();
SSLContext sslContext3 = sslFactory.getSslContext();
}
}
It is in java, but IntelliJ Idea provides a handy translate function to scala when pasting the code snippet.
I tried to generate the token which can be used as the HTTP header to authenticate to the HDFS WebHDFS URL and Oozie REST API URL.
I referenced the url below to have the below code to generate the Negotiate token.
https://www.ibm.com/support/knowledgecenter/en/SS7JFU_8.5.5/com.ibm.websphere.express.doc/ae/tsec_SPNEGO_token.html
public class TokenCreation {
private static final String SPNEGO_OID = "1.3.6.1.5.5.2";
private static final String KERBEROS_OID = "1.2.840.113554.1.2.2";
public static byte[] genToken(String principal) {
System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
byte[] spnegoToken = new byte[0];
try {
Oid spnegoMechOid = new Oid(SPNEGO_OID);
Oid krb5MechOid = new Oid(KERBEROS_OID);
GSSCredential clientGssCreds = null;
GSSManager manager = GSSManager.getInstance();
GSSName gssUserName = manager.createName(principal, GSSName.NT_USER_NAME, krb5MechOid);
clientGssCreds = manager.createCredential(gssUserName.canonicalize(krb5MechOid),
GSSCredential.INDEFINITE_LIFETIME,
krb5MechOid,
GSSCredential.INITIATE_ONLY);
clientGssCreds.add(gssUserName,
GSSCredential.INDEFINITE_LIFETIME,
GSSCredential.INDEFINITE_LIFETIME,
spnegoMechOid, GSSCredential.INITIATE_ONLY);
GSSName gssServerName = manager.createName(principal, GSSName.NT_USER_NAME);
GSSContext clientContext = manager.createContext(gssServerName.canonicalize(spnegoMechOid),
spnegoMechOid,
clientGssCreds,
GSSContext.DEFAULT_LIFETIME);
// optional enable GSS credential delegation
clientContext.requestCredDeleg(true);
// create a SPNEGO token for the target server
spnegoToken = clientContext.initSecContext(spnegoToken, 0, spnegoToken.length);
} catch (GSSException e) {
e.printStackTrace();
}
return spnegoToken;
}
But after running the above code, I always got the below prompt:
2019-09-25 14:12:51 760 [INFO] [pool-2-thread-1] c.s.n.c.u.security.KrbUtils - after loginUserFromKeytab............AtoimcUser:HTTP/host1.exmaple.com#EXAMPLE.COM
2019-09-25 14:12:51 760 [INFO] [pool-2-thread-1] c.s.n.app.oozie.OozieAppCaller - ->>>>>>User Name is HTTP/host1.exmaple.com#EXAMPLE.COM
2019-09-25 14:12:51 760 [INFO] [pool-2-thread-1] c.s.n.app.oozie.OozieAppCaller - ->>>>>>Mode is KERBEROS
>>>KinitOptions cache name is /tmp/krb5cc_0
Kerberos username [root]: ^C^C^C
Kerberos password for root:
You can see at the end of the above output log.
The "Kerberos username" is always prompt to ask for username.
Also I have tried to manually run kinit the keytab.
and the above class can generate the token successfully.
But manually run kinit is NOT the way I wanted.
Would you please help it?
Thanks.
Kerberos and SPNEGO support in Java is cumbersome unfortunately.
I've created a small library to simplify some Kerberos use cases: https://github.com/bedrin/kerb4j
You can use it like this to generate SPNEGO token:
SpnegoClient spnegoClient = SpnegoClient.loginWithKeyTab("svc_consumer", "/opt/myapp/consumer.keytab");
URL url = new URL("http://api.provider.acme.com/api/operation1");
SpnegoContext context = spnegoClient.createContext("http://provider.acme.com"); // Will result in HTTP/provider.acme.com SPN
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Authorization", context.createTokenAsAuthroizationHeader());
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);
}
}
(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);