diff --git a/src/main/java/nz/co/breakpoint/jmeter/modifiers/AbstractWSSecurityPreProcessor.java b/src/main/java/nz/co/breakpoint/jmeter/modifiers/AbstractWSSecurityPreProcessor.java index 8043c9c..4333c6d 100644 --- a/src/main/java/nz/co/breakpoint/jmeter/modifiers/AbstractWSSecurityPreProcessor.java +++ b/src/main/java/nz/co/breakpoint/jmeter/modifiers/AbstractWSSecurityPreProcessor.java @@ -7,28 +7,21 @@ import org.apache.jorphan.logging.LoggingManager; import org.apache.log.Logger; import java.io.StringReader; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.Properties; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import org.apache.wss4j.common.crypto.Crypto; -import org.apache.wss4j.common.crypto.CryptoFactory; import org.apache.wss4j.common.ext.WSSecurityException; import org.apache.wss4j.common.util.XMLUtils; import org.apache.wss4j.dom.message.WSSecHeader; import org.apache.wss4j.dom.message.WSSecBase; -import org.apache.wss4j.dom.WSConstants; import org.w3c.dom.Document; import org.xml.sax.InputSource; -import static org.apache.wss4j.common.crypto.Merlin.PREFIX; -import static org.apache.wss4j.common.crypto.Merlin.KEYSTORE_FILE; -import static org.apache.wss4j.common.crypto.Merlin.KEYSTORE_PASSWORD; -import static org.apache.wss4j.common.crypto.Merlin.KEYSTORE_TYPE; - +/** + * Abstract base class for any preprocessor that creates/modifies a SOAP WSS header in the sampler payload. + * Subclasses need to provide an actual wss4j WSSecBase instance and implement some wrapper methods. + */ public abstract class AbstractWSSecurityPreProcessor extends AbstractTestElement implements PreProcessor, TestBean { private static final Logger log = LoggingManager.getLoggerForClass(); @@ -38,44 +31,14 @@ public abstract class AbstractWSSecurityPreProcessor extends AbstractTestElement transient private final DocumentBuilder docBuilder; // Handles the XML document - private final Properties cryptoProps; // Holds configured attributes for crypto instance - - private String certAlias, certPassword; // Certificate alias and password (if private cert) - - private List partsToSecure; // Holds the names of XML elements to secure (e.g. SOAP Body) - - static final Map keyIdentifierMap = new HashMap(); - static { - keyIdentifierMap.put("Binary Security Token", WSConstants.BST_DIRECT_REFERENCE); - keyIdentifierMap.put("Issuer Name and Serial Number", WSConstants.ISSUER_SERIAL); - keyIdentifierMap.put("X509 Certificate", WSConstants.X509_KEY_IDENTIFIER); - keyIdentifierMap.put("Subject Key Identifier", WSConstants.SKI_KEY_IDENTIFIER); - keyIdentifierMap.put("Thumbprint SHA1 Identifier", WSConstants.THUMBPRINT_IDENTIFIER); - keyIdentifierMap.put("Encrypted Key SHA1", WSConstants.ENCRYPTED_KEY_SHA1_IDENTIFIER); // only for encryption (symmetric signature not implemented yet - would require UI fields for setSecretKey or setEncrKeySha1value) - keyIdentifierMap.put("Custom Key Identifier", WSConstants.CUSTOM_KEY_IDENTIFIER); // not implemented yet (requires UI fields for setCustomTokenId and setCustomTokenValueType) - keyIdentifierMap.put("Key Value", WSConstants.KEY_VALUE); // only for signature - keyIdentifierMap.put("Endpoint Key Identifier", WSConstants.ENDPOINT_KEY_IDENTIFIER); // not supported by Merlin https://ws.apache.org/wss4j/apidocs/org/apache/wss4j/common/crypto/Merlin.html#getX509Certificates-org.apache.wss4j.common.crypto.CryptoType- - } + private String username, password; public AbstractWSSecurityPreProcessor() throws ParserConfigurationException { super(); docBuilder = factory.newDocumentBuilder(); - cryptoProps = new Properties(); - cryptoProps.setProperty("org.apache.wss4j.crypto.provider", "org.apache.wss4j.common.crypto.Merlin"); - cryptoProps.setProperty(PREFIX+KEYSTORE_TYPE, "jks"); initSecBuilder(); } - /* Reverse lookup for above keyIdentifierMap. Mainly used for populating the GUI dropdown. - */ - protected static String getKeyIdentifierLabelForType(int keyIdentifierType) { - for (Map.Entry id : keyIdentifierMap.entrySet()) { - if (id.getValue() == keyIdentifierType) - return id.getKey(); - } - return null; - } - protected Sampler getSampler() { return getThreadContext().getCurrentSampler(); } @@ -98,14 +61,12 @@ protected void setSamplerPayload(String payload) { /* Subclasses are to implement the actual creation of the signature or encryption, * as WSSecBase does not define a build method. */ - protected abstract Document build(Document document, Crypto crypto, WSSecHeader secHeader) + protected abstract Document build(Document document, WSSecHeader secHeader) throws WSSecurityException; /* The main method that is called before the sampler. * This will get, parse, secure (sign or encrypt) and then replace * the sampler's payload. - * A new crypto instance needs to be created for every iteration - * as the config could contain variables which may change. */ @Override public void process() { @@ -120,16 +81,13 @@ public void process() { WSSecHeader secHeader = new WSSecHeader(doc); secHeader.insertSecurityHeader(); - log.debug("Getting crypto instance"); - Crypto crypto = CryptoFactory.getInstance(cryptoProps); - log.debug("Building WSS header"); - doc = this.build(doc, crypto, secHeader); // Delegate in abstract method + doc = this.build(doc, secHeader); // Delegate in abstract method setSamplerPayload(XMLUtils.prettyDocumentToString(doc)); } - catch (Exception e) { - log.error(e.toString()); + catch (Exception e) { + log.error("Processing failed! ", e); } /* There is no cleanup method in the wss4j API, so we have to discard the old instance * and create a fresh one during the next iteration. @@ -141,56 +99,20 @@ public void process() { initSecBuilder(); } - // Accessors - public String getKeystoreFile() { - return cryptoProps.getProperty(PREFIX+KEYSTORE_FILE); + // Accessors (protected for subclasses to use but hidden from bean introspector, or they would show on subclass GUIs) + protected String getUsername() { + return username; } - public void setKeystoreFile(String keystoreFile) { - cryptoProps.setProperty(PREFIX+KEYSTORE_FILE, keystoreFile); + protected void setUsername(String username) { + getSecBuilder().setUserInfo(this.username = username, password); } - public String getKeystorePassword() { - return cryptoProps.getProperty(PREFIX+KEYSTORE_PASSWORD); + protected String getPassword() { + return password; } - public void setKeystorePassword(String keystorePassword) { - cryptoProps.setProperty(PREFIX+KEYSTORE_PASSWORD, keystorePassword); + protected void setPassword(String password) { + getSecBuilder().setUserInfo(username, this.password = password); } - - public String getCertAlias() { - return certAlias; - } - - public void setCertAlias(String certAlias) { - getSecBuilder().setUserInfo(this.certAlias = certAlias, certPassword); - } - - public String getCertPassword() { - return certPassword; - } - - public void setCertPassword(String certPassword) { - getSecBuilder().setUserInfo(certAlias, this.certPassword = certPassword); - } - - public String getKeyIdentifier() { - return getKeyIdentifierLabelForType(getSecBuilder().getKeyIdentifierType()); - } - - public void setKeyIdentifier(String keyIdentifier) { - getSecBuilder().setKeyIdentifierType(keyIdentifierMap.get(keyIdentifier)); - } - - public List getPartsToSecure() { - return partsToSecure; - } - - public void setPartsToSecure(List partsToSecure) { - this.partsToSecure = partsToSecure; - getSecBuilder().getParts().clear(); - for (SecurityPart part : partsToSecure) { - getSecBuilder().getParts().add(part.getPart()); - } - } } diff --git a/src/main/java/nz/co/breakpoint/jmeter/modifiers/AbstractWSSecurityPreProcessorBeanInfo.java b/src/main/java/nz/co/breakpoint/jmeter/modifiers/AbstractWSSecurityPreProcessorBeanInfo.java index 4f0e833..c8f16b5 100644 --- a/src/main/java/nz/co/breakpoint/jmeter/modifiers/AbstractWSSecurityPreProcessorBeanInfo.java +++ b/src/main/java/nz/co/breakpoint/jmeter/modifiers/AbstractWSSecurityPreProcessorBeanInfo.java @@ -1,46 +1,10 @@ package nz.co.breakpoint.jmeter.modifiers; -import java.beans.PropertyDescriptor; import org.apache.jmeter.testbeans.BeanInfoSupport; -import org.apache.jmeter.testbeans.gui.FileEditor; -import org.apache.jmeter.testbeans.gui.TableEditor; -import org.apache.jmeter.testbeans.gui.PasswordEditor; public class AbstractWSSecurityPreProcessorBeanInfo extends BeanInfoSupport { - protected final static String PARTSTOSECURE = "partsToSecure"; - public AbstractWSSecurityPreProcessorBeanInfo(Class clazz) { super(clazz); - - createPropertyGroup("Certificate", new String[]{ - "keystoreFile", "keystorePassword", "certAlias", "certPassword" - }); - PropertyDescriptor p; - - p = property("keystoreFile"); - p.setPropertyEditorClass(FileEditor.class); - p.setValue(NOT_UNDEFINED, Boolean.TRUE); - p.setValue(DEFAULT, ""); - - p = property("keystorePassword"); - p.setPropertyEditorClass(PasswordEditor.class); - p.setValue(NOT_UNDEFINED, Boolean.TRUE); - p.setValue(DEFAULT, ""); - - p = property("certAlias"); - p.setValue(NOT_UNDEFINED, Boolean.TRUE); - p.setValue(DEFAULT, ""); - - p = property("certPassword"); - p.setPropertyEditorClass(PasswordEditor.class); - p.setValue(NOT_UNDEFINED, Boolean.TRUE); - p.setValue(DEFAULT, ""); - - p = property(PARTSTOSECURE); // this is expected to be added to a property group by subclasses - p.setPropertyEditorClass(TableEditor.class); - p.setValue(TableEditor.CLASSNAME, SecurityPart.class.getName()); - p.setValue(TableEditor.HEADERS, new String[]{"Name", "Namespace", "Encode"}); - p.setValue(TableEditor.OBJECT_PROPERTIES, new String[]{"name", "namespace", "modifier"}); } } diff --git a/src/main/java/nz/co/breakpoint/jmeter/modifiers/CryptoWSSecurityPreProcessor.java b/src/main/java/nz/co/breakpoint/jmeter/modifiers/CryptoWSSecurityPreProcessor.java new file mode 100644 index 0000000..3544f8b --- /dev/null +++ b/src/main/java/nz/co/breakpoint/jmeter/modifiers/CryptoWSSecurityPreProcessor.java @@ -0,0 +1,119 @@ +package nz.co.breakpoint.jmeter.modifiers; + +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import javax.xml.parsers.ParserConfigurationException; +import org.apache.wss4j.common.crypto.Crypto; +import org.apache.wss4j.common.crypto.CryptoFactory; +import org.apache.wss4j.common.ext.WSSecurityException; +import org.apache.wss4j.dom.WSConstants; + +import static org.apache.wss4j.common.crypto.Merlin.PREFIX; +import static org.apache.wss4j.common.crypto.Merlin.KEYSTORE_FILE; +import static org.apache.wss4j.common.crypto.Merlin.KEYSTORE_PASSWORD; +import static org.apache.wss4j.common.crypto.Merlin.KEYSTORE_TYPE; + +/** + * Abstract parent class of any preprocessors that perform crypto operations (e.g. signature or encryption). + */ +public abstract class CryptoWSSecurityPreProcessor extends AbstractWSSecurityPreProcessor { + + private static final Logger log = LoggingManager.getLoggerForClass(); + + private final Properties cryptoProps; // Holds configured attributes for crypto instance + + private List partsToSecure; // Holds the names of XML elements to secure (e.g. SOAP Body) + + static final Map keyIdentifierMap = new HashMap(); + static { + keyIdentifierMap.put("Binary Security Token", WSConstants.BST_DIRECT_REFERENCE); + keyIdentifierMap.put("Issuer Name and Serial Number", WSConstants.ISSUER_SERIAL); + keyIdentifierMap.put("X509 Certificate", WSConstants.X509_KEY_IDENTIFIER); + keyIdentifierMap.put("Subject Key Identifier", WSConstants.SKI_KEY_IDENTIFIER); + keyIdentifierMap.put("Thumbprint SHA1 Identifier", WSConstants.THUMBPRINT_IDENTIFIER); + keyIdentifierMap.put("Encrypted Key SHA1", WSConstants.ENCRYPTED_KEY_SHA1_IDENTIFIER); // only for encryption (symmetric signature not implemented yet - would require UI fields for setSecretKey or setEncrKeySha1value) + keyIdentifierMap.put("Custom Key Identifier", WSConstants.CUSTOM_KEY_IDENTIFIER); // not implemented yet (requires UI fields for setCustomTokenId and setCustomTokenValueType) + keyIdentifierMap.put("Key Value", WSConstants.KEY_VALUE); // only for signature + keyIdentifierMap.put("Endpoint Key Identifier", WSConstants.ENDPOINT_KEY_IDENTIFIER); // not supported by Merlin https://ws.apache.org/wss4j/apidocs/org/apache/wss4j/common/crypto/Merlin.html#getX509Certificates-org.apache.wss4j.common.crypto.CryptoType- + } + + public CryptoWSSecurityPreProcessor() throws ParserConfigurationException { + super(); + cryptoProps = new Properties(); + cryptoProps.setProperty("org.apache.wss4j.crypto.provider", "org.apache.wss4j.common.crypto.Merlin"); + cryptoProps.setProperty(PREFIX+KEYSTORE_TYPE, "jks"); + } + + /* Reverse lookup for above keyIdentifierMap. Mainly used for populating the GUI dropdown. + */ + protected static String getKeyIdentifierLabelForType(int keyIdentifierType) { + for (Map.Entry id : keyIdentifierMap.entrySet()) { + if (id.getValue() == keyIdentifierType) + return id.getKey(); + } + return null; + } + + protected Crypto getCrypto() throws WSSecurityException { + // A new crypto instance needs to be created for every iteration as the config could contain variables which may change. + log.debug("Getting crypto instance"); + return CryptoFactory.getInstance(cryptoProps); + } + + // Accessors + public String getCertAlias() { + return getUsername(); + } + + public void setCertAlias(String certAlias) { + setUsername(certAlias); + } + + public String getCertPassword() { + return getPassword(); + } + + public void setCertPassword(String certPassword) { + setPassword(certPassword); + } + + public String getKeystoreFile() { + return cryptoProps.getProperty(PREFIX+KEYSTORE_FILE); + } + + public void setKeystoreFile(String keystoreFile) { + cryptoProps.setProperty(PREFIX+KEYSTORE_FILE, keystoreFile); + } + + public String getKeystorePassword() { + return cryptoProps.getProperty(PREFIX+KEYSTORE_PASSWORD); + } + + public void setKeystorePassword(String keystorePassword) { + cryptoProps.setProperty(PREFIX+KEYSTORE_PASSWORD, keystorePassword); + } + + public String getKeyIdentifier() { + return getKeyIdentifierLabelForType(getSecBuilder().getKeyIdentifierType()); + } + + public void setKeyIdentifier(String keyIdentifier) { + getSecBuilder().setKeyIdentifierType(keyIdentifierMap.get(keyIdentifier)); + } + + public List getPartsToSecure() { + return partsToSecure; + } + + public void setPartsToSecure(List partsToSecure) { + this.partsToSecure = partsToSecure; + getSecBuilder().getParts().clear(); + for (SecurityPart part : partsToSecure) { + getSecBuilder().getParts().add(part.getPart()); + } + } +} diff --git a/src/main/java/nz/co/breakpoint/jmeter/modifiers/CryptoWSSecurityPreProcessorBeanInfo.java b/src/main/java/nz/co/breakpoint/jmeter/modifiers/CryptoWSSecurityPreProcessorBeanInfo.java new file mode 100644 index 0000000..9f4c67b --- /dev/null +++ b/src/main/java/nz/co/breakpoint/jmeter/modifiers/CryptoWSSecurityPreProcessorBeanInfo.java @@ -0,0 +1,45 @@ +package nz.co.breakpoint.jmeter.modifiers; + +import java.beans.PropertyDescriptor; +import org.apache.jmeter.testbeans.gui.FileEditor; +import org.apache.jmeter.testbeans.gui.TableEditor; +import org.apache.jmeter.testbeans.gui.PasswordEditor; + +public class CryptoWSSecurityPreProcessorBeanInfo extends AbstractWSSecurityPreProcessorBeanInfo { + + protected final static String PARTSTOSECURE = "partsToSecure"; + + public CryptoWSSecurityPreProcessorBeanInfo(Class clazz) { + super(clazz); + + createPropertyGroup("Certificate", new String[]{ + "keystoreFile", "keystorePassword", "certAlias", "certPassword" + }); + PropertyDescriptor p; + + p = property("keystoreFile"); + p.setPropertyEditorClass(FileEditor.class); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + p = property("keystorePassword"); + p.setPropertyEditorClass(PasswordEditor.class); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + p = property("certAlias"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + p = property("certPassword"); + p.setPropertyEditorClass(PasswordEditor.class); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + p = property(PARTSTOSECURE); // this is expected to be added to a property group by subclasses + p.setPropertyEditorClass(TableEditor.class); + p.setValue(TableEditor.CLASSNAME, SecurityPart.class.getName()); + p.setValue(TableEditor.HEADERS, new String[]{"Name", "Namespace", "Encode"}); + p.setValue(TableEditor.OBJECT_PROPERTIES, new String[]{"name", "namespace", "modifier"}); + } +} diff --git a/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSEncryptionPreProcessor.java b/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSEncryptionPreProcessor.java index e18bfdd..8ebb475 100644 --- a/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSEncryptionPreProcessor.java +++ b/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSEncryptionPreProcessor.java @@ -9,7 +9,7 @@ import org.apache.wss4j.dom.message.WSSecHeader; import org.w3c.dom.Document; -public class WSSEncryptionPreProcessor extends AbstractWSSecurityPreProcessor { +public class WSSEncryptionPreProcessor extends CryptoWSSecurityPreProcessor { private static final long serialVersionUID = 1L; @@ -55,8 +55,8 @@ protected WSSecBase getSecBuilder() { } @Override - protected Document build(Document document, Crypto crypto, WSSecHeader secHeader) throws WSSecurityException { - return secBuilder.build(document, crypto, secHeader); + protected Document build(Document document, WSSecHeader secHeader) throws WSSecurityException { + return secBuilder.build(document, getCrypto(), secHeader); } // Accessors @@ -87,10 +87,12 @@ public void setCreateEncryptedKey(boolean createEncryptedKey) { /* This getter/setter pair seems to be required for the bean introspector when building the GUI, * otherwise the parent class property will be overwritten when building child class GUIs. */ + @Override public String getKeyIdentifier() { return super.getKeyIdentifier(); } + @Override public void setKeyIdentifier(String keyIdentifier) { super.setKeyIdentifier(keyIdentifier); } diff --git a/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSEncryptionPreProcessorBeanInfo.java b/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSEncryptionPreProcessorBeanInfo.java index 6a3281d..78fffbb 100644 --- a/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSEncryptionPreProcessorBeanInfo.java +++ b/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSEncryptionPreProcessorBeanInfo.java @@ -3,7 +3,7 @@ import java.beans.PropertyDescriptor; import org.apache.wss4j.dom.WSConstants; -public class WSSEncryptionPreProcessorBeanInfo extends AbstractWSSecurityPreProcessorBeanInfo { +public class WSSEncryptionPreProcessorBeanInfo extends CryptoWSSecurityPreProcessorBeanInfo { public WSSEncryptionPreProcessorBeanInfo() { super(WSSEncryptionPreProcessor.class); diff --git a/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSSignaturePreProcessor.java b/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSSignaturePreProcessor.java index 53fb8da..d502e9c 100644 --- a/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSSignaturePreProcessor.java +++ b/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSSignaturePreProcessor.java @@ -11,7 +11,7 @@ import org.apache.xml.security.signature.XMLSignature; import org.w3c.dom.Document; -public class WSSSignaturePreProcessor extends AbstractWSSecurityPreProcessor { +public class WSSSignaturePreProcessor extends CryptoWSSecurityPreProcessor { private static final long serialVersionUID = 1L; @@ -64,8 +64,8 @@ protected WSSecBase getSecBuilder() { } @Override - protected Document build(Document document, Crypto crypto, WSSecHeader secHeader) throws WSSecurityException { - return secBuilder.build(document, crypto, secHeader); + protected Document build(Document document, WSSecHeader secHeader) throws WSSecurityException { + return secBuilder.build(document, getCrypto(), secHeader); } // Accessors @@ -104,10 +104,12 @@ public void setUseSingleCertificate(boolean useSingleCertificate) { /* This getter/setter pair seems to be required for the bean introspector when building the GUI, * otherwise the parent class property will be overwritten when building child class GUIs. */ + @Override public String getKeyIdentifier() { return super.getKeyIdentifier(); } + @Override public void setKeyIdentifier(String keyIdentifier) { super.setKeyIdentifier(keyIdentifier); } diff --git a/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSSignaturePreProcessorBeanInfo.java b/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSSignaturePreProcessorBeanInfo.java index 3d77a06..0641f88 100644 --- a/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSSignaturePreProcessorBeanInfo.java +++ b/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSSignaturePreProcessorBeanInfo.java @@ -3,7 +3,7 @@ import java.beans.PropertyDescriptor; import org.apache.wss4j.dom.WSConstants; -public class WSSSignaturePreProcessorBeanInfo extends AbstractWSSecurityPreProcessorBeanInfo { +public class WSSSignaturePreProcessorBeanInfo extends CryptoWSSecurityPreProcessorBeanInfo { public WSSSignaturePreProcessorBeanInfo() { super(WSSSignaturePreProcessor.class); diff --git a/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSUsernameTokenPreProcessor.java b/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSUsernameTokenPreProcessor.java new file mode 100644 index 0000000..be0baf5 --- /dev/null +++ b/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSUsernameTokenPreProcessor.java @@ -0,0 +1,114 @@ +package nz.co.breakpoint.jmeter.modifiers; + +import java.util.HashMap; +import java.util.Map; +import javax.xml.parsers.ParserConfigurationException; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.apache.wss4j.common.crypto.Crypto; +import org.apache.wss4j.common.ext.WSSecurityException; +import org.apache.wss4j.dom.message.WSSecBase; +import org.apache.wss4j.dom.message.WSSecHeader; +import org.apache.wss4j.dom.message.WSSecUsernameToken; +import org.apache.wss4j.dom.WSConstants; +import org.w3c.dom.Document; + +public class WSSUsernameTokenPreProcessor extends AbstractWSSecurityPreProcessor { + + private static final long serialVersionUID = 1L; + + private static final Logger log = LoggingManager.getLoggerForClass(); + + transient private WSSecUsernameToken secBuilder; + + private String passwordType; + private boolean addNonce; + private boolean addCreated; + + static final Map passwordTypeMap = new HashMap(); + static { + passwordTypeMap.put("Password Digest", WSConstants.PASSWORD_DIGEST); + passwordTypeMap.put("Password Text", WSConstants.PASSWORD_TEXT); + } + + /* Currently supported attributes are listed below. + * The first value for each will be displayed in the GUI as default. + */ + static final String[] passwordTypes = new String[]{ + getPasswordTypeLabelForType(WSConstants.PASSWORD_DIGEST), + getPasswordTypeLabelForType(WSConstants.PASSWORD_TEXT) + }; + + public WSSUsernameTokenPreProcessor() throws ParserConfigurationException { + super(); + } + + /* Reverse lookup for above passwordTypeMap. Mainly used for populating the GUI dropdown. + */ + protected static String getPasswordTypeLabelForType(String passwordType) { + for (Map.Entry id : passwordTypeMap.entrySet()) { + if (passwordType.equals(id.getValue())) + return id.getKey(); + } + return null; + } + + @Override + protected void initSecBuilder() { + secBuilder = new WSSecUsernameToken(); + } + + @Override + protected WSSecBase getSecBuilder() { + return secBuilder; + } + + @Override + protected Document build(Document document, WSSecHeader secHeader) throws WSSecurityException { + if (addNonce) secBuilder.addNonce(); + if (addCreated) secBuilder.addCreated(); + return secBuilder.build(document, secHeader); + } + + // Accessors + public String getPasswordType() { + return getPasswordTypeLabelForType(passwordType); + } + + public void setPasswordType(String passwordType) { + secBuilder.setPasswordType(passwordTypeMap.get(this.passwordType = passwordType)); + } + + public boolean getAddNonce() { + return addNonce; + } + + public void setAddNonce(boolean addNonce) { + this.addNonce = addNonce; + } + + public boolean getAddCreated() { + return addCreated; + } + + public void setAddCreated(boolean addCreated) { + this.addCreated = addCreated; + } + + // Make accessors public for bean introspector to build the GUI. + public String getUsername() { + return super.getUsername(); + } + + public void setUsername(String username) { + super.setUsername(username); + } + + public String getPassword() { + return super.getPassword(); + } + + public void setPassword(String password) { + super.setPassword(password); + } +} diff --git a/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSUsernameTokenPreProcessorBeanInfo.java b/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSUsernameTokenPreProcessorBeanInfo.java new file mode 100644 index 0000000..f2972d1 --- /dev/null +++ b/src/main/java/nz/co/breakpoint/jmeter/modifiers/WSSUsernameTokenPreProcessorBeanInfo.java @@ -0,0 +1,39 @@ +package nz.co.breakpoint.jmeter.modifiers; + +import java.beans.PropertyDescriptor; +import org.apache.jmeter.testbeans.gui.PasswordEditor; + +public class WSSUsernameTokenPreProcessorBeanInfo extends AbstractWSSecurityPreProcessorBeanInfo { + + public WSSUsernameTokenPreProcessorBeanInfo() { + super(WSSUsernameTokenPreProcessor.class); + + createPropertyGroup("UsernameToken", new String[]{ + "username", "password", "passwordType", "addNonce", "addCreated" + }); + PropertyDescriptor p; + + p = property("username"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + p = property("password"); + p.setPropertyEditorClass(PasswordEditor.class); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, ""); + + p = property("passwordType"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, WSSUsernameTokenPreProcessor.passwordTypes[0]); + p.setValue(NOT_OTHER, Boolean.TRUE); + p.setValue(TAGS, WSSUsernameTokenPreProcessor.passwordTypes); + + p = property("addNonce"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.TRUE); + + p = property("addCreated"); + p.setValue(NOT_UNDEFINED, Boolean.TRUE); + p.setValue(DEFAULT, Boolean.TRUE); + } +} diff --git a/src/main/resources/nz/co/breakpoint/jmeter/modifiers/WSSUsernameTokenPreProcessorResources.properties b/src/main/resources/nz/co/breakpoint/jmeter/modifiers/WSSUsernameTokenPreProcessorResources.properties new file mode 100644 index 0000000..901e309 --- /dev/null +++ b/src/main/resources/nz/co/breakpoint/jmeter/modifiers/WSSUsernameTokenPreProcessorResources.properties @@ -0,0 +1,12 @@ +displayName=SOAP Message UsernameToken +UsernameToken.displayName=UsernameToken Settings +username.displayName=Username +username.shortDescription=The username (duh) +password.displayName=Password +password.shortDescription=The password (in clear text) +passwordType.displayName=Password Type +passwordType.shortDescription=How the password is submitted (clear text or digest) +addNonce.displayName=Add Nonce +addNonce.shortDescription=Whether to add a Nonce element to the UsernameToken +addCreated.displayName=Add Created +addCreated.shortDescription=Whether to add a Created element to the UsernameToken diff --git a/src/test/java/nz/co/breakpoint/jmeter/modifiers/TestAbstractWSSecurityPreProcessor.java b/src/test/java/nz/co/breakpoint/jmeter/modifiers/TestAbstractWSSecurityPreProcessor.java index 1123707..9598792 100644 --- a/src/test/java/nz/co/breakpoint/jmeter/modifiers/TestAbstractWSSecurityPreProcessor.java +++ b/src/test/java/nz/co/breakpoint/jmeter/modifiers/TestAbstractWSSecurityPreProcessor.java @@ -5,7 +5,6 @@ import org.apache.jmeter.protocol.jms.sampler.JMSSampler; import org.apache.jmeter.threads.JMeterContext; import org.apache.jmeter.threads.JMeterContextService; -import org.apache.wss4j.common.crypto.Crypto; import org.apache.wss4j.common.ext.WSSecurityException; import org.apache.wss4j.dom.message.WSSecHeader; import org.apache.wss4j.dom.message.WSSecBase; @@ -35,7 +34,7 @@ protected void initSecBuilder() { } @Override - protected Document build(Document document, Crypto crypto, WSSecHeader secHeader) throws WSSecurityException { + protected Document build(Document document, WSSecHeader secHeader) throws WSSecurityException { return document; } } diff --git a/src/test/java/nz/co/breakpoint/jmeter/modifiers/TestWSSSecurityPreProcessorBase.java b/src/test/java/nz/co/breakpoint/jmeter/modifiers/TestWSSSecurityPreProcessorBase.java index e02bd45..daa8f42 100644 --- a/src/test/java/nz/co/breakpoint/jmeter/modifiers/TestWSSSecurityPreProcessorBase.java +++ b/src/test/java/nz/co/breakpoint/jmeter/modifiers/TestWSSSecurityPreProcessorBase.java @@ -33,11 +33,11 @@ static HTTPSamplerBase createHTTPSampler() { return sampler; } - static void initCertSettings(AbstractWSSecurityPreProcessor mod) { + static void initCertSettings(CryptoWSSecurityPreProcessor mod) { initCertSettings(mod, WSSSignaturePreProcessor.signatureAlgorithms[0]); } - static void initCertSettings(AbstractWSSecurityPreProcessor mod, String signatureAlgorithm) { + static void initCertSettings(CryptoWSSecurityPreProcessor mod, String signatureAlgorithm) { mod.setKeystoreFile("src/test/resources/keystore.jks"); mod.setKeystorePassword("changeit"); mod.setCertAlias( diff --git a/src/test/java/nz/co/breakpoint/jmeter/modifiers/TestWSSUsernameTokenPreProcessor.java b/src/test/java/nz/co/breakpoint/jmeter/modifiers/TestWSSUsernameTokenPreProcessor.java new file mode 100644 index 0000000..fb59be0 --- /dev/null +++ b/src/test/java/nz/co/breakpoint/jmeter/modifiers/TestWSSUsernameTokenPreProcessor.java @@ -0,0 +1,56 @@ +package nz.co.breakpoint.jmeter.modifiers; + +import static org.junit.Assert.assertThat; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.not; + +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.wss4j.dom.WSConstants; +import org.junit.Before; +import org.junit.Test; + +public class TestWSSUsernameTokenPreProcessor extends TestWSSSecurityPreProcessorBase { + private WSSUsernameTokenPreProcessor mod = null; + private final static String USERNAME = "BREAKPOINT"; + private final static String PASSWORD = "JMETERROCKS"; + private JMeterContext context = null; + + @Before + public void setUp() throws Exception { + context = JMeterContextService.getContext(); + } + + @Test + public void testAllUsernameTokenCombinations() throws Exception { + for (String pt : WSSUsernameTokenPreProcessor.passwordTypes) { + boolean isDigest = WSConstants.PASSWORD_DIGEST.equals(WSSUsernameTokenPreProcessor.passwordTypeMap.get(pt)); + boolean isText = WSConstants.PASSWORD_TEXT.equals(WSSUsernameTokenPreProcessor.passwordTypeMap.get(pt)); + for (boolean an : new boolean[]{true, false}) { + for (boolean ac : new boolean[]{true, false}) { + mod = new WSSUsernameTokenPreProcessor(); + mod.setThreadContext(context); + mod.setUsername(USERNAME); + mod.setPassword(PASSWORD); + mod.setPasswordType(pt); + mod.setAddNonce(an); + mod.setAddCreated(ac); + HTTPSamplerBase sampler = createHTTPSampler(); + context.setCurrentSampler(sampler); + mod.process(); + String content = SamplerPayloadAccessor.getPayload(sampler); + assertThat(content, containsString(":UsernameToken")); + assertThat(content, containsString(">"+USERNAME+"<")); + assertThat(content, containsString(":Password")); + assertThat(content, isText ? containsString(PASSWORD) + : not(containsString(PASSWORD))); + assertThat(content, an || isDigest ? containsString(":Nonce") + : not(containsString(":Nonce"))); + assertThat(content, ac || isDigest ? containsString(":Created") + : not(containsString(":Created"))); + } + } + } + } +}