/**
* WS-Attacker - A Modular Web Services Penetration Testing Framework Copyright
* (C) 2013 Juraj Somorovsky
*
* 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.signatureFaking;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.UnrecoverableEntryException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.xml.XMLConstants;
import javax.xml.crypto.AlgorithmMethod;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.KeySelectorException;
import javax.xml.crypto.KeySelectorResult;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import wsattacker.library.signatureFaking.exceptions.ConfigurationException;
import wsattacker.library.xmlutilities.dom.DomUtilities;
/**
* @author Juraj Somorovsky - juraj.somorovsky@rub.de
* @version 0.1
*/
public class XmlMessageSigner
{
private KeyStore keyStore;
private String keyAlias;
private char[] password;
private final static String DEFAULT_NAMESPACE_PREFIX = "ds";
private String signatureNamespacePrefix;
public XmlMessageSigner()
{
signatureNamespacePrefix = DEFAULT_NAMESPACE_PREFIX;
}
public String signMessage(String message, String signedElementId, boolean useEnvelopedTransform, String signatureParent, String signatureSibling)
throws ConfigurationException {
try {
// Create a DOM XMLSignatureFactory that will be used to
// generate the enveloped signature.
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
List<Transform> transforms = new LinkedList<>();
if (useEnvelopedTransform) {
transforms.add(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null));
}
transforms.add(fac.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE,
(C14NMethodParameterSpec) null));
Reference ref = fac.newReference(signedElementId, fac.newDigestMethod(DigestMethod.SHA1, null),
transforms, null, null);
// Create the SignedInfo.
SignedInfo si = fac.newSignedInfo(fac.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE,
(C14NMethodParameterSpec) null),
fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null),
Collections.singletonList(ref));
KeyStore.PrivateKeyEntry keyEntry
= (KeyStore.PrivateKeyEntry) keyStore.getEntry(keyAlias, new KeyStore.PasswordProtection(password));
X509Certificate cert = (X509Certificate) keyEntry.getCertificate();
// Create the KeyInfo containing the X509Data.
KeyInfoFactory kif = fac.getKeyInfoFactory();
List x509Content = new ArrayList(2);
x509Content.add(cert.getSubjectX500Principal().getName());
x509Content.add(cert);
X509Data xd = kif.newX509Data(x509Content);
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(xd));
// Instantiate the document to be signed.
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse(new ByteArrayInputStream(message.getBytes()));
// set explicitly all Id attributes
setAllIdAttributesInDocument(doc, "Id");
setAllIdAttributesInDocument(doc, "ID");
// Create a DOMSignContext and specify the RSA PrivateKey and
// location of the resulting XMLSignature's parent element.
DOMSignContext dsc = new DOMSignContext(keyEntry.getPrivateKey(), doc.getDocumentElement());
dsc.setDefaultNamespacePrefix(signatureNamespacePrefix);
List<? extends Node> nl1 = DomUtilities.evaluateXPath(doc, "//*[local-name()=\"" + signatureParent + "\"]");
List<? extends Node> nl2 = DomUtilities.evaluateXPath(doc, "//*[local-name()=\"" + signatureSibling + "\"]");
System.out.println(nl1.get(0));
dsc.setParent(nl1.get(0));
dsc.setNextSibling(nl2.get(0));
// Create the XMLSignature, but don't sign it yet.
XMLSignature signature = fac.newXMLSignature(si, ki);
// Marshal, generate, and sign the enveloped signature.
signature.sign(dsc);
String result = DomUtilities.domToString(doc);
return result;
} catch (IOException | InvalidAlgorithmParameterException | KeyStoreException |
MarshalException | NoSuchAlgorithmException | ParserConfigurationException |
SAXException | UnrecoverableEntryException | XMLSignatureException |
XPathExpressionException e) {
throw new ConfigurationException(e);
}
}
public boolean verifyMessage( String message )
throws SAXException, MarshalException, XMLSignatureException, XPathExpressionException
{
Document doc = DomUtilities.stringToDom( message );
setAllIdAttributesInDocument( doc, "Id" );
setAllIdAttributesInDocument( doc, "ID" );
// Find Signature element.
NodeList nl = doc.getElementsByTagNameNS( XMLSignature.XMLNS, "Signature" );
if ( nl.getLength() == 0 )
{
throw new RuntimeException( "Cannot find Signature element" );
}
boolean valid = true;
XMLSignatureFactory fac = XMLSignatureFactory.getInstance( "DOM" );
for ( int i = 0; i < nl.getLength(); i++ )
{
DOMValidateContext valContext = new DOMValidateContext( new X509KeySelector(), nl.item( i ) );
// Unmarshal the XMLSignature.
XMLSignature signature = fac.unmarshalXMLSignature( valContext );
// Validate the XMLSignature.
boolean coreValidity = signature.validate( valContext );
if ( coreValidity == false )
{
valid = false;
}
}
return valid;
}
public static void setAllIdAttributesInDocument( Document doc, String idName )
throws XPathExpressionException
{
List<? extends Node> result = DomUtilities.evaluateXPath( doc, "//*/@" + idName );
for ( int i = 0; i < result.size(); ++i )
{
Attr attribute = (Attr) result.get( i );
attribute.getOwnerElement().setIdAttributeNode( attribute, true );
}
}
public String getSignatureNamespacePrefix()
{
return signatureNamespacePrefix;
}
public void setSignatureNamespacePrefix( String signatureNamespacePrefix )
{
this.signatureNamespacePrefix = signatureNamespacePrefix;
}
public KeyStore getKeyStore()
{
return keyStore;
}
public void setKeyStore( KeyStore keyStore )
{
this.keyStore = keyStore;
}
public String getKeyAlias()
{
return keyAlias;
}
public void setKeyAlias( String keyAlias )
{
this.keyAlias = keyAlias;
}
public char[] getPassword()
{
return password;
}
public void setPassword( char[] password )
{
this.password = password;
}
public class X509KeySelector
extends KeySelector
{
public KeySelectorResult select( KeyInfo keyInfo, KeySelector.Purpose purpose, AlgorithmMethod method,
XMLCryptoContext context )
throws KeySelectorException
{
Iterator ki = keyInfo.getContent().iterator();
while ( ki.hasNext() )
{
XMLStructure info = (XMLStructure) ki.next();
if ( !( info instanceof X509Data ) )
{
continue;
}
X509Data x509Data = (X509Data) info;
Iterator xi = x509Data.getContent().iterator();
while ( xi.hasNext() )
{
Object o = xi.next();
if ( !( o instanceof X509Certificate ) )
{
continue;
}
final PublicKey key = ( (X509Certificate) o ).getPublicKey();
// Make sure the algorithm is compatible
// with the method.
if ( algEquals( method.getAlgorithm(), key.getAlgorithm() ) )
{
return new KeySelectorResult()
{
public Key getKey()
{
return key;
}
};
}
}
}
throw new KeySelectorException( "No key found!" );
}
boolean algEquals( String algURI, String algName )
{
if ( ( algName.equalsIgnoreCase( "DSA" ) && algURI.equalsIgnoreCase( SignatureMethod.DSA_SHA1 ) )
|| ( algName.equalsIgnoreCase( "RSA" ) && algURI.equalsIgnoreCase( SignatureMethod.RSA_SHA1 ) ) )
{
return true;
}
else
{
return false;
}
}
}
}