Adding a signature to a certificate - certificate

I have some odd requirements that I have to live with. I need to pass my crypto system a TBS certificate, they will sign it and send back a String of the signature which I need to incorporate into a certificate to make a signed certificate.
Looking at com.ibm.security.x509.X509CertImpl and various BouncyCastle posts on SO, I can't find out how to do that.
Questions:
Is this possible ?
If so, how ?

I'd refer to the source code for the BouncyCastle X509v3CertificateBuilder class (pkix jar), and tweak it to suit your needs. Note that this class uses a V3TBSCertificateGenerator to produce the TBSCertificate. That's an ASN.1 object that you can DER encode. Then you can get the DER encoding signed by the "crypto system". Then consult X509v3CertificateBuilder.build() method as to how to put the TBS certificate and signature together into the final X.509 certificate.

I've put together an example that shows one way to do it. Most of this code was stolen from the bouncycastle pkix or lwcrypto libraries, but any bugs are almost certainly mine. The most important method to concentrate on below is generateCert. The rest of the code is test harness to drive the test.
The code is specifically written to need only the bounycastle bcpkix and lwcrypto jars. It could be shortened somewhat if the bcprov jar was used instead of lwcrypto.
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.TBSCertificate;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.bc.BcX509v3CertificateBuilder;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
import javax.security.auth.x500.X500Principal;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Date;
public class Main {
private static class TBSCertPlusSignature {
private final byte[] encodedTbsCert;
private final byte[] signature;
public TBSCertPlusSignature(byte[] encodedTbsCert, byte[] signature) {
this.encodedTbsCert = encodedTbsCert;
this.signature = signature;
}
public byte[] getEncodedTbsCert() {
return encodedTbsCert;
}
public byte[] getSignature() {
return signature;
}
}
private static TBSCertPlusSignature makeTestCert(KeyPair keyPair) throws Exception {
Date now = new Date();
Date nowPlus1Hour = new Date(now.getTime() + 1000 * 60 * 60 * 1L);
byte[] encodedName = new X500Principal("CN=Duke, OU=JavaSoft, O=Sun Microsystems, C=US").getEncoded();
X500Name issuer = X500Name.getInstance(encodedName);
X500Name subject = issuer;
RSAPublicKey rsaPub = (RSAPublicKey) keyPair.getPublic();
RSAKeyParameters rsaPubParams = new RSAKeyParameters(false, rsaPub.getModulus(), rsaPub.getPublicExponent());
BcX509v3CertificateBuilder certBuilder = new BcX509v3CertificateBuilder(
issuer,
BigInteger.valueOf(100L),
now,
nowPlus1Hour,
subject,
rsaPubParams
);
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WithRSA");
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
RSAPrivateCrtKey rsaPriv = (RSAPrivateCrtKey) keyPair.getPrivate();
RSAPrivateCrtKeyParameters rsaPrivParams = new RSAPrivateCrtKeyParameters(
rsaPriv.getModulus(),
rsaPriv.getPublicExponent(),
rsaPriv.getPrivateExponent(),
rsaPriv.getPrimeP(),
rsaPriv.getPrimeQ(),
rsaPriv.getPrimeExponentP(),
rsaPriv.getPrimeExponentQ(),
rsaPriv.getCrtCoefficient()
);
ContentSigner contentSigner = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(rsaPrivParams);
final X509CertificateHolder x509CertificateHolder = certBuilder.build(contentSigner);
byte[] tbsCertDER = x509CertificateHolder.toASN1Structure().getTBSCertificate().getEncoded();
byte[] signature = x509CertificateHolder.getSignature();
return new TBSCertPlusSignature(tbsCertDER, signature);
}
private static X509Certificate generateCert(byte[] tbsCertEncoded, byte[] signature) throws Exception {
// Given the der encoded TBS cert and signature, create the corresponding X509 certificate
TBSCertificate tbsCert = TBSCertificate.getInstance(tbsCertEncoded);
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(tbsCert);
v.add(tbsCert.getSignature());
v.add(new DERBitString(signature));
DERSequence derSequence = new DERSequence(v);
ByteArrayInputStream baos = new ByteArrayInputStream(derSequence.getEncoded());
return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(baos);
}
public static void main(String[] args) throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair keyPair = kpg.generateKeyPair();
TBSCertPlusSignature testData = makeTestCert(keyPair);
X509Certificate x509Cert = generateCert(testData.getEncodedTbsCert(), testData.getSignature());
// Verify the signature
x509Cert.verify(keyPair.getPublic());
// Print the cert
PublicKey publicKey = x509Cert.getPublicKey();
System.out.println(x509Cert);
}
}

