Sendgrid Inbound Parse webhook and Java MimeMessage Compatibility - sendgrid

I am trying to parse raw mime message which sengrid post to a URL by inbound parse web hook settings. Previously i was listening for incoming mails from Mailserver through Imap and from java MimeMessage i was able to convert it to the String and vice versa. Please see below code how i used to convert from MimeMessage to String and vice versa in java.
private void convertMimeMessageToStringAndViceVersa(javax.mail.internet.MimeMessage message) {
ByteArrayOutputStream bStream = new ByteArrayOutputStream();
message.writeTo(bStream);
String rawMimeMessageString = new String(bStream.toByteArray(), StandardCharsets.UTF_8.name());
// Now from the above String to MimeMessage see below code
Properties props = new Properties();
Session session = Session.getDefaultInstance(props, null);
ByteArrayInputStream bais = new ByteArrayInputStream(rawMimeMessageString.getBytes());
javax.mail.internet.MimeMessage convertedMimeMessage = new MimeMessage(session, bais);
}
So my question is, i cannot convert the string raw mail message which sendgrid is posting through inbound parse webhook to javax.mail.internet.MimeMessage type. Is there anyway.

Probably SendGrid Raw MimeMessage is broken, however you can try to use non-raw payload and convert this payload to whatever you want.
According to this article: https://varunsastrydevulapalli.medium.com/the-sendgrid-inbound-webhook-with-spring-dc7b5bae4e0c,
we can receive the Inbound Message using this spring controller:
#Controller
#RequestMapping(value = "/messaging")
public class InboundMessageController {
#Bean(name = "multipartResolver")
public CommonsMultipartResolver commonsMultipartResolver() {
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
commonsMultipartResolver.setDefaultEncoding("UTF-8");
commonsMultipartResolver.setMaxUploadSize(5000);
return commonsMultipartResolver;
}
#RequestMapping(value = "/inbound", method = {RequestMethod.POST, RequestMethod.HEAD}, consumes = MediaType.MULTIPART_FORM_DATA)
public #ResponseBody
void processInboundSendGridEmails(HttpServletRequest request,
HttpServletResponse response,
#RequestParam(required = false) MultipartFile file,
SendGridInbound sendGridInbound) {
System.out.println(sendGridInbound);
convertToMimeMessage(sendGridInbound);
}
}
public class SendGridInbound {
String headers;
String dkim;
String to;
String html;
String from;
String text;
String sender_ip;
String spam_report;
String envelope;
String attachments;
String subject;
String spam_score;
String attchmentInfo;
String charsets;
String spf;
//getter setters toString
}
Hope it could help.

The previous solution hasn't worked to me. But the following solution worked fine.
This code snippet reads the request through the class MimeMessage, to learn how to deal with that you can read this topic.
Here goes the solution:
package package_;
import org.apache.log4j.Logger;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
#RestController
#RequestMapping("/listener")
public class SendGridListener {
protected final Logger logger = Logger.getLogger(this.getClass());
#RequestMapping(
method = {RequestMethod.POST},
consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}
)
public #ResponseBody void listen(HttpServletRequest request) throws MessagingException, IOException {
String email = request.getParameter("email");
Session s = Session.getInstance(new Properties());
InputStream is = new ByteArrayInputStream(email.getBytes());
MimeMessage message = new MimeMessage(s, is);
logger.info(message);
}
}

Related

Micronaut HTTP Client Fails to Bind Response that is Missing Content-Type Header

