/** * WS-Attacker - A Modular Web Services Penetration Testing Framework Copyright * (C) 2013 Christian Mainka * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package wsattacker.library.signatureWrapping.option; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.text.ParseException; import java.util.*; import org.apache.log4j.Logger; import org.apache.ws.security.WSConstants; import org.w3c.dom.Attr; import org.w3c.dom.Element; import org.w3c.dom.Node; import wsattacker.library.signatureWrapping.util.exception.InvalidPayloadException; import wsattacker.library.signatureWrapping.util.signature.ReferringElementInterface; import wsattacker.library.signatureWrapping.util.timestamp.TimestampUpdateHelper; import wsattacker.library.xmlutilities.dom.DomUtilities; /** * The Payload class hold gives a connection between the signed element and the payload element. */ public class Payload { public static final String PROP_TIMESTAMP = "timestamp"; public static final String PROP_WRAPONLY = "wrapOnly"; public static final String PROP_PAYLOADELEMENT = "payloadElement"; public static final String PROP_SIGNEDELEMENT = "signedElement"; public static final String PROP_REFERRINGELEMENT = "referringElement"; private static final Logger LOG = Logger.getLogger( Payload.class ); private static final long serialVersionUID = 2L; private static final String ASSERTION = "Assertion"; private static final String CONDITIONS = "Conditions"; private static final String NOTBEFORE = "NotBefore"; private static final String NOTONORAFTER = "NotOnOrAfter"; private boolean timestamp = false, wrapOnly = false; private Element payloadElement; private Element signedElement; private ReferringElementInterface referringElement; private final transient PropertyChangeSupport propertyChangeSupport = new java.beans.PropertyChangeSupport( this ); public Payload() { } /** * Constructor for the Payload. * * @param referringElement : Reference to the Reference element. * @param name : Name of the option. * @param signedElement : The signed element. This is usefull, if the Reference element selects more than one signed * element (e.g. when using XPath). * @param description . Description of the option. */ public Payload( ReferringElementInterface referringElement, Element signedElement ) { setReferringElement( referringElement ); setSignedElement( signedElement ); } /** * Does this option has any payload? * * @return */ public boolean hasPayload() { boolean hasPayload = true; if ( payloadElement == null ) { hasPayload = false; } else if ( payloadElement.isEqualNode( signedElement ) ) { hasPayload = false; } return hasPayload; // return (payloadElement != null); } /** * Returns the payload element. If it is a Timestamp element, automatically an updated one is returned. * * @return the payload elemeent. * @throws InvalidPayloadException */ public Element getPayloadElement() throws InvalidPayloadException { // This is now needed for the case: wrapOnly // if (payloadElement == null) { // payloadElement = (Element) signedElement.cloneNode(true); // } Element retr = payloadElement; // If it is a timestamp, we need to create a valid one! if ( timestamp ) { // Element timestamp = (Element) // originalDocument.getDocumentElement().cloneNode(true); Element timestamp = (Element) signedElement.cloneNode( true ); if ( timestamp.getLocalName().equals( WSConstants.TIMESTAMP_TOKEN_LN ) ) { // CASE: WSU:TIMESTAMP // 1) Find created and expires Element // //////////////////////////////////// Element createdElement = null, expiresElement = null; for ( Node cur = timestamp.getFirstChild(); cur != null; cur = cur.getNextSibling() ) { if ( cur.getNodeType() == Node.ELEMENT_NODE ) { // Case Created if ( WSConstants.CREATED_LN.equals( cur.getLocalName() ) && WSConstants.WSU_NS.equals( cur.getNamespaceURI() ) ) { createdElement = (Element) cur; } // Case Exires else if ( WSConstants.EXPIRES_LN.equals( cur.getLocalName() ) && WSConstants.WSU_NS.equals( cur.getNamespaceURI() ) ) { expiresElement = (Element) cur; } } } if ( createdElement == null ) { String warning = "Could not find Created Element in Timestamp"; LOG.warn( warning ); throw new InvalidPayloadException( warning ); } if ( expiresElement == null ) { String warning = "Could not find Expires Element in Timestamp"; LOG.warn( warning ); throw new InvalidPayloadException( warning ); } TimestampUpdateHelper helper; try { helper = new TimestampUpdateHelper( createdElement.getTextContent(), expiresElement.getTextContent() ); } catch ( ParseException ex ) { String warning = "Timestampformat could not be handled"; LOG.warn( warning ); throw new InvalidPayloadException( warning ); } createdElement.setTextContent( helper.getStart() ); expiresElement.setTextContent( helper.getEnd() ); retr = timestamp; } else if ( timestamp.getLocalName().equals( ASSERTION ) ) { // CASE 2: SAML ASSERTION List<Element> conditionElementList = DomUtilities.findChildren( timestamp, CONDITIONS, null ); if ( conditionElementList.isEmpty() ) { String warning = "Could not find the Element <" + CONDITIONS + "/>"; LOG.warn( warning ); throw new InvalidPayloadException( warning ); } if ( conditionElementList.size() > 1 ) { String warning = "There are " + conditionElementList.size() + " <" + CONDITIONS + "/> Elements"; LOG.warn( warning ); throw new InvalidPayloadException( warning ); } Element conditionElement = conditionElementList.get( 0 ); Attr notBefore = conditionElement.getAttributeNode( NOTBEFORE ); if ( notBefore == null ) { String warning = "Could not find '" + NOTBEFORE + "' Attribute"; LOG.warn( warning ); throw new InvalidPayloadException( warning ); } Attr notOnOrAfter = conditionElement.getAttributeNode( NOTONORAFTER ); if ( notOnOrAfter == null ) { String warning = "Could not find '" + NOTONORAFTER + "' Attribute"; LOG.warn( warning ); throw new InvalidPayloadException( warning ); } TimestampUpdateHelper helper; try { helper = new TimestampUpdateHelper( notBefore.getTextContent(), notOnOrAfter.getTextContent() ); } catch ( ParseException ex ) { String warning = "Timestampformat could not be handled"; LOG.warn( warning ); throw new InvalidPayloadException( warning ); } notBefore.setTextContent( helper.getStart() ); notOnOrAfter.setTextContent( helper.getEnd() ); retr = timestamp; } } return retr; } /** * Return the signed element. * * @return */ public Element getSignedElement() { return signedElement; } /** * Return the Reference element. * * @return */ public ReferringElementInterface getReferringElement() { return referringElement; } /** * Is the signed element a Timestamp element? * * @return */ public boolean isTimestamp() { return timestamp; } /** * Set if the signed element is a Timestamp element. * * @param timestamp */ public void setTimestamp( boolean timestamp ) { log().trace( "Payload.setTimestamp() setTimestamp = " + timestamp ); boolean oldTimestamp = this.timestamp; this.timestamp = timestamp; propertyChangeSupport.firePropertyChange( PROP_TIMESTAMP, oldTimestamp, timestamp ); } public void setPayloadElement( Element payloadElement ) { org.w3c.dom.Element oldPayloadElement = this.payloadElement; this.payloadElement = payloadElement; propertyChangeSupport.firePropertyChange( PROP_PAYLOADELEMENT, oldPayloadElement, payloadElement ); } public void setSignedElement( Element signedElement ) { org.w3c.dom.Element oldSignedElement = this.signedElement; this.signedElement = signedElement; propertyChangeSupport.firePropertyChange( PROP_SIGNEDELEMENT, oldSignedElement, signedElement ); if ( signedElement != null ) { setPayloadElement( (Element) signedElement.cloneNode( true ) ); setTimestamp( detectTimestamp() ); } } public void setReferringElement( ReferringElementInterface referringElement ) { wsattacker.library.signatureWrapping.util.signature.ReferringElementInterface oldReferringElement = this.referringElement; this.referringElement = referringElement; propertyChangeSupport.firePropertyChange( PROP_REFERRINGELEMENT, oldReferringElement, referringElement ); } private Logger log() { return Logger.getLogger( getClass() ); } public boolean isValid( String value ) { boolean isValid = true; if ( value.length() >= 3 ) { try { DomUtilities.stringToDom( value ); } catch ( Exception e ) { log().error( "Payload.isValid() Error: " + e.getLocalizedMessage() ); isValid = false; } } return isValid; } /** * The the value for the payload. */ public void setValue( String value ) throws IllegalArgumentException { if ( isValid( value ) ) { try { setPayloadElement( DomUtilities.stringToDom( value ).getDocumentElement() ); } catch ( Exception e ) { throw new IllegalArgumentException( e ); } // this.value = value; log().info( "Has payload? " + hasPayload() ); } } /** * The the value for the payload. */ public void removeValue() throws IllegalArgumentException { setPayloadElement( signedElement ); log().info( "Has payload? " + hasPayload() ); } private boolean detectTimestamp() { boolean isT = signedElement.getLocalName().equals( WSConstants.TIMESTAMP_TOKEN_LN ); // TODO: Think about detection of SAML Timestamps // if (!isT) { // String elementLocalName = this.signedElement.getLocalName(); // if (elementLocalName.equals(getASSERTION())) { // isT = !DomUtilities.findChildren(signedElement, CONDITIONS, // null).isEmpty(); // } // } return isT; } public String getValue() { // return value; return DomUtilities.domToString( payloadElement ); } public boolean isWrapOnly() { return wrapOnly; } public void setWrapOnly( boolean wrapOnly ) { boolean oldWrapOnly = this.wrapOnly; this.wrapOnly = wrapOnly; propertyChangeSupport.firePropertyChange( PROP_WRAPONLY, oldWrapOnly, wrapOnly ); } /** * Add PropertyChangeListener. * * @param listener */ public void addPropertyChangeListener( final PropertyChangeListener listener ) { propertyChangeSupport.addPropertyChangeListener( listener ); } /** * Add PropertyChangeListener. * * @param propertyName * @param listener */ public void addPropertyChangeListener( final String propertyName, final PropertyChangeListener listener ) { propertyChangeSupport.addPropertyChangeListener( propertyName, listener ); } /** * Remove PropertyChangeListener. * * @param listener */ public void removePropertyChangeListener( final PropertyChangeListener listener ) { propertyChangeSupport.removePropertyChangeListener( listener ); } /** * Remove PropertyChangeListener. * * @param propertyName * @param listener */ public void removePropertyChangeListener( final String propertyName, final PropertyChangeListener listener ) { propertyChangeSupport.removePropertyChangeListener( propertyName, listener ); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append( "Payload{" ); sb.append( "signedElement=" ).append( signedElement ); sb.append( ", payloadElement=" ).append( payloadElement ); sb.append( ", referringElement=" ).append( referringElement ); sb.append( ", timestamp=" ).append( timestamp ); sb.append( ", wrapOnly=" ).append( wrapOnly ); sb.append( '}' ); return sb.toString(); } }