Related

Send an encrypted request to APi in Dart

I am trying to send an encrypted request to a specific API in dart, but without success - I don't have any experience with the Dart language.
This are the requirements:
The JSON to be sent is encrypted as follows: "AES/CBC/ZeroBytePadding", IV is generated according to SHA1PRNG with a length of 16 bytes.
The encrypted bytes are Base64 encoded. This results in the encryptedJson.
The hmac is generated from base64 encoded IV and the encryptedJson with "HmacSHA256".
A json will be generated: {"value":encryptedJson,"iv":initialisationVector,"mac":hmac}
This json will be base64 encoded and sent as an encrypted payload.
Can anyone help me? Thanks in advance!
This is the Dart Code so far.
import 'dart:convert';
import 'dart:core';
import 'package:crypto/crypto.dart' as crypto;
import 'package:encrypt/encrypt.dart' as enc;
String encrypt(String string) {
// json encryption
final enc.Key key = enc.Key.fromUtf8(env.get('password'));
final enc.IV iv = enc.IV.fromSecureRandom(IV_LENGTH);
final enc.Encrypter encrypter = enc.Encrypter(enc.AES(key, mode: enc.AESMode.cbc));
final encryptedJson = encrypter.encrypt(string, iv: iv);
final String IVBase64String = base64.encode(iv.bytes);
print('encrypted JSON: '+encryptedJson.base64);
print('decrypted JSON: '+encrypter.decrypt(encryptedJson, iv: iv));
crypto.Hmac hmacSha256 = new crypto.Hmac(crypto.sha256, key.bytes);
crypto.Digest sha256Result = hmacSha256.convert(iv.bytes + encryptedJson.bytes);
print('data: ' + encryptedJson.base64);
print('iv: ' + IVBase64String);
print('hmac: ' + sha256Result.toString());
// Payload
final encryptedText = "{\"value\":\""+encryptedJson.base64+"\",\"iv\":\""+IVBase64String+"\",\"mac\":\""+sha256Result.toString()+"\"}";
print('final: ' + jsonEncode(encryptedText));
return base64.encode(utf8.encode(encryptedText));
}
This is the JavaExample
import java.io.UnsupportedEncodingException;
import java.util.Base64;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class ApiJavaSample
{
private final Cipher cipher;
private final SecretKeySpec key;
private static final String TAG = "AESCrypt";
private static final int IV_LENGTH = 16;
private String cypher_mode = "AES/CBC/NoPadding";
private String cypher_mode_iv = "SHA1PRNG";
public static void main (String[] args)
{
try{
System.out.println("encrypting");
ApiJavaSample test = new ApiJavaSample("password");
String encryptedString = test.encrypt("{\"coupon_key\":\"011205358365\",\"location_id\":\"2\",\"device_key\":\"test_1234\"}");
System.out.println("encrpyted");
System.out.println(encryptedString);
}
catch(Exception e)
{
System.out.println(e);
}
}
public ApiJavaSample(String password) throws Exception
{
// hash password with SHA-256 and crop the output to 128-bit for key
//MessageDigest digest = MessageDigest.getInstance("SHA-256");
//digest.Updater(password.getBytes("UTF-8"));
byte[] keyBytes = password.getBytes();
cipher = Cipher.getInstance(cypher_mode);
key = new SecretKeySpec(keyBytes, "AES");
}
private String hmacDigest(String msg, String algo)
{
String digest = null;
try
{
//SecretKeySpec key = new SecretKeySpec((keyString).getBytes("UTF-8"), algo);
Mac mac = Mac.getInstance(algo);
mac.init(key);
byte[] bytes = mac.doFinal(msg.getBytes("UTF-8"));
StringBuilder hash = new StringBuilder();
for (int i = 0; i < bytes.length; i++)
{
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1)
{
hash.append('0');
}
hash.append(hex);
}
digest = hash.toString();
}
catch (UnsupportedEncodingException | InvalidKeyException e)
{
e.printStackTrace();
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
return digest;
}
public String encrypt(String plainText) throws Exception
{
byte[] iv_bytes = generateIv();
AlgorithmParameterSpec spec = new IvParameterSpec(iv_bytes);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
int blockSize = cipher.getBlockSize();
while (plainText.length() % blockSize != 0) {
plainText += "\0";
}
byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
String encryptedText = Base64.getEncoder().encodeToString(encrypted);
String iv_base64_string = Base64.getEncoder().encodeToString(iv_bytes);
String mac = hmacDigest(iv_base64_string + encryptedText.trim(), "HmacSHA256");
//JSONObject encryptedJson = new JSONObject();
//encryptedJson.put("value", encryptedText.trim());
//encryptedJson.put("iv", iv_base64_string);
//encryptedJson.put("mac", mac);
String base64Encrypt = "{\"value\":\""+encryptedText.trim()+"\",\"iv\":\""+iv_base64_string+"\",\"mac\":\""+mac+"\"}";
return Base64.getEncoder().encodeToString(base64Encrypt.getBytes());
}
private byte[] generateIv() throws NoSuchAlgorithmException
{
SecureRandom random = SecureRandom.getInstance(cypher_mode_iv);
byte[] iv = new byte[IV_LENGTH];
random.nextBytes(iv);
return iv;
}
}
Here is my test data:
Plaintext:
"{\"coupon_key\":\"382236526272\",\"location_id\":\"2\",\"device_key\":\"test_1234\"}"
Key:
33a485cb146e1153c69b588c671ab474
The following has to be changed/optimized in the Dart code:
The Java code uses Zero padding. PointyCastle and the encrypt package (a PointyCastle wrapper) do not support Zero padding (to my knowledge). A possible approach for the Dart code is to disable the default PKCS#7 padding in combination with a custom implementation for Zero padding.
The Java code applies the Base64 encoded data for the HMAC, while the Dart code uses the raw data. This has to be changed.
The Base64 encoding of the IV is obtained more efficiently with iv.base64.
Thus, the code is to be changed as follows:
import 'package:crypto/crypto.dart' as crypto;
import 'package:encrypt/encrypt.dart' as enc;
import 'package:convert/convert.dart';
import 'dart:typed_data';
import 'dart:convert';
String encrypt(String string) {
final enc.Key key = enc.Key.fromUtf8(env.get('password')); // Valid AES key
final enc.IV iv = enc.IV.fromSecureRandom(IV_LENGTH); // IV_LENGTH = 16
final dataPadded = pad(Uint8List.fromList(utf8.encode(string)), 16);
final enc.Encrypter encrypter = enc.Encrypter(enc.AES(key, mode: enc.AESMode.cbc, padding: null));
final encryptedJson = encrypter.encryptBytes(dataPadded, iv: iv);
crypto.Hmac hmacSha256 = crypto.Hmac(crypto.sha256, key.bytes);
crypto.Digest sha256Result = hmacSha256.convert(utf8.encode(iv.base64 + encryptedJson.base64));
final encryptedText = "{\"value\":\""+encryptedJson.base64+"\",\"iv\":\""+iv.base64+"\",\"mac\":\""+sha256Result.toString()+"\"}";
return base64.encode(utf8.encode(encryptedText));
}
Uint8List pad(Uint8List plaintext, int blockSize){
int padLength = (blockSize - (plaintext.lengthInBytes % blockSize)) % blockSize;
if (padLength != 0) {
BytesBuilder bb = BytesBuilder();
Uint8List padding = Uint8List(padLength);
bb.add(plaintext);
bb.add(padding);
return bb.toBytes();
}
else {
return plaintext;
}
}
Test (using a static IV to allow comparison between the ciphertexts of the two codes):
Key: enc.Key.fromUtf8("5432109876543210")
IV: enc.IV.fromUtf8("0123456789012345")
Plaintext: "{\"coupon_key\":\"011205358365\",\"location_id\":\"2\",\"device_key\":\"test_1234\"}"
Result: eyJ2YWx1ZSI6InNRTjJ0OWc5ZWY2RzdNV2RsOFB3emlXSlQwclNxUWJ2ZnN0eCtpMmNtSTQyUXJjUGRNV0JLbTlRZ2kxdmM0dElna2NOZEJsOVpEM0JlYTFPZ1kxaHNSeklSbHM1TnlaN0s1T2NqMTEzdkdvPSIsIml2IjoiTURFeU16UTFOamM0T1RBeE1qTTBOUT09IiwibWFjIjoiMzkwYzlhMzAxMjAxYjc1MWUxNjBhM2JlZTdmZGU5YzE5ZDY0MzJlNTBjOTJhNTg0ODBhMTJkNTYyNWRkYWMyNSJ9
After the changes, both codes return the above result for the above input data.
Security:
Typically, an AES key is a randomly generated byte sequence and not a string. If the key is to be derived from a passphrase/string, a reliable key derivation like PBKDF2 is to be used.
Zero padding is unreliable, so the reliable PKCS#7 padding that most libraries use by default should be applied. If the Java code had used PKCS#7 padding, porting would have been easier.
For encoding/decoding the charset should be specified (e.g. getBytes(StandardCharsets.UTF_8)), otherwise the default encoding will be used (which might not be wanted).
Using the same key for encryption and integrity checking for AES/HMAC is not a pressing security issue, but should be avoided as a preventive measure, see here.
The code is partially inefficient, e.g. when concatenating the Base64 encoded data instead of the raw data to determine the HMAC.