I've successfully used the Micronaut HTTP Client with several different external services in the past. However, I'm really struggling with one external service. I think it might be related to the fact that the response from the external service does not contain a Content-Type header, but I'm not sure.
The client and response type are defined in the same groovy file.
package us.cloudcard.api.transact
import groovy.transform.ToString
import io.micronaut.http.HttpResponse
import io.micronaut.http.MediaType
import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Post
import io.micronaut.http.annotation.Produces
import io.micronaut.http.client.annotation.Client
import javax.validation.constraints.NotNull
#Client('${transact.url}')
interface TransactAuthenticationClient {
#Post
#Produces(MediaType.TEXT_PLAIN)
HttpResponse<TransactAuthenticationResponse> authenticate(#NotNull #Body String token)
}
#ToString
class TransactAuthenticationResponse {
Boolean Expired
String InstitutionId
String UserName
String customCssUrl
String email
String role
}
I'm testing it with a simple controller that just calls the client and renders the response status and body.
package us.cloudcard.api
import grails.compiler.GrailsCompileStatic
import grails.converters.JSON
import grails.plugin.springsecurity.annotation.Secured
import io.micronaut.http.HttpResponse
import org.springframework.beans.factory.annotation.Autowired
import us.cloudcard.api.transact.TransactAuthenticationClient
import us.cloudcard.api.transact.TransactAuthenticationResponse
#GrailsCompileStatic
#Secured("permitAll")
class MyController {
static responseFormats = ['json', 'xml']
#Autowired
TransactAuthenticationClient transactAuthenticationClient
def show(String id) {
String goodToken = "5753D...REDACTED...647F"
HttpResponse response = transactAuthenticationClient.authenticate(goodToken)
TransactAuthenticationResponse authenticationResponse = response.body()
log.error("status: ${response.status()} body: $authenticationResponse")
render "status: ${response.status()} body: $authenticationResponse"
}
}
However, the result I get is
status: OK body: null
Making the same request in Postman results in the correct response
When I debug, I can inspect the HttpResponse object and see all the correct headers, so I know I'm making the request successfully. I just can't bind the response.
I tried changing the client to bind to a String
#Post
#Produces(MediaType.TEXT_PLAIN)
HttpResponse<String> authenticate(#NotNull #Body String token)
and I got the following response
status: OK body: PooledSlicedByteBuf(ridx: 0, widx: 176, cap: 176/176, unwrapped: PooledUnsafeDirectByteBuf(ridx: 484, widx: 484, cap: 513))
This was interesting because the widx: 176, cap: 176/176 perfectly matched the content length of the successful response.
I am really at a loss, so I would appreciate any help you can give.
Thanks in advance for your help!
TL;DR: The Micronaut HTTP Client is not designed to work for this API
The Micronaut HTTP Client cannot consume APIs that do not include a content-type header in the response. I talked with Jeff Scott Brown about this, and that's just how Micronaut is designed. If there's no content-type header in the response, the client won't know how to parse the response body.
Work-Around
package us.cloudcard.api.transact
import groovy.json.JsonSlurper
import groovy.transform.ToString
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClientBuilder
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
#Component
class TransactAuthenticationClient {
#Value('${transact.url}')
String transactAuthenticationUrl
TransactAuthenticationResponse workaround2(String token) {
HttpPost post = new HttpPost(transactAuthenticationUrl)
post.addHeader("content-type", "text/plain")
post.setEntity(new StringEntity(token))
CloseableHttpClient client = HttpClientBuilder.create().build()
CloseableHttpResponse response = client.execute(post)
def bufferedReader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()))
def json = bufferedReader.getText()
println "response: \n" + json
def resultMap = new JsonSlurper().parseText(json)
return new TransactAuthenticationResponse(resultMap)
}
}
#ToString(includeNames = true)
class TransactAuthenticationResponse {
Boolean Expired
String InstitutionId
String UserName
String customCssUrl
String email
String role
}
JUST FYI: Before I found the above workaround, I tried this and it also does not work.
TransactAuthenticationResponse thisAlsoDoesNotWork (String token) {
String baseUrl = "https://example.com"
HttpClient client = HttpClient.create(baseUrl.toURL())
HttpRequest request = HttpRequest.POST("/path/to/endpoint", token)
HttpResponse<String> resp = client.toBlocking().exchange(request, String)
String json = resp.body()
println "json: $json"
ObjectMapper objectMapper = new ObjectMapper()
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
TransactAuthenticationResponse response = objectMapper.readValue(json, TransactAuthenticationResponse)
return response
}
Having the same problem, this is the best solution I found so far:
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.convert.TypeConverter;
import io.netty.buffer.ByteBuf;
// (...)
ConversionService.SHARED.addConverter(ByteBuf.class, String.class, new TypeConverter<ByteBuf, String>() {
#Override
public Optional<String> convert(ByteBuf object, Class<String> targetType, ConversionContext context) {
return Optional.ofNullable(object).map(bb -> bb.toString(StandardCharsets.UTF_8));
}
});
HttpRequest<String> req = HttpRequest.POST("<url>", "<body>");
// res is instance of io.micronaut.http.client.netty.FullNettyClientHttpResponse which uses the shared conversion service as "last chance" to convert the response body
HttpResponse<String> res = httpClient.toBlocking().exchange(req);
String responseBody = res.getBody(String.class).get();

How to send TestNG emailable report to email from maven?

