XmlSigInHandlerTest.java
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cxf.rs.security.xml;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Properties;
import javax.xml.stream.XMLStreamReader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import jakarta.ws.rs.WebApplicationException;
import org.apache.cxf.helpers.DOMUtils;
import org.apache.cxf.message.ExchangeImpl;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageImpl;
import org.apache.cxf.rt.security.SecurityConstants;
import org.apache.cxf.staxutils.StaxUtils;
import org.apache.cxf.staxutils.W3CDOMStreamReader;
import org.apache.wss4j.common.crypto.Crypto;
import org.apache.wss4j.common.crypto.CryptoFactory;
import org.apache.wss4j.common.crypto.CryptoType;
import org.apache.xml.security.algorithms.MessageDigestAlgorithm;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xml.security.utils.Constants;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
public class XmlSigInHandlerTest {
private static Crypto crypto;
@BeforeClass
public static void setUpCrypto() throws Exception {
org.apache.xml.security.Init.init();
Properties props = new Properties();
props.put("org.apache.wss4j.crypto.provider", "org.apache.wss4j.common.crypto.Merlin");
props.put("org.apache.wss4j.crypto.merlin.keystore.type", "jks");
props.put("org.apache.wss4j.crypto.merlin.keystore.password", "password");
props.put("org.apache.wss4j.crypto.merlin.keystore.alias", "alice");
props.put("org.apache.wss4j.crypto.merlin.keystore.file", "alice.jks");
crypto = CryptoFactory.getInstance(props);
}
@Test
public void testEnvelopedSignature() throws Exception {
Document doc = createSignedDocument();
assertBody(doc, "CXF");
}
@Test
public void testWrappedEnvelopedSignatureIsRejected() throws Exception {
Document signedDoc = createSignedDocument();
Element signedRoot = signedDoc.getDocumentElement();
Element signature =
DOMUtils.getFirstChildWithName(signedRoot, Constants.SignatureSpecNS, "Signature");
// Wrap the signed element in a new unsigned root and move the Signature
// to be a direct child of that root
Document doc = DOMUtils.createDocument();
Element root = createBook(doc, "evil");
doc.appendChild(root);
signedRoot.removeChild(signature);
root.appendChild(doc.importNode(signedRoot, true));
root.appendChild(doc.importNode(signature, true));
assertRejected(doc);
}
@Test
public void testEnvelopingSignature() throws Exception {
Document doc = createEnvelopingSignedDocument();
assertBody(doc, "CXF");
}
@Test
public void testEnvelopingSignatureWithUnsignedObjectContentIsRejected() throws Exception {
Document doc = createEnvelopingSignedDocument();
Element object =
DOMUtils.getFirstChildWithName(doc.getDocumentElement(), Constants.SignatureSpecNS, "Object");
// Add an unsigned element before the signed element in the same Object
object.insertBefore(createBook(doc, "evil"), object.getFirstChild());
assertRejected(doc);
}
@Test
public void testEnvelopingSignatureWithUnsignedObjectIsRejected() throws Exception {
Document doc = createEnvelopingSignedDocument();
Element root = doc.getDocumentElement();
Element object = DOMUtils.getFirstChildWithName(root, Constants.SignatureSpecNS, "Object");
// Add an unsigned Object before the signed Object
Element evilObject = doc.createElementNS(Constants.SignatureSpecNS, "ds:Object");
evilObject.appendChild(createBook(doc, "evil"));
root.insertBefore(evilObject, object);
assertRejected(doc);
}
@Test
public void testEnvelopingSignatureWithObjectInKeyInfo() throws Exception {
Document doc = createEnvelopingSignedDocument();
Element keyInfo =
DOMUtils.getFirstChildWithName(doc.getDocumentElement(), Constants.SignatureSpecNS, "KeyInfo");
// An unsigned Object nested in the KeyInfo must not be passed on as the body
Element evilObject = doc.createElementNS(Constants.SignatureSpecNS, "ds:Object");
evilObject.appendChild(createBook(doc, "evil"));
keyInfo.appendChild(evilObject);
assertBody(doc, "CXF");
}
@Test
public void testDetachedSignature() throws Exception {
Document doc = createDetachedSignedDocument();
// Only the signed element is passed on, not the unsigned wrapper
assertBody(doc, "CXF");
}
@Test
public void testDetachedSignatureWithUnsignedContent() throws Exception {
Document doc = createDetachedSignedDocument();
// Add unsigned content to the unsigned root, before the signed element
Element root = doc.getDocumentElement();
root.insertBefore(createBook(doc, "evil"), root.getFirstChild());
assertBody(doc, "CXF");
}
private static void assertBody(Document doc, String expectedName) throws Exception {
Message message = createMessage(doc);
new AbstractXmlSigInHandler() { }.checkSignature(message);
XMLStreamReader reader = message.getContent(XMLStreamReader.class);
assertNotNull(reader);
Element root = StaxUtils.read(reader).getDocumentElement();
assertEquals("Book", root.getLocalName());
assertEquals(expectedName, DOMUtils.getFirstElement(root).getTextContent());
}
private static void assertRejected(Document doc) {
Message message = createMessage(doc);
try {
new AbstractXmlSigInHandler() { }.checkSignature(message);
fail("Failure expected on a wrapped signature");
} catch (WebApplicationException ex) {
assertEquals(400, ex.getResponse().getStatus());
}
}
private static Message createMessage(Document doc) {
Message message = new MessageImpl();
message.setExchange(new ExchangeImpl());
message.put(Message.HTTP_REQUEST_METHOD, "POST");
message.put(SecurityConstants.SIGNATURE_CRYPTO, crypto);
message.setContent(XMLStreamReader.class, new W3CDOMStreamReader(doc.getDocumentElement()));
return message;
}
private static Document createSignedDocument() throws Exception {
Document doc = DOMUtils.createDocument();
Element root = createBook(doc, "CXF");
doc.appendChild(root);
String id = "_book";
root.setAttributeNS(null, "Id", id);
root.setIdAttributeNS(null, "Id", true);
XMLSignature sig = new XMLSignature(doc, "", XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256);
root.appendChild(sig.getElement());
Transforms transforms = new Transforms(doc);
transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
transforms.addTransform(Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);
sig.addDocument("#" + id, transforms, MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA256);
sign(sig);
return doc;
}
private static Document createEnvelopingSignedDocument() throws Exception {
Document doc = DOMUtils.createDocument();
Element book = createBook(doc, "CXF");
String id = "_book";
book.setAttributeNS(null, "Id", id);
book.setIdAttributeNS(null, "Id", true);
XMLSignature sig = new XMLSignature(doc, "", XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256);
doc.appendChild(sig.getElement());
Element object = doc.createElementNS(Constants.SignatureSpecNS, "ds:Object");
object.appendChild(book);
sig.getElement().appendChild(object);
Transforms transforms = new Transforms(doc);
transforms.addTransform(Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);
sig.addDocument("#" + id, transforms, MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA256);
sign(sig);
return doc;
}
private static Document createDetachedSignedDocument() throws Exception {
Document doc = DOMUtils.createDocument();
Element root = doc.createElementNS("http://org.apache.cxf/rs/env", "env:Envelope");
doc.appendChild(root);
Element book = createBook(doc, "CXF");
root.appendChild(book);
String id = "_book";
book.setAttributeNS(null, "Id", id);
book.setIdAttributeNS(null, "Id", true);
XMLSignature sig = new XMLSignature(doc, "", XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256);
root.appendChild(sig.getElement());
Transforms transforms = new Transforms(doc);
transforms.addTransform(Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);
sig.addDocument("#" + id, transforms, MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA256);
sign(sig);
return doc;
}
private static Element createBook(Document doc, String bookName) {
Element book = doc.createElementNS(null, "Book");
Element name = doc.createElementNS(null, "name");
name.setTextContent(bookName);
book.appendChild(name);
return book;
}
private static void sign(XMLSignature sig) throws Exception {
CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
cryptoType.setAlias("alice");
X509Certificate cert = crypto.getX509Certificates(cryptoType)[0];
PrivateKey key = crypto.getPrivateKey("alice", "password");
sig.addKeyInfo(cert);
sig.sign(key);
}
}