Sendgrid Inbound Parse webhook and Java MimeMessage Compatibility

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);
}
}

Rest API java, JSON Web token authentication : invalid signature

In Java, I try to set up a JWT authentication on a Rest API using jersey 1.9.
I'm using a friend's code sample (his token are valid with that code) and io.jsonwebtoken to generate token but I keep getting an Invalid signature warning when I test them on https://jwt.io/.
I've done some research and tried to fix that but I'm running out of ideas.
Example of invalid token I get with that code:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIyIiwiaWF0IjoxNDg1MDI2MDgzLCJzdWIiOiJodHRwOi8vbG9jYWxob3N0OjkxODAvVHJvY1RvblNhdm9pci8iLCJpc3MiOiJzZXJnZW50LWNoZWYiLCJleHAiOjE1MTY1NjIwODN9.HY8S7QbOhSB22d1_Dkmtg6qCiKxQRKz9W1etMqDookw
This is the login path and the method I use to create those token :
import java.security.Key;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.xml.bind.DatatypeConverter;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.TextCodec;
import io.jsonwebtoken.impl.crypto.MacProvider;
import dao.UserDao;
import model.User;
#Path("/home")
public class MainController {
UserController userCtrl = new UserController();
//The not really secret key
String key = "supersecretkey";
#POST
#Path("login")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response Login(
#FormParam("username") String username,
#FormParam("password") String password){
if (username == null || password == null) {
return Response
.status(Status.PRECONDITION_FAILED)
.build();
}
User user = userCtrl.Authenticate(username,password);
if (user == null) {
return Response
.status(Status.FORBIDDEN)
.build();
}
String token = createJWT(user.getUserID()+"",user.getUserName(),"http://localhost:8080/rest_api/",TimeUnit.DAYS.toMillis(365));
return Response
.status(Status.OK)
.entity(token)
.build();
}
private String createJWT(String id, String issuer, String subject, long ttlMillis) {
//The JWT signature algorithm we will be using to sign the token
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
//We will sign our JWT with our ApiKey secret
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(key);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
//Let's set the JWT Claims
JwtBuilder builder = Jwts.builder().setId(id)
.setIssuedAt(now)
.setSubject(subject)
.setIssuer(issuer)
.signWith(signatureAlgorithm, signingKey );
//if it has been specified, let's add the expiration
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}
//Builds the JWT and serializes it to a compact, URL-safe string
return builder.compact();
}
}
Solved
I don't really know what was wrong with my secret key but I manage to validate my token by changing my key generation:
private static final Key secret = MacProvider.generateKey(SignatureAlgorithm.HS256);
private static final byte[] secretBytes = secret.getEncoded();
public static final String base64SecretBytes = Base64.getEncoder().encodeToString(secretBytes);

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.