I am using the below snippet for sending emailable-report.html of testng through mail.
public class SampleSendMail {
public void sendmailfun() {
String username = "mailid";
String password = "password";
Properties props = new Properties();
props.put("mail.smtp.auth", true);
props.put("mail.smtp.starttls.enable", true);
props.put("mail.smtp.host", "smtp.gmail.com");
props.put("mail.smtp.port", "587");
Session session = Session.getInstance(props, new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
try {
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress("sendingmailid"));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse("receivingmailid"));
message.setSubject("Testing Subject");
message.setText("PFA");
MimeBodyPart messageBodyPart = new MimeBodyPart();
Multipart multipart = new MimeMultipart();
messageBodyPart = new MimeBodyPart();
String file = "/Users/Documents/workspace/sampleproject/test-output/emailable-report.html";
String fileName = "emailable-report.html";
DataSource source = new FileDataSource(file);
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName(fileName);
multipart.addBodyPart(messageBodyPart);
message.setContent(multipart);
System.out.println("Sending");
Transport.send(message);
System.out.println("Done");
} catch (MessagingException e) {
e.printStackTrace();
}
}
}
In #AfterSuite ,i'm callig this function.
public void appstop() throws IOException {
sendingemail.sendmailfun();
}
I'm getting the the following error.
com.sun.mail.smtp.SMTPSendFailedException: 530 5.7.0 Must issue a STARTTLS command first
Can anyone help me to rectify this?
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import org.testng.annotations.Test;
public class SendAttachment{
#Test
public static void sendmail() throws AddressException, MessagingException, InterruptedException{
Thread.sleep(50000);
System.out.println("Test mail");
String[] to={"mail address","mail address"};
String to2="mail address";//change accordingly
final String user="mail address";//change accordingly
final String password="password";//change accordingly
//1) get the session object
Properties properties = System.getProperties();
properties.setProperty("mail.smtp.host", "smtp.gmail.com");
properties.put("mail.smtp.port", "587"); //TLS Port
properties.put("mail.smtp.auth", "true"); //enable authentication
properties.put("mail.smtp.starttls.enable", "true");
Session session = Session.getDefaultInstance(properties,
new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(user,password);
}
});
//2) compose message
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(user));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse("mailid1,mailid2"));
message.setSubject("ECM Regression Test suite Results");
//3) create MimeBodyPart object and set your message text
BodyPart messageBodyPart1 = new MimeBodyPart();
messageBodyPart1.setText("Please find the Regression Result in the attachment");
//4) create new MimeBodyPart object and set DataHandler object to this object
MimeBodyPart messageBodyPart2 = new MimeBodyPart(); MimeBodyPart messageBodyPart3 = new MimeBodyPart(); MimeBodyPart messageBodyPart4 = new MimeBodyPart(); MimeBodyPart messageBodyPart5 = new MimeBodyPart();
File f3=new File("D:\\svn\\CI_1.0\\seleniumScriptsRegression\\seleniumScriptsRegression\\test-output\\emailable-report.html");
DataSource source4 = new FileDataSource(f3);
messageBodyPart5.setDataHandler(new DataHandler(source4));
messageBodyPart5.setFileName(f3.getName());
//5) create Multipart object and add MimeBodyPart objects to this object
Multipart multipart = new MimeMultipart();
multipart.addBodyPart(messageBodyPart1);
multipart.addBodyPart(messageBodyPart5);
//6) set the multiplart object to the message object
message.setContent(multipart);
//7) send message
Transport.send(message);
System.out.println("message sent....");
}
//}
}
Try to use like this its working for me. But When we run this old email able report only getting attached in the mail.
**You can send the mail through **Apache commons Email**
=======================================================
**but before using Apache commons you need to add the dependency in Maven POM File.**
Dependency is as mentioned below
--------------------------------
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-email -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
<version>1.5</version>
</dependency>
You can refer to the below link for compatible dependency.
**Link:** https://mvnrepository.com/artifact/org.apache.commons/commons-email
**JAVA CODE**
------------
public void sendMail() throws MessagingException, EmailException
{
String username="abc.#gmail.com";
String password="1234567890";
String from_Email="xyz.#gmail.com";
String to_Email="pqr.#gmail.com";
Email email = new SimpleEmail();
email.setHostName("smtp.gmail.com");
email.setSmtpPort(465);
email.setAuthenticator(new DefaultAuthenticator(username, password));
email.setSSLOnConnect(true);
email.setFrom(from_Email);
email.setSubject("This is a test mail ... :-)");
email.setMsg("Body Message");
email.addTo(to_Email);
email.send();
}

