Send certificate file with Scala Dispatch - scala

I need to be able to send a certificate file (.pem, I think), with a get request using scala and dispatch.
How do you do that?

Based on the Java code in #sbridges sample, I came up with the following Scala code using dispatch. It creates a custom SSL context containing the certificates you provide (and only those; the default store of trusted root certificates is not used by this code when verifying the remote host).
class SslAuthenticatingHttp(certData: SslCertificateData) extends Http {
override val client = new AsyncHttpClient(
(new AsyncHttpClientConfig.Builder).setSSLContext(buildSslContext(certData)).build
)
private def buildSslContext(certData: SslCertificateData): SSLContext = {
import certData._
val clientCertStore = loadKeyStore(clientCertificateData, clientCertificatePassword)
val rootCertStore = loadKeyStore(rootCertificateData, rootCertificatePassword)
val keyManagerFactory = KeyManagerFactory.getInstance("SunX509")
keyManagerFactory.init(clientCertStore, clientCertificatePassword.toCharArray)
val keyManagers = keyManagerFactory.getKeyManagers()
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.init(rootCertStore)
val trustManagers = trustManagerFactory.getTrustManagers()
val context = SSLContext.getInstance("TLS")
context.init(keyManagers, trustManagers, null)
context
}
private def loadKeyStore(keyStoreData: Array[Byte], password: String): KeyStore = {
val store = KeyStore.getInstance(KeyStore.getDefaultType)
store.load(new ByteArrayInputStream(keyStoreData), password.toCharArray)
store
}
}
case class SslCertificateData (
clientCertificateData: Array[Byte],
clientCertificatePassword: String,
rootCertificateData: Array[Byte],
rootCertificatePassword: String)
which would be used as in:
val certificateData = SslCertificateData(/* bytes from .jks file for client cert here */, "secret!",
/* bytes from .jks file for root cert here */, "also secret!")
val http = new SslAuthenticatingHttp(certificateData)
val page = http(req OK as.String)
println(page())
Note that this keeps the certificate data in memory, which is not the most secure way to do it and consumes memory unnecessarily. It may in many cases be more suitable to store an InputStream or a filename in the SslCertificateData case class.

I am assuming you want to do https with client certificates. I think this needs to be set up at the jvm level, there is a good explanation here how to do it.
There seems to be a way to do this with ning directly, as explained here,
the code is copied below,
// read in PEM file and parse with commons-ssl PKCS8Key
// (ca.juliusdavies:not-yet-commons-ssl:0.3.11)
RandomAccessFile in = null;
byte[] b = new byte[(int) certFile.length()];
in = new RandomAccessFile( certFile, "r" );
in.readFully( b );
char[] password = hints.get( "password" ).toString().toCharArray();
PKCS8Key key = new PKCS8Key( b, password );
// create empty key store
store = KeyStore.getInstance( KeyStore.getDefaultType() );
store.load( null, password );
// cert chain is not important if you override the default KeyManager and/or
// TrustManager implementation, IIRC
store.setKeyEntry( alias, key.getPrivateKey(), password, new DefaultCertificate[0] );
// initialize key and trust managers -> default behavior
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance( "SunX509" );
// password for key and store have to be the same IIRC
keyManagerFactory.init( store, password );
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
TrustManagerFactory tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() );
tmf.init( store );
TrustManager[] trustManagers = tmf.getTrustManagers();
// override key and trust managers with desired behavior - for example
// * 'trust everything the server gives us' -> TrustManager#checkServerTrusted
// * 'always return a preset alias to use for auth' -> X509ExtendedKeyManager#chooseClientAlias, X509ExtendedKeyManager#chooseEngineClientAlias
for ( int i = 0; i < keyManagers.length; i++ )
{
if ( keyManagers[i] instanceof X509ExtendedKeyManager )
{
AHCKeyManager ahcKeyManager = new AHCKeyManager( (X509ExtendedKeyManager) keyManagers[i] );
keyManagers[i] = ahcKeyManager;
}
}
for ( int i = 0; i < trustManagers.length; i++ )
{
if ( tm instanceof X509TrustManager )
{
AHCTrustManager ahcTrustManager = new AHCTrustManager( manager, (X509TrustManager) trustManagers[i] );
trustManagers[i] = ahcTrustManager;
}
}
// construct SSLContext and feed to AHC config
SSLContext context = SSLContext.getInstance( "TLS" );
context.init( keyManagers, trustManagers, null );
ahcCfgBuilder.setSSLContext(context);

Related

How can I parameterise information in Gatling scenarios

