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!
Is it possible to reload at runtime certificates in a web server using API of Akka-HTTP? I would that the HttpsConnectionContext is reloaded without shutdown of server. Is there a way to do that? I've already implemented a mechanism that read the renew of certificate but my problem is to reload the context at runtime.
Below I show how my server is started:
log.info("HTTPS ENABLED!")
val https: HttpsConnectionContext = newHttpsConnectionContext()
val (host, port, connectionContext) = ("0.0.0.0", 8080, https)
log.debug(s" Binding RESTful Web Services ... https://$host:$port/")
val bindingFuture =
Http().bindAndHandle(
endpoints,
host,
port,
connectionContext
)
bindingFuture.onComplete {
case Success(bind) =>
log.info(s"HTTPS server binding $bind")
binding = Some(bind)
log.warn(
s" Service online at https://$host:$port/"
)
case Failure(ex) =>
log.error(
s" Failed to bind to https://$host:$port/ - Error : ${ex.getMessage}"
)
close()
}
}
def newHttpsConnectionContext(): HttpsConnectionContext = {
import myService.TlsSettings
log.debug(
s"Creating a new HTTPS Connection Context between my Service (Server) and Clients..."
)
val sslParameters: Option[SSLParameters] = None
val sslConnectionContext: HttpsConnectionContext =
ConnectionContext.https(
TlsSettings(
ApplicationProperties.clientCert,
ApplicationProperties.clientPrivKey
).sslContext,
None,
Some(SSL_CIPHER_SUITES),
Some(SSL_PROTOCOLS),
Some(TLSClientAuth.Need),
sslParameters
)
log.info(
s"HTTPS Connection Context my Service <--> Clients created! $sslConnectionContext"
)
sslConnectionContext
}
I'm using Akka (version 2.5.18) to send JSON strings to a specific server via https. I have used a poolRouter (balancing-pool with 10 instances) in order to create a pool of actors that are going to send JSONs (generated from different customers) to a single server:
val router: ActorRef = system.actorOf(
FromConfig.props(Props(new SenderActor(configuration.getString("https://server.com"), this.self))),
"poolRouter"
)
The project specification says that the requests can also be sent using curl:
curl -X PUT --cert certificate.pem --key private.key -H 'Content-Type: application / json' -H 'cache-control: no-cache' -d '[{"id" : "test"}] 'https://server.com'
Where "certificate.pem" is the tls certificate of the customer and "private.key" is the private key used to generate the CSR of the customer.
I'm using a balancing-pool because I will have a very big set of certificates (one for each customer) and I need to send the requests concurrently.
My approach is to have a "SenderActor" class that will be created by the balancing pool. Each actor, upon the reception of a message with a "customerId" and the JSON data generated by this customer, will send a https request:
override def receive: Receive = {
case Data(customerId, jsonData) =>
send(customerId(cid, jsonData))
Each SenderActor will read the certificate (and the private key) based on a path using the customerId. For instance, the customerId: "cust1" will have their certificate and key stored in "/home/test/cust1". This way, the same actor class can be used for all the customers.
According to the documentation, I need to create a HttpsConnectionContext in order to send the different requests:
def send(customerId: String, dataToSend): Future[HttpResponse] = {
// Create the request
val req = HttpRequest(
PUT,
uri = "https://server.com",
entity = HttpEntity(`application/x-www-form-urlencoded` withCharset `UTF-8`, dataToSend),
protocol = `HTTP/1.0`)
val ctx: SSLContext = SSLContext.getInstance("TLS")
val permissiveTrustManager: TrustManager = new X509TrustManager() {
override def checkClientTrusted(chain: Array[X509Certificate], authType: String): Unit = {}
override def checkServerTrusted(chain: Array[X509Certificate], authType: String): Unit = {}
override def getAcceptedIssuers(): Array[X509Certificate] = Array.empty
}
ctx.init(Array.empty, Array(permissiveTrustManager), new SecureRandom())
val httpsConnContext: HttpsConnectionContext = ConnectionContext.https(ctx)
// Send the request
Http(system).singleRequest(req, httpsConnContext)
}
The problem I have is that I don't have any clue about how to "set the certificate and the key" in the request, so that the server accepts them.
For instance, I can read the certificate using the following code:
import java.util.Base64
val certificate: String => String = (customer: String) => IO {
Source.fromInputStream(getClass.getClassLoader
.getResourceAsStream("/home/test/".concat(customer).concat("_cert.pem")))
.getLines().mkString
}.unsafeRunSync()
val decodedCertificate = Base64.getDecoder.decode(certificate(customerId)
.replaceAll(X509Factory.BEGIN_CERT, "").replaceAll(X509Factory.END_CERT, ""))
val cert: Certificate = CertificateFactory.getInstance("X.509")
.generateCertificate(new ByteArrayInputStream(decodedCertificate))
But I don't know how to "set" this certificate and the private key in the request (which is protected by a passphrase), so that the server accepts it.
Any hint or help would be greatly appreciated.
The following allows making a https request and identifying yourself with a private key from a x.509 certificate.
The following libraries are used to manage ssl configuration and to make https calls:
ssl-config
akka-http
Convert your pem certificate to pks12 format as defined here
openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt
Define key-store in your application.conf. It supports only pkcs12 and because of
this step 1 is required.
ssl-config {
keyManager {
stores = [
{
type = "pkcs12"
path = "/path/to/pkcs12/cetificate"
password = changeme //the password is set when using openssl
}
]
}
}
Load ssl config using special akka trait DefaultSSLContextCreation
import akka.actor.ActorSystem
import akka.actor.ExtendedActorSystem
import akka.http.scaladsl.DefaultSSLContextCreation
import com.typesafe.sslconfig.akka.AkkaSSLConfig
import com.typesafe.sslconfig.ssl.SSLConfigFactory
class TlsProvider(val actorSystem: ActorSystem) extends DefaultSSLContextCreation {
override protected def sslConfig: AkkaSSLConfig =
throw new RuntimeException("Unsupported behaviour when creating new sslConfig")
def httpsConnectionContext() = {
val akkaSslConfig =
new AkkaSSLConfig(system.asInstanceOf[ExtendedActorSystem], SSLConfigFactory.parse(system.settings.config))
createClientHttpsContext(akkaSslConfig)
}
}
Create a https context and use in http connection pool.
Http(actorSystem).cachedHostConnectionPoolHttps[RequestContext](
host = host,
port = portValue,
connectionContext = new TlsProvider(actorSystem).httpsConnectionContext()
)
Or set connection context to Http(actorSystem).singleRequest method.
In summary, I used ssl-config library to manage certificates instead of doing it programmatically yourself. By defining a keyManager in a ssl-config, any http request done with help of custom httpsConnectionContext will use the certificate to identify the caller/client.
I focused on describing how to establish a https connection using client certificate. Any dynamic behavior for managing multiple certificates is omitted. But I hope this code should be able give you understanding how to proceed.
I looked extensively online if this question was answered elsewhere and it was not.
When trying to use CRLs in the python openssl library, we are getting "certificate unknown" errors. Specifically, when the crl has a revocation of a cert that is not used in the handshake of the ssl connection, there is still an error. If instead an empty CRL is used, there is no error and the devices are able to complete a socket.connect()
Simple client/server model to illustrate problem:
Server:
def createServerSideSocket(port, backlog=5):
context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(certfile="cert.pem", keyfile="key.pem", password='tempPassword')
context.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF #specifying flag to check crl
context.load_verify_locations(cafile = "crl.pem") #loading crl
sock = socket.socket()
sock.bind(('', port))
sock.listen(backlog) #listen on port 5000
return sock, context
port = 5000
sock, context = createServerSideSocket(port)
newSocket, fromAddr = sock.accept()
connStream = context.wrap_socket(newSocket, server_side=True)
print(bytes.decode(connStream.recv(1024))) #receive and print data
Client:
def createClientSideSocket(server_ip):
context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH, cafile="cacert.pem")
context.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF #specifying flag to check crl
context.load_verify_locations("crl.pem") #loading crl
conn = context.wrap_socket(socket.socket(), server_hostname=server_ip)
return conn
conn = createClientSideSocket("192.168.1.7") #our IP address of the server
conn.connect(("192.168.1.7", 5000))
conn.sendall(str.encode("test data"))
When crl.pem has 0 revoked certs, connection works. When crl.pem has any revocation (even for revocations not used in the handshake) it fails with the following error:
on client:
ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:833)
on server:
[SSL: SSLV3_ALERT_CERTIFICATE_UNKNOWN] sslv3 alert certificate unknown (_ssl.c:777)
Trying out the Nest API, I got the OAuth flow working without problems, made the first API call (to https://developer-api.nest.com/devices.json), got the 307 redirect as expected, but then my call to the redirect location fails with Remote host closed connection during handshake. I went to the Nest developer event in San Francisco last night, and Lev Stesin told me to post a full log here and mention his name.
Code (Apex, running on Force.com):
public with sharing virtual class NestController {
public class OAuthResponse {
public String access_token;
public String token_type;
public Integer expires_in;
public String refresh_token;
public String error;
}
public static OAuthResponse parse(String json) {
return (OAuthResponse) System.JSON.deserialize(json, OAuthResponse.class);
}
public String accessToken {get; set;}
public String output {get; set;}
private String getAll(String accessToken) {
String url = 'https://developer-api.nest.com/devices.json?auth='+accessToken+'&print=pretty';
HttpRequest req = new HttpRequest();
req.setEndpoint(url);
req.setMethod('GET');
req.setTimeout(60*1000);
Http h = new Http();
String resp;
HttpResponse res = h.send(req);
resp = res.getBody();
if (res.getStatusCode() == 307) {
url = res.getHeader('Location');
System.debug('Redirect to: '+url);
req = new HttpRequest();
req.setEndpoint(url);
req.setMethod('GET');
req.setTimeout(60*1000);
h = new Http();
res = h.send(req);
resp = res.getBody();
}
System.debug('Get returns: '+resp);
return resp;
}
public virtual PageReference login() {
String clientId = '989360fb-9a1f-4d13-929e-0b40111c725a';
String clientSecret = 'SECRET';
String sessionId = null;
String state = 'wow';
// Get a URL for the page without any query params
String url = ApexPages.currentPage().getUrl().split('\\?')[0];
System.debug('url is '+url);
// note: connect url in fb application connect setting should be: https://c.na3.visual.force.com/apex/
// you need the trailing slash even though it bitches about it
String rediruri = 'https://'+ApexPages.currentPage().getHeaders().get('Host')+url;
System.debug('rediruri is:'+rediruri);
String authuri = 'https://home.nest.com/login/oauth2'+
'?client_id='+clientId+
'&state='+state;
// No session
PageReference pageRef;
if (ApexPages.currentPage().getParameters().containsKey('error')) {
// Initial step of OAuth - redirect to OAuth service
System.debug('Error:' + ApexPages.currentPage().getParameters().get('error'));
return null;
}
if (! ApexPages.currentPage().getParameters().containsKey('code')) {
// Initial step of OAuth - redirect to OAuth service
System.debug('Nest OAuth Step 1');
return new PageReference(authuri);
}
// Second step of OAuth - get token from OAuth service
String code = ApexPages.currentPage().getParameters().get('code');
System.debug('Nest OAuth Step 2 - code:'+code);
String tokenuri = 'https://api.home.nest.com/oauth2/access_token';
String body = 'code='+code+
'&client_id='+clientId+
'&client_secret='+clientSecret+
'&grant_type=authorization_code';
System.debug('body is:'+body);
HttpRequest req = new HttpRequest();
req.setEndpoint(tokenuri);
req.setMethod('POST');
req.setTimeout(60*1000);
req.setBody(body);
Http h = new Http();
String resp;
if (code.equals('TEST')) {
resp = 'access_token=TEST&expires=3600';
} else {
HttpResponse res = h.send(req);
resp = res.getBody();
}
System.debug('FINAL RESP IS:'+resp);
OAuthResponse oauth = parse(resp);
if (oauth.error != null) {
// Error getting token - probably reusing code - start again
return new PageReference(authuri);
}
accessToken = oauth.access_token;
output = getAll(accessToken);
return null;
}
}
Initial OAuth Redirect:
https://home.nest.com/login/oauth2?client_id=989360fb-9a1f-4d13-929e-0b40111c725a&state=wow
User authorizes app to access thermostats, Nest redirects back to my app:
https://c.na9.visual.force.com/apex/Nest?state=wow&code=6F3GV6WQ35NGLYB2
I successfully exchange the code for an access token:
POST to https://api.home.nest.com/oauth2/access_token with body
code=6F3GV6WQ35NGLYB2&client_id=989360fb-9a1f-4d13-929e-0b40111c725a&client_secret=SECRET&grant_type=authorization_code
Response:
{"access_token":"c.eDzTiwBeVak0Jq7RWVjBJPXrZT8kI5Hh4rgnYG7eDvzytZbqTJbMsnGBHLUcKOSZ7xjk8NR4oNAE4iUh1EBtkHllg55C0Ckb29jsSqL5VwdMxSUoTSBDkKt8QzMAoUCD3Ru8iSo7XYpPc8qU","expires_in":315360000}
(I revoked the token from home.nest.com, so it's safe for me to post here!)
So I do a GET on
https://developer-api.nest.com/devices.json?auth=c.eDzTiwBeVak0Jq7RWVjBJPXrZT8kI5Hh4rgnYG7eDvzytZbqTJbMsnGBHLUcKOSZ7xjk8NR4oNAE4iUh1EBtkHllg55C0Ckb29jsSqL5VwdMxSUoTSBDkKt8QzMAoUCD3Ru8iSo7XYpPc8qU&print=pretty
and receive the expected 307 redirect, with location
https://firebase-apiserver01-tah01-iad01.dapi.production.nest.com:9553/devices.json?auth=c.eDzTiwBeVak0Jq7RWVjBJPXrZT8kI5Hh4rgnYG7eDvzytZbqTJbMsnGBHLUcKOSZ7xjk8NR4oNAE4iUh1EBtkHllg55C0Ckb29jsSqL5VwdMxSUoTSBDkKt8QzMAoUCD3Ru8iSo7XYpPc8qU&print=pretty
Now, when I GET that URL in my Apex code running on Force.com, it fails with
System.CalloutException: Remote host closed connection during handshake
But if I do the same GET from curl on the command line, it succeeds, returning the expected JSON response.
So it looks like there may be some incompatibility in the SSL handshake. I'll investigate at the Force.com end; it would be good if someone at Nest could check the logs at their end - there should be enough detail here.
EDIT - Here's the output from curl -v to that URL:
$ curl -v 'https://firebase-apiserver01-tah01-iad01.dapi.production.nest.com:9553/devices.json?auth=c.dPHNEweWehQ47tzSm0sf13o8rX1isO9IdEG1HFwoAmeA2FtBLH1fTiksRtN9DGcPAOyEI3VINz2fD3CFma5ozSNbpqUIwGDGc8ixD1etjiIW6TmXN0Rd0p5VzEtk6sDwIe8j10NH1hKDhevX&print=pretty'
* About to connect() to firebase-apiserver01-tah01-iad01.dapi.production.nest.com port 9553 (#0)
* Trying 54.196.205.148...
* connected
* Connected to firebase-apiserver01-tah01-iad01.dapi.production.nest.com (54.196.205.148) port 9553 (#0)
* SSLv3, TLS handshake, Client hello (1):
* SSLv3, TLS handshake, Server hello (2):
* SSLv3, TLS handshake, CERT (11):
* SSLv3, TLS handshake, Server key exchange (12):
* SSLv3, TLS handshake, Server finished (14):
* SSLv3, TLS handshake, Client key exchange (16):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSL connection using EDH-RSA-DES-CBC3-SHA
* Server certificate:
* subject: OU=Domain Control Validated; CN=*.dapi.production.nest.com
* start date: 2014-05-28 22:31:28 GMT
* expire date: 2015-05-28 22:31:28 GMT
* subjectAltName: firebase-apiserver01-tah01-iad01.dapi.production.nest.com matched
* issuer: C=US; ST=Arizona; L=Scottsdale; O=GoDaddy.com, Inc.; OU=http://certs.godaddy.com/repository/; CN=Go Daddy Secure Certificate Authority - G2
* SSL certificate verify ok.
> GET /devices.json?auth=c.dPHNEweWehQ47tzSm0sf13o8rX1isO9IdEG1HFwoAmeA2FtBLH1fTiksRtN9DGcPAOyEI3VINz2fD3CFma5ozSNbpqUIwGDGc8ixD1etjiIW6TmXN0Rd0p5VzEtk6sDwIe8j10NH1hKDhevX&print=pretty HTTP/1.1
> User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8y zlib/1.2.5
> Host: firebase-apiserver01-tah01-iad01.dapi.production.nest.com:9553
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=UTF-8
< Access-Control-Allow-Origin: *
< Cache-Control: private, no-cache, max-age=0
< Content-Length: 2218
<
{
"thermostats" : {
"pYo-lbpXuVm_DctuTckA_HdEswRgRkbx" : {
"locale" : "en-US",
"temperature_scale" : "F",
"is_using_emergency_heat" : false,
"has_fan" : true,
"software_version" : "4.2.3",
"has_leaf" : true,
"device_id" : "pYo-lbpXuVm_DctuTckA_HdEswRgRkbx",
"name" : "Downstairs",
"can_heat" : true,
"can_cool" : true,
"hvac_mode" : "off",
"target_temperature_c" : 24.5,
"target_temperature_f" : 76,
"target_temperature_high_c" : 24.0,
"target_temperature_high_f" : 75,
"target_temperature_low_c" : 20.0,
"target_temperature_low_f" : 68,
"ambient_temperature_c" : 25.0,
"ambient_temperature_f" : 78,
"away_temperature_high_c" : 24.0,
"away_temperature_high_f" : 76,
"away_temperature_low_c" : 15.5,
"away_temperature_low_f" : 60,
"structure_id" : "HqSZlH08Jc3CtBNIS4OLPdiWLpcfW5o6dP2DvSox7hcGVpBGOH9cQA",
"fan_timer_active" : false,
"name_long" : "Downstairs Thermostat",
"is_online" : true,
"last_connection" : "2014-06-26T23:16:24.341Z"
},
"pYo-lbpXuVncrx7IdGTWyXdEswRgRkbx" : {
"locale" : "en-US",
"temperature_scale" : "F",
"is_using_emergency_heat" : false,
"has_fan" : true,
"software_version" : "4.2.3",
"has_leaf" : true,
"device_id" : "pYo-lbpXuVncrx7IdGTWyXdEswRgRkbx",
"name" : "Upstairs",
"can_heat" : true,
"can_cool" : true,
"hvac_mode" : "off",
"target_temperature_c" : 24.0,
"target_temperature_f" : 76,
"target_temperature_high_c" : 24.0,
"target_temperature_high_f" : 75,
"target_temperature_low_c" : 20.0,
"target_temperature_low_f" : 68,
"ambient_temperature_c" : 25.0,
"ambient_temperature_f" : 78,
"away_temperature_high_c" : 24.0,
"away_temperature_high_f" : 76,
"away_temperature_low_c" : 15.5,
"away_temperature_low_f" : 60,
"structure_id" : "HqSZlH08Jc3CtBNIS4OLPdiWLpcfW5o6dP2DvSox7hcGVpBGOH9cQA",
"fan_timer_active" : false,
"name_long" : "Upstairs Thermostat",
"is_online" : true,
"last_connection" : "2014-06-26T23:16:27.849Z"
}
}
* Connection #0 to host firebase-apiserver01-tah01-iad01.dapi.production.nest.com left intact
}* Closing connection #0
* SSLv3, TLS alert, Client hello (1):
I don't think the server supports SSLv3. Try using --tlsv1 and see if that works.
The same callout from Salesforce works just fine now. I guess Nest or Force.com must have tweaked some SSL config.