Californium Framework CoAP and PUT request

I am trying to do a request to coap server (er-rest-example) using Californium.
I succesfully do a POST request.
But with PUT I am getting a BAD REQUEST, I try using this URLs in url:
coap://[aaaa::c30c:0000:0000:0002]:5683/actuators/leds
coap://[aaaa::c30c:0000:0000:0002]:5683/actuators/leds?
coap://[aaaa::c30c:0000:0000:0002]:5683/actuators/leds?color=r
But with no one get success.
What I am doing wrong?.
This is my simple script:
package coap_client;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
public class cliente {
public static void main(String[] args) throws Exception {
Timer timer;
timer = new Timer();
TimerTask task = new TimerTask(){
#Override
public void run(){
String url="coap://[aaaa::c30c:0000:0000:0002]:5683/actuators/leds";
URI uri= null;
try {
uri = new URI(url);
} catch (URISyntaxException e) {
e.printStackTrace();
}
CoapClient client = new CoapClient(uri);
CoapResponse response = client.put("color=r",MediaTypeRegistry.TEXT_PLAIN);
System.out.println(response.isSuccess());
if (response!=null) {
byte[] myreponse=response.getPayload();
String respuesta2 = new String(myreponse);
System.out.println(respuesta2);
}
}
};
timer.schedule(task, 10,10*1000);
}
}
In Contiki er-rest-example, see the POST/PUT handler(1) for the LED CoAP resource. It expects a mode param without which you will get a BAD_REQUEST as response. I assume that has to go in the request body.

Add Signature in a SOAP request in Apache JMeter