I need to send specific parameters to a scenario that is being reused multiple times with different payloads depending on the workflows. The following is the code that is to be reused:
var reqName = ""
var payloadName = ""
lazy val sendInfo: ScenarioBuilder = scenario("Send info")
.exec(session => {
reqName = session("localReqName").as[String]
payloadName = session("localPayloadName").as[String]
session}
)
.exec(jms(s"$reqName")
.send
.queue(simQueue)
.textMessage(ElFileBody(s"$payloadName.json"))
)
.exec(session => {
val filePath = s"$payloadName"
val body = new String(Files.readAllBytes(Paths.get(ClassLoader.getSystemResource(filePath).toURI)))
logger.info("timestamp value: " + session("timestamp").as[String])
logger.debug("Template body:\n " + body)
session
})
I know that you can chain scenarios in Scala/Gatling but how can I pass in information like reqName and payloadName down the chain, where reqName is a parameter to indicate the name of the request where the info is being sent and payloadName is the name of the actual JSON payload for the related request:
lazy val randomInfoSend: ScenarioBuilder = scenario("Send random info payloads")
.feed(csv("IDfile.csv").circular)
.randomSwitch(
(randomInfoType1prob) -> exec(
feed(timeFeeder)
.exec(session => {
payloadName = "Info1payload.json"
reqName ="Info1Send"
session.set("localReqName", "Info1Send")
session.set("localPayloadName", "Info1payload.json")
session
})
.exec(sendInfo)
),
(100.0 - randomInfoType1prob) -> exec(
feed(timeFeeder)
.exec(session => {
payloadName = "Info2Payload.json"
reqName ="Info2Send"
session.set("localReqName", "Info2Send")
session.set("localPayloadName", "Info2Payload.json")
session
})
.exec(sendInfo)
)
I attempted the above but the values of that 2 specific parameters were not passed through correctly. (The IDs and timestamps were fed through correctly though) Any suggestions?
Please properly read the Session API documentation. Session is immutable so Session#set returns a new instance.

Jmeter Groovy JavaMail API multipart add content to sample result

Looking at answers posted in Reading Emails based on recipient email id in Jmeter using groovy I actually managed to use the recipient search term.
Using the below in a JSR223 Sampler
import javax.mail.Multipart
import javax.mail.internet.MimeMultipart
import javax.mail.Message
import javax.mail.search.RecipientStringTerm
Properties properties = new Properties()
properties.put('mail.imap.host', 'your mail server host') // i.e. imap.gmail.com
properties.put('mail.imap.port', your mail server port) // i.e. 993
properties.setProperty('mail.imap.socketFactory.class', 'javax.net.ssl.SSLSocketFactory')
properties.setProperty('mail.imap.socketFactory.fallback', 'false')
properties.setProperty('mail.imap.socketFactory.port', 'your_mail_server_port') // i.e. 993
def session = javax.mail.Session.getDefaultInstance(properties)
def store = session.getStore('imap')
store.connect('your username (usually email address)', 'your_password')
def inbox = store.getFolder('INBOX')
inbox.open(javax.mail.Folder.READ_ONLY)
def onlyToGivenUser = inbox.search(new RecipientStringTerm(Message.RecipientType.TO,'your_recipient_address')) // i.e. test+1#gmail.com
onlyFromGivenUser.each { message ->
if (message.getContent() instanceof Multipart) {
StringBuilder content = new StringBuilder()
def multipart = (Multipart) message.getContent()
multipart.eachWithIndex { Multipart entry, int i ->
def part = entry.getBodyPart(i)
if (part.isMimeType('text/plain')) {
content.append(part.getContent().toString())
}
}
SampleResult.setResponseData(content.toString(), 'UTF-8')
} else {
SampleResult.setResponseData(message.getContent().toString(), 'UTF-8')
}
}
This works perfectly, but fails when email is ContentType: multipart/MIXED as it does not drill down to multipart/RELATED, multipart/ALTERNATIVE and then to TEXT/PLAIN or TEXT/HTML, on which I like to do a regex on to extract a link from the body.
Guessing some counter on i is needed and an "if else", or something like mentioned here, but unsure how to convert to fit in the above script...
Any help would be much appreciated.
I stepped away from javax.mail.Multipart and javax.mail.internet.MimeMultipart and have implemented the below code in a While Controller
import javax.mail.Message
import javax.mail.search.RecipientStringTerm
Properties properties = new Properties();
properties.put('mail.imap.host', 'your mail server host') // i.e. imap.gmail.com
properties.put('mail.imap.port', your mail server port) // i.e. 993
properties.setProperty('mail.imap.socketFactory.class', 'javax.net.ssl.SSLSocketFactory')
properties.setProperty('mail.imap.socketFactory.fallback', 'false')
properties.setProperty('mail.imap.socketFactory.port', 'your_mail_server_port') // i.e. 993
def session = javax.mail.Session.getDefaultInstance(properties)
def store = session.getStore('imap')
store.connect('your username (usually email address)', 'your_password')
def inbox = store.getFolder('INBOX');
inbox.open(javax.mail.Folder.READ_ONLY);
def onlyToGivenUser = inbox.search(new RecipientStringTerm(Message.RecipientType.TO,'your_recipient_address')); // i.e. test+1#gmail.com
try {
onlyToGivenUser.each { message ->
ByteArrayOutputStream emailRaw = new ByteArrayOutputStream();
message.writeTo(emailRaw);
SampleResult.setResponseData(emailRaw.toString(), 'UTF-8');
}
} catch (Exception ex) {
log.warn("Something went wrong", ex);
throw ex;
}
Hope this helps someone one day.

Play Framework - edit Sec-WebSocket-Protocol in Web socket Response Header

Edit the Web socket header response sent from server to client.
I am creating a websocket server application using playframework. Right now the websocket response from the server is
taken care by Play. Following is the response header,
Request Header:
(UpgradeToWebSocket,),
(Host,localhost:8083),
(Connection,Upgrade),
(Upgrade,websocket),
(Sec-WebSocket-Version,13),
(Accept-Encoding,gzip, deflate, br),
(Accept-Language,en-US,en;q=0.9),
(Sec-WebSocket-Key,ZvfzpVo3EX4DFA4BRcgRIA==)
def chatSystem(): WebSocket = WebSocket.acceptOrResult[String, String] { request =>
Future.successful{
AuthenticationService.doBasicAuthentication(request.headers) match {
case Results.Ok => Right(ActorFlow.actorRef { out => ChatServiceActor.props(out) })
case _ => Left(Unauthorized)
}
}
}
I want to validate Sec-WebSocket-Protocol if it is present in the request header or add the same with value in the server response if it is not present.
I used the following code:
// Defined at http://tools.ietf.org/html/rfc6455#section-4.2.2
val MagicGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
def websocketAcceptForKey(key: String): String = {
val sha1 = MessageDigest.getInstance("sha1")
val salted = key + MagicGuid
val hash = sha1.digest(salted.asciiBytes)
val acceptKey: String = Base64.rfc2045().encodeToString(hash, false)
acceptKey
}
Use it like the following:
val wsKey: Optional[HttpHeader] = request.getHeader("Sec-WebSocket-Key")
val wsAccept = if (wsKey.isPresent) Some(RawHeader("Sec-WebSocket-Accept", websocketAcceptForKey(wsKey.get.value()))) else None

Akka. How to set a pem certificate in a https request

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.

SSLHandshakeException happens during file upload to AWS S3 via Alpakka

I'm trying to setup an Alpakka S3 for files upload purpose. Here is my configs:
alpakka s3 dependency:
...
"com.lightbend.akka" %% "akka-stream-alpakka-s3" % "0.20"
...
Here is application.conf:
akka.stream.alpakka.s3 {
buffer = "memory"
proxy {
host = ""
port = 8000
secure = true
}
aws {
credentials {
provider = default
}
}
path-style-access = false
list-bucket-api-version = 2
}
File upload code example:
private val awsCredentials = new BasicAWSCredentials("my_key", "my_secret_key")
private val awsCredentialsProvider = new AWSStaticCredentialsProvider(awsCredentials)
private val regionProvider = new AwsRegionProvider { def getRegion: String = "us-east-1" }
private val settings = new S3Settings(MemoryBufferType, None, awsCredentialsProvider, regionProvider, false, None, ListBucketVersion2)
private val s3Client = new S3Client(settings)(system, materializer)
val fileSource = Source.fromFuture(ByteString("ololo blabla bla"))
val fileName = UUID.randomUUID().toString
val s3Sink: Sink[ByteString, Future[MultipartUploadResult]] = s3Client.multipartUpload("my_basket", fileName)
fileSource.runWith(s3Sink)
.map {
result => println(s"${result.location}")
} recover {
case ex: Exception => println(s"$ex")
}
When I run this code I get:
javax.net.ssl.SSLHandshakeException: General SSLEngine problem
What can be a reason?
The certificate problem arises for bucket names containing dots.
You may switch to
akka.stream.alpakka.s3.path-style-access = true to get rid of this.
We're considering making it the default: https://github.com/akka/alpakka/issues/1152