I need to add signature using X509 certificate in SOAP request in Apache JMeter.
I already have .p12 with me.
Please help how can I achieve it in Apache JMeter.
I know how to do it in SOAPUI but not finding any way in JMeter.
don't know wheter you need this answer still but I ran into the same problem and got it fixed after a couple of days of work and I suppose others might find this usefull as well...
I based my answer upon the answer by Dmitri T; the blog post on blazemeter, this bugreport + some custom hacking.
Let's assume you need to sign this request:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:ser="http://custom.namespace.com/service-v1.0-rc2">
<soap:Header>
<wsa:MessageID xmlns:wsa="http://www.w3.org/2004/12/addressing">a2749a0f-555-9135-367ed901d244</wsa:MessageID>
</soap:Header>
<soap:Body>
<ser:request>
<ser:person>
<ser:id>11552</ser:id>
<ser:number>81067776992</ser:number>
</ser:person>
</ser:request>
</soap:Body>
</soap:Envelope>
You've followed the guide on blazemeter until the part with the custom java code to sign the request.
In stead of using that code do the following:
Copy paste the following code into JMeter(3.0):
import com.example.wss.SOAPSecurity;
import org.apache.jmeter.services.FileServer;
// get SOAP message from parent sampler body
String soapData = sampler.getArguments().getArgument(0).getValue();
String baseDir = FileServer.getFileServer().getBaseDir();
String pathToKeystore = baseDir + File.separator + "keystore_files" + File.separator + "your.jks";
String keystorePassword = "yourPassword";
int timeToLive = 5000;
String signingAlias = "yourAlias";
String encryptAlias = "yourEncryptingAlias";
String secureSoap = "";
try {
secureSoap = SOAPSecurity.secureSoapMessageFromString(soapData, pathToKeystore, keystorePassword, null, null, timeToLive, signingAlias, yourEncryptingAlias);
}
catch (Exception ex){
log.warn("Error in script", ex);
throw ex;
}
// replace parent sampler body with secured SOAP message
sampler.getArguments().getArgument(0).setValue(secureSoap);
vars.put("SoapDataRaw", secureSoap);
Use groovy as interpreter.
This is the java class file adapted for my use case:
package com.example.wss;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.WSEncryptionPart;
import org.apache.ws.security.WSSecurityException;
import org.apache.ws.security.components.crypto.Crypto;
import org.apache.ws.security.components.crypto.CryptoFactory;
import org.apache.ws.security.components.crypto.Merlin;
import org.apache.ws.security.message.WSSecEncrypt;
import org.apache.ws.security.message.WSSecHeader;
import org.apache.ws.security.message.WSSecSignature;
import org.apache.ws.security.message.WSSecTimestamp;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.soap.*;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.net.ssl.*;
import javax.xml.transform.dom.DOMSource;
import java.io.*;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class SOAPSecurity {
private KeyStore keystore;
private KeyManagerFactory keyManagerFactory;
private String keystorePassword;
private TrustManagerFactory trustManagerFactory;
private KeyStore truststore;
private String truststorePassword;
private Crypto crypto;
public SOAPSecurity(String pathToKeystore, String keystorePassword, String pathToTruststore, String truststorePassword) throws IOException, NoSuchAlgorithmException, KeyStoreException, CertificateException, UnrecoverableKeyException, KeyManagementException, WSSecurityException {
keystore = KeyStore.getInstance("JKS");
InputStream fileReader = new FileInputStream(new File(pathToKeystore));
keystore.load(fileReader, keystorePassword.toCharArray());
this.keystorePassword = keystorePassword;
keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keystore, keystorePassword.toCharArray());
Properties properties = new Properties();
properties.setProperty("org.apache.ws.security.crypto.provider", "org.apache.ws.security.components.crypto.Merlin");
crypto = CryptoFactory.getInstance(properties);
((Merlin) crypto).setKeyStore(keystore);
truststore = KeyStore.getInstance("JKS");
fileReader = new FileInputStream(new File(pathToTruststore));
truststore.load(fileReader, truststorePassword.toCharArray());
this.truststorePassword = truststorePassword;
trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
trustManagerFactory.init(truststore);
}
//EDITOR: added constructor without truststore
public SOAPSecurity(String pathToKeystore, String keystorePassword) throws IOException, NoSuchAlgorithmException, KeyStoreException, CertificateException, UnrecoverableKeyException, KeyManagementException, WSSecurityException {
keystore = KeyStore.getInstance("JKS");
InputStream fileReader = new FileInputStream(new File(pathToKeystore));
keystore.load(fileReader, keystorePassword.toCharArray());
this.keystorePassword = keystorePassword;
keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keystore, keystorePassword.toCharArray());
Properties properties = new Properties();
properties.setProperty("org.apache.ws.security.crypto.provider", "org.apache.ws.security.components.crypto.Merlin");
crypto = CryptoFactory.getInstance(properties);
((Merlin) crypto).setKeyStore(keystore);
}
public static String secureSoapMessageFromFile(String messagePath, String pathToKeystore, String keystorePassword,
String pathToTruststore, String trustStorePassword, int timeToLive,
String signingAlias, String encryptAlias) throws
SAXException, ParserConfigurationException, SOAPException, IOException, WSSecurityException,
TransformerException, UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException,
KeyStoreException, KeyManagementException {
SOAPSecurity soapSecurity = new SOAPSecurity(pathToKeystore, keystorePassword, pathToTruststore, trustStorePassword);
SOAPMessage soapMessage = SOAPSecurity.createSOAPRequestFromFile(messagePath);
return soapSecurity.applyWSSecurity(soapMessage, timeToLive, signingAlias, encryptAlias);
}
public static String secureSoapMessageFromString(String messageString, String pathToKeystore, String keystorePassword,
String pathToTruststore, String trustStorePassword, int timeToLive,
String signingAlias, String encryptAlias) throws
SAXException, ParserConfigurationException, SOAPException, IOException, WSSecurityException,
TransformerException, UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException,
KeyStoreException, KeyManagementException {
SOAPSecurity soapSecurity = new SOAPSecurity(pathToKeystore, keystorePassword);
SOAPMessage soapMessage = SOAPSecurity.createSOAPRequestFromString(messageString);
return soapSecurity.applyWSSecurity(soapMessage, timeToLive, signingAlias, encryptAlias);
}
//EDITOR: ...and static signing methods as well
public static String secureSoapMessageFromString(String messageString, String pathToKeystore, String keystorePassword,
int timeToLive,
String signingAlias, String encryptAlias) throws
SAXException, ParserConfigurationException, SOAPException, IOException, WSSecurityException,
TransformerException, UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException,
KeyStoreException, KeyManagementException {
SOAPSecurity soapSecurity = new SOAPSecurity(pathToKeystore, keystorePassword);
SOAPMessage soapMessage = SOAPSecurity.createSOAPRequestFromString(messageString);
return soapSecurity.applyWSSecurity(soapMessage, timeToLive, signingAlias, encryptAlias);
}
public static String secureSoapMessageFromFile(String messagePath, String pathToKeystore, String keystorePassword,
int timeToLive,
String signingAlias, String encryptAlias) throws
SAXException, ParserConfigurationException, SOAPException, IOException, WSSecurityException,
TransformerException, UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException,
KeyStoreException, KeyManagementException {
SOAPSecurity soapSecurity = new SOAPSecurity(pathToKeystore, keystorePassword);
SOAPMessage soapMessage = SOAPSecurity.createSOAPRequestFromFile(messagePath);
return soapSecurity.applyWSSecurity(soapMessage, timeToLive, signingAlias, encryptAlias);
}
public interface SOAPDocWriter {
Document writeDocument(String s, DocumentBuilder documentBuilder) throws IOException, SAXException;
}
public static SOAPMessage createSOAPRequestFromFile(String messagePath) throws SOAPException, IOException, ParserConfigurationException, SAXException {
SOAPDocWriter pathWriter = (s, d) -> {
File messageFile = new File(s);
return d.parse(new InputSource(new FileInputStream(messageFile)));
};
return createSOAPRequestLambda(messagePath, pathWriter);
}
public static SOAPMessage createSOAPRequestFromString(String messageString) throws SOAPException, IOException, ParserConfigurationException, SAXException {
SOAPDocWriter stringWriter = (s, d) -> d.parse(new InputSource(new StringReader(s)));
return createSOAPRequestLambda(messageString, stringWriter);
}
private static SOAPMessage createSOAPRequestLambda(String s, SOAPDocWriter w) throws SOAPException, IOException, ParserConfigurationException, SAXException
{
//MessageFactory messageFactory = MessageFactory.newInstance();
//we use the SOAP 1.2 specification
MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL);
SOAPMessage soapMessage = messageFactory.createMessage();
SOAPPart soapPart = soapMessage.getSOAPPart();
SOAPEnvelope soapEnvelope = soapPart.getEnvelope();
SOAPBody soapBody = soapEnvelope.getBody();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document document = w.writeDocument(s, documentBuilder);
soapBody.addDocument(document);
soapMessage.saveChanges();
return soapMessage;
}
public static Document toDocument(SOAPMessage soapMsg) throws TransformerConfigurationException, TransformerException, SOAPException, IOException {
Source src = soapMsg.getSOAPPart().getContent();
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMResult result = new DOMResult();
transformer.transform(src, result);
return (Document) result.getNode();
}
/**
* Secures a soap message according to the given security actions
*
* #param soapMessage the soap message to be secured
* #param timestampTimeToLive optional: the time to live for the timestamp
* #param signatureKeyAlias optional: the alias for the signature key in the keystore
* #param encryptionKeyAlias optional: the alias for the encryption key in the keystore
* #throws WSSecurityException
* #throws IOException
* #throws SOAPException
* #throws TransformerException
*/
public String applyWSSecurity(SOAPMessage soapMessage,
int timestampTimeToLive,
String signatureKeyAlias,
String encryptionKeyAlias) throws WSSecurityException, IOException, SOAPException, TransformerException {
Document soapMessageDocument = toDocument(soapMessage);
// add security header
WSSecHeader securityHeader = new WSSecHeader();
securityHeader.setMustUnderstand(false);
//we keep the security header element because we need it afterwards
Element secHead = securityHeader.insertSecurityHeader(soapMessageDocument);
/*
for a reason not yet clear to me this method of signing creates a soap request inside a soap request
I don't know why but I know how to work around it:
*/
//append the security header to the soap:Header parent
soapMessageDocument.getElementsByTagName("soap:Header").item(0).appendChild(secHead);
//move the soap:Envelope inner soap message to the root of the document and omit the env:Envelope tree
soapMessageDocument.replaceChild(soapMessageDocument.getElementsByTagName("soap:Envelope").item(0),
soapMessageDocument.getElementsByTagName("env:Envelope").item(0));
//for debugging purposes -> this output shows up in the console output of JMeter.bat
//System.out.println("insert:"+soapMessageDocument.getFirstChild().getNodeName()+",soap:"+soapMessageDocument.getElementsByTagName("soap:Envelope").item(0).getNodeName());
WSSecTimestamp timestamp = null;
// timestamp document
timestamp = new WSSecTimestamp();
timestamp.setTimeToLive(timestampTimeToLive);
timestamp.build(soapMessageDocument, securityHeader);
// sign document
/*
EDITOR: originals are commented out and replaced by own values
values should be adapted from SOAPUI and searched for at:
https://ws.apache.org/wss4j/apidocs/org/apache/wss4j/dom/WSConstants.html
*/
WSSecSignature signatureBuilder = new WSSecSignature();
signatureBuilder.setUserInfo(signatureKeyAlias, keystorePassword);
signatureBuilder.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE);
//signatureBuilder.setSignatureAlgorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
signatureBuilder.setSignatureAlgorithm("http://www.w3.org/2000/09/xmldsig#rsa-sha1");
signatureBuilder.setSigCanonicalization(WSConstants.C14N_EXCL_OMIT_COMMENTS);
/*
also setDigestAlgo can be set
https://ws.apache.org/wss4j/apidocs/org/apache/wss4j/dom/message/WSSecSignature.html#setDigestAlgo-java.lang.String-
but I used the default so I didn't bother
*/
//also custom
signatureBuilder.setUseSingleCertificate(true);
List<WSEncryptionPart> signatureParts = new ArrayList<WSEncryptionPart>();
//WSEncryptionPart timestampPart = new WSEncryptionPart(timestamp.getId());
WSEncryptionPart timestampPart = new WSEncryptionPart("Timestamp","http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd","Content");
signatureParts.add(timestampPart);
//WSEncryptionPart bodyPart = new WSEncryptionPart(WSConstants.ELEM_BODY, WSConstants.URI_SOAP11_ENV, "Element");
WSEncryptionPart bodyPart = new WSEncryptionPart("Body","http://www.w3.org/2003/05/soap-envelope", "Content");
signatureParts.add(bodyPart);
signatureBuilder.setParts(signatureParts);
signatureBuilder.build(soapMessageDocument, crypto, securityHeader);
// encrypt document
/*
we didn't encrypt so no need for this code
(encryption code untested (by me))
WSSecEncrypt encrypt = new WSSecEncrypt();
encrypt.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE);
encrypt.setSymmetricEncAlgorithm(WSConstants.AES_128_GCM);
encrypt.setKeyEncAlgo(WSConstants.KEYTRANSPORT_RSAOEP);
encrypt.setUserInfo(encryptionKeyAlias, keystorePassword);
List<WSEncryptionPart> encryptionParts = new ArrayList<WSEncryptionPart>();
WSEncryptionPart encryptionSignaturePart = new WSEncryptionPart("Signature", WSConstants.SIG_NS, "Element");
WSEncryptionPart encryptionBodyPart = new WSEncryptionPart("Body", WSConstants.URI_SOAP11_ENV, "Content");
encryptionParts.add(encryptionBodyPart);
encryptionParts.add(encryptionSignaturePart);
encrypt.setParts(encryptionParts);
encrypt.build(soapMessageDocument, crypto, securityHeader);
*/
DOMSource domSource = new DOMSource(soapMessageDocument);
soapMessage.getSOAPPart().setContent(domSource);
soapMessage.saveChanges();
ByteArrayOutputStream out = new ByteArrayOutputStream();
soapMessage.writeTo(out);
String strMsg = new String(out.toByteArray());
return strMsg;
}
}
Put the code in a com\example\wss\SOAPSecurity.java file
Put wss4j-1.6.18.jar in the same dir.
http://archive.apache.org/dist/ws/wss4j/1.6.18/
I compiled/build/deployed it with this .sh script (you can use cygwin for that):
/cygdrive/c/Program\ Files\ \(x86\)/Java/jdk1.8.0_73/bin/javac.exe -cp wss4j-1.6.18.jar com/example/wss/SOAPSecurity.java
cp SOAPSecurity.jar SOAPSecurity.jar.bak$1
zip -r test.zip com/
mv test.zip SOAPSecurity.jar
cp SOAPSecurity.jar /cygdrive/c/Program\ Files\ \(x86\)/apache-jmeter-3.0/lib/
Run JMeter 3.0 with the .bat file
-> the SOAPSecurity.jar should be in the apache-jmeter-3.0/lib/ folder
-> I have also these configuration in JMeter's system.properties file:
-Djavax.net.ssl.keyStore=C:/path/to/client.jks
-Djavax.net.ssl.keyStorePassword=verySecret
-> and added a keystore configuration element to the soap request in JMeter
So, this is about it, feel free to give it a spin and let me know whether it worked for you, if not I might provide some help, if you'd like to debug yourself an excelent way to do so is by adapting/placing System.out.println's in the custom class, it gives very usefull information about what's going on and can be quite a life saver,
Keep having fun!!!
S.
You need to do some scripting in order to encrypt the message via the JSR223 PreProcessor. The idea is to get current sampler body, encrypt it and replace on-the-fly.
Add JSR223 PreProcessor as a child of the request
Use the following code as a reference:
import com.sun.org.apache.xml.internal.security.Init;
import com.sun.org.apache.xml.internal.security.c14n.Canonicalizer;
import com.sun.org.apache.xml.internal.security.signature.XMLSignature;
import com.sun.org.apache.xml.internal.security.transforms.Transforms;
import com.sun.org.apache.xml.internal.security.utils.Constants;
import com.sun.org.apache.xml.internal.security.utils.XMLUtils;
import org.apache.jmeter.protocol.http.sampler.SoapSampler;
import org.apache.commons.io.FileUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Date;
//write sampler body into "signature.xml" file
String body = sampler.getXmlData();
FileUtils.writeStringToFile(new File("signature.xml"),body);
//X509 properties
String keystoreType = "JKS";
String keystoreFile = "wso2carbon.jks";
String keystorePass = "wso2carbon";
String privateKeyAlias = "wso2carbon";
String privateKeyPass = "wso2carbon";
String certificateAlias = "wso2carbon";
Element element = null;
String BaseURI = signatureFile.toURI().toURL().toString();
//SOAP envelope to be signed
//get the private key used to sign, from the keystore
KeyStore ks = KeyStore.getInstance(keystoreType);
FileInputStream fis = new FileInputStream(keystoreFile);
ks.load(fis, keystorePass.toCharArray());
PrivateKey privateKey =
(PrivateKey) ks.getKey(privateKeyAlias, privateKeyPass.toCharArray());
//create basic structure of signature
javax.xml.parsers.DocumentBuilderFactory dbf =
javax.xml.parsers.DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
String request = sampler.getXmlData();
ByteArrayInputStream in = new ByteArrayInputStream(request.getBytes());
Document doc = dBuilder.parse(in);
in.close();
Init.init();
XMLSignature sig =
new XMLSignature(doc, BaseURI, XMLSignature.ALGO_ID_SIGNATURE_RSA);
element = doc.getDocumentElement();
element.normalize();
element.getElementsByTagName("soapenv:Header").item(0).appendChild(sig.getElement());
{
Transforms transforms = new Transforms(doc);
transforms.addTransform(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
//Sign the content of SOAP Envelope
sig.addDocument("", transforms, Constants.ALGO_ID_DIGEST_SHA1);
}
//Signing procedure
{
X509Certificate cert =
(X509Certificate) ks.getCertificate(certificateAlias);
sig.addKeyInfo(cert);
sig.addKeyInfo(cert.getPublicKey());
sig.sign(privateKey);
}
//write signature to file
FileOutputStream f = new FileOutputStream(signatureFile);
XMLUtils.outputDOMc14nWithComments(doc, f);
f.close();
//set sampler's XML data from file
String request = FileUtils.readFileToString(signatureFile);
sampler.setXmlData(request);
You will need to replace keystore and encryption related bits as per your configuration and service definition
See Take the Pain out of Load Testing Secure Web Services for comprehensive explanation

How to verify digital signature of SOAP call?

I wrote an interceptor in Apache CXF and get a SoapMessage. How do I get the raw XML from the SOAP message without changing the data to hurt the verification of the digital signature?
I refer to org.apache.cxf.binding.soap.SoapMessage:
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.binding.soap.interceptor.EndpointSelectionInterceptor;
import org.apache.cxf.binding.soap.interceptor.ReadHeadersInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class XmlSignatureVerifyInInterceptor extends AbstractSoapInterceptor {
private static final Logger log = LogManager.getLogger(XmlSignatureVerifyInInterceptor.class);
public XmlSignatureVerifyInInterceptor() {
super(Phase.READ);
log.entry();
addAfter(ReadHeadersInterceptor.class.getName());
addAfter(EndpointSelectionInterceptor.class.getName());
}
#Override
public void handleMessage(SoapMessage soapMessage) throws Fault {
log.entry(soapMessage);
}
}
Cheers and thank you in advance!
Fireball
If you are refering a javax.xml.soap.SOAPMessage, and you want a String result of XML, use ByteArrayOutputStream:
SOAPMessage message;
ByteArrayOutputStream out = new ByteArrayOutputStream();
String msg = "";
try {
message.writeTo(out);
msg = out.toString("UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
I use UTF-8 as encoding, you can change it to any others.