Skip to content
This repository has been archived by the owner on May 26, 2020. It is now read-only.

Commit

Permalink
SANTUARIO-532 User-defined and delayed evaluation of which XML elemen…
Browse files Browse the repository at this point in the history
…ts need to be secured
  • Loading branch information
peterdemaeyer committed Apr 9, 2020
1 parent cc40b75 commit 4f7f5c3
Show file tree
Hide file tree
Showing 52 changed files with 2,007 additions and 420 deletions.
13 changes: 13 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@
<targetJdk>1.8</targetJdk>
<clirr.version>2.8</clirr.version>
<maven-owasp-plugin-version>5.2.4</maven-owasp-plugin-version>
<mockito.version>3.3.0</mockito.version>

<!-- Allow Clirr severity to be overriden by the command-line option -DminSeverity=level -->
<minSeverity>info</minSeverity>
Expand Down Expand Up @@ -559,6 +560,12 @@
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
Expand Down Expand Up @@ -628,6 +635,12 @@
<version>0.7.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<distributionManagement>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@ stax.unsupportedKeyTransp = Der public-key Algorithmus ist zu kurz um den symmet
stax.recursiveKeyReference = Rekursive Schl\u00fcssel referenzierung detektiert.
stax.ecParametersNotSupported = ECParameters werden nicht unterst\u00fctzt.
stax.namedCurveMissing = NamedCurve fehlt.
stax.encryption.securePartNotFound = Part zum Verschl\u00fcsseln nicht gefunden: {0}
stax.signature.securePartNotFound = Part zum Signieren nicht gefunden: {0}
stax.encryption.securePartNotFound = {0}/{1} Part(e) zum Verschl\u00fcsseln nicht gefunden: {2}
stax.signature.securePartNotFound = {0}/{1} Part(e) zum Signieren nicht gefunden: {2}
stax.multipleSignaturesNotSupported = Mehrere Signaturen werden nicht unterstützt.
stax.signature.keyNameMissing = KeyName nicht konfiguriert.
stax.keyNotFoundForName = Kein Schl\u00fcssel für Schl\u00fcsselname konfiguriert: {0}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@ stax.unsupportedKeyTransp = public key algorithm too weak to encrypt symmetric k
stax.recursiveKeyReference = Recursive key reference detected.
stax.ecParametersNotSupported = ECParameters not supported.
stax.namedCurveMissing = NamedCurve is missing.
stax.encryption.securePartNotFound = Part to encrypt not found: {0}
stax.signature.securePartNotFound = Part to sign not found: {0}
stax.encryption.securePartNotFound = {0}/{1} part(s) to encrypt not found: {2}
stax.signature.securePartNotFound = {0}/{1} part(s) to sign not found: {2}
stax.multipleSignaturesNotSupported = Multiple signatures are not supported.
stax.signature.keyNameMissing = KeyName not configured.
stax.keyNotFoundForName = No key configured for KeyName: {0}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,10 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;

import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.stax.ext.stax.XMLSecAttribute;
Expand All @@ -37,6 +35,7 @@
import org.apache.xml.security.stax.ext.stax.XMLSecEventFactory;
import org.apache.xml.security.stax.ext.stax.XMLSecNamespace;
import org.apache.xml.security.stax.ext.stax.XMLSecStartElement;
import org.apache.xml.security.utils.KeyValue;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
Expand Down Expand Up @@ -124,15 +123,6 @@ public XMLSecurityConstants.Action getAction() {
return action;
}

public abstract void processEvent(XMLSecEvent xmlSecEvent, OutputProcessorChain outputProcessorChain)
throws XMLStreamException, XMLSecurityException;

@Override
public void processNextEvent(XMLSecEvent xmlSecEvent, OutputProcessorChain outputProcessorChain)
throws XMLStreamException, XMLSecurityException {
processEvent(xmlSecEvent, outputProcessorChain);
}

@Override
public void doFinal(OutputProcessorChain outputProcessorChain) throws XMLStreamException, XMLSecurityException {
outputProcessorChain.doFinal();
Expand Down Expand Up @@ -239,24 +229,24 @@ protected void outputAsEvent(OutputProcessorChain outputProcessorChain, XMLSecEv
outputProcessorChain.processEvent(xmlSecEvent);
}

protected SecurePart securePartMatches(XMLSecStartElement xmlSecStartElement,
OutputProcessorChain outputProcessorChain, String dynamicParts) {
Map<Object, SecurePart> dynamicSecureParts = outputProcessorChain.getSecurityContext().getAsMap(dynamicParts);
return securePartMatches(xmlSecStartElement, dynamicSecureParts);
protected KeyValue<SecurePartSelector, SecurePart> securePartMatches(XMLSecStartElement xmlSecStartElement,
OutputProcessorChain outputProcessorChain, String dynamicPartSelectors) {
OutboundSecurityContext securityContext = outputProcessorChain.getSecurityContext();
List<SecurePartSelector> dynamicSecurePartSelectors = securityContext.get(dynamicPartSelectors);
Element currentElement = securityContext.get(XMLSecurityConstants.CURRENT_ELEMENT);
return securePartMatches(xmlSecStartElement, dynamicSecurePartSelectors, currentElement);
}

protected SecurePart securePartMatches(XMLSecStartElement xmlSecStartElement, Map<Object, SecurePart> secureParts) {
SecurePart securePart = null;
if (secureParts != null) {
securePart = secureParts.get(xmlSecStartElement.getName());
if (securePart == null) {
Attribute attribute = xmlSecStartElement.getAttributeByName(securityProperties.getIdAttributeNS());
if (attribute != null) {
securePart = secureParts.get(attribute.getValue());
protected KeyValue<SecurePartSelector, SecurePart> securePartMatches(XMLSecStartElement xmlSecStartElement, List<SecurePartSelector> securePartSelectors, Element currentElement) {
if (securePartSelectors != null) {
for (SecurePartSelector securePartSelector : securePartSelectors) {
SecurePart securePart = securePartSelector.select(xmlSecStartElement.getName(), currentElement);
if (securePart != null) {
return new KeyValue<>(securePartSelector, securePart);
}
}
}
return securePart;
return null;
}

protected void outputDOMElement(Element element, OutputProcessorChain outputProcessorChain)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.apache.xml.security.stax.ext;

import org.w3c.dom.Element;

import javax.xml.namespace.QName;

/**
* This interface allows implementors to select secure parts based on an element's qualified name and skeleton DOM
* element, returning a {@link SecurePart} upon match or {@code null} otherwise.
*
* @author Peter De Maeyer
*/
public interface ElementSelector {

/**
* Gets the required number of occurrences, or {@code -1} for no such requirement.
* The number of occurrences is verified after processing the entire XML document.
* Processing will fail when the number of occurrences mismatches the required number.
* Use {@code 0} to verify that a secure part <i>never</i> occurs.
* Use {@code -1} to disable verification altogether.
* The recommended default for all implementations is {@code -1}
*
* @return a number of required secure parts, or {@code -1} for no particular number of required secure parts.
*/
int getRequiredNumOccurrences();

/**
* Selects the given element for encryption or signing when the return value is non-{@code null},
* or does not select it when the return value is {@code null}.
* The given element is a combination of qualified name and skeleton DOM element.
* The structure of the skeleton DOM element depends on the
* {@link org.apache.xml.security.stax.ext.XMLSecurityProperties.ElementModifier} set on
* {@link XMLSecurityProperties}, and is possibly {@code null}.
* The skeleton DOM element has no content, it only has:
* <ol>
* <li>local name, namespace URI and prefix;</li>
* <li>attributes;</li>
* <li>namespace declarations (which are just a special type of attributes).</li>
* </ol>
*
* @param name the qualified name, never {@code null}.
* @param element the skeleton DOM element, possibly {@code null}.
* @return {@code true} to select the given skeleton element for encryption or signing, {@code false} otherwise
*/
boolean select(QName name, Element element);
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public XMLStreamReader processInMessage(
requestSecurityEvents = Collections.emptyList();
}

final InboundSecurityContextImpl inboundSecurityContext = new InboundSecurityContextImpl();
final InboundSecurityContextImpl inboundSecurityContext = new InboundSecurityContextImpl(securityProperties);
inboundSecurityContext.putList(SecurityEvent.class, requestSecurityEvents);
inboundSecurityContext.addSecurityEventListener(securityEventListener);

Expand Down
94 changes: 37 additions & 57 deletions src/main/java/org/apache/xml/security/stax/ext/OutboundXMLSec.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,9 @@
*/
package org.apache.xml.security.stax.ext;

import java.io.OutputStream;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.List;

import javax.crypto.KeyGenerator;
import javax.xml.stream.XMLStreamWriter;

import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.stax.config.JCEAlgorithmMapper;
import org.apache.xml.security.stax.ext.XMLSecurityProperties.SecurePartElementSelector;
import org.apache.xml.security.stax.impl.DocumentContextImpl;
import org.apache.xml.security.stax.impl.OutboundSecurityContextImpl;
import org.apache.xml.security.stax.impl.OutputProcessorChainImpl;
Expand All @@ -45,6 +35,18 @@
import org.apache.xml.security.stax.securityToken.SecurityTokenConstants;
import org.apache.xml.security.stax.securityToken.SecurityTokenProvider;

import javax.crypto.KeyGenerator;
import javax.xml.stream.XMLStreamWriter;
import java.io.OutputStream;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.List;

import static java.util.stream.Collectors.toList;

/**
* Outbound Streaming-XML-Security
* An instance of this class can be retrieved over the XMLSec class
Expand Down Expand Up @@ -94,7 +96,7 @@ public XMLStreamWriter processOutMessage(XMLStreamWriter xmlStreamWriter, String

private XMLStreamWriter processOutMessage(
Object output, String encoding, SecurityEventListener eventListener) throws XMLSecurityException {
final OutboundSecurityContextImpl outboundSecurityContext = new OutboundSecurityContextImpl();
final OutboundSecurityContextImpl outboundSecurityContext = new OutboundSecurityContextImpl(securityProperties);

if (eventListener != null) {
outboundSecurityContext.addSecurityEventListener(eventListener);
Expand All @@ -105,58 +107,27 @@ private XMLStreamWriter processOutMessage(

OutputProcessorChainImpl outputProcessorChain = new OutputProcessorChainImpl(outboundSecurityContext, documentContext);

SecurePart signEntireRequestPart = null;
SecurePart encryptEntireRequestPart = null;

for (XMLSecurityConstants.Action action : securityProperties.getActions()) {
if (XMLSecurityConstants.SIGNATURE.equals(action)) {
XMLSignatureOutputProcessor signatureOutputProcessor = new XMLSignatureOutputProcessor();
initializeOutputProcessor(outputProcessorChain, signatureOutputProcessor, action);

configureSignatureKeys(outboundSecurityContext);
List<SecurePart> signatureParts = securityProperties.getSignatureSecureParts();
for (SecurePart securePart : signatureParts) {
if (securePart.getIdToSign() == null && securePart.getName() != null) {
outputProcessorChain.getSecurityContext().putAsMap(
XMLSecurityConstants.SIGNATURE_PARTS,
securePart.getName(),
securePart
);
} else if (securePart.getIdToSign() != null) {
outputProcessorChain.getSecurityContext().putAsMap(
XMLSecurityConstants.SIGNATURE_PARTS,
securePart.getIdToSign(),
securePart
);
} else if (securePart.isSecureEntireRequest()) {
// Special functionality to sign the first element in the request
signEntireRequestPart = securePart;
}
}
List<SecurePartSelector> signaturePartSelectors = securityProperties.getSignaturePartSelectors();
outputProcessorChain.getSecurityContext().put(
XMLSecurityConstants.SIGNATURE_PART_SELECTORS,
filter(signaturePartSelectors)
);
} else if (XMLSecurityConstants.ENCRYPT.equals(action)) {
XMLEncryptOutputProcessor encryptOutputProcessor = new XMLEncryptOutputProcessor();
initializeOutputProcessor(outputProcessorChain, encryptOutputProcessor, action);

configureEncryptionKeys(outboundSecurityContext);
List<SecurePart> encryptionParts = securityProperties.getEncryptionSecureParts();
for (SecurePart securePart : encryptionParts) {
if (securePart.getIdToSign() == null && securePart.getName() != null) {
outputProcessorChain.getSecurityContext().putAsMap(
XMLSecurityConstants.ENCRYPTION_PARTS,
securePart.getName(),
securePart
);
} else if (securePart.getIdToSign() != null) {
outputProcessorChain.getSecurityContext().putAsMap(
XMLSecurityConstants.ENCRYPTION_PARTS,
securePart.getIdToSign(),
securePart
);
} else if (securePart.isSecureEntireRequest()) {
// Special functionality to encrypt the first element in the request
encryptEntireRequestPart = securePart;
}
}
List<SecurePartSelector> encryptionPartSelectors = securityProperties.getEncryptionPartSelectors();
outputProcessorChain.getSecurityContext().put(
XMLSecurityConstants.ENCRYPTION_PART_SELECTORS,
filter(encryptionPartSelectors)
);
}
}
if (output instanceof OutputStream) {
Expand All @@ -171,11 +142,20 @@ private XMLStreamWriter processOutMessage(
throw new IllegalArgumentException(output + " is not supported as output");
}

XMLSecurityStreamWriter streamWriter = new XMLSecurityStreamWriter(outputProcessorChain);
streamWriter.setSignEntireRequestPart(signEntireRequestPart);
streamWriter.setEncryptEntireRequestPart(encryptEntireRequestPart);
return new XMLSecurityStreamWriter(outputProcessorChain);
}

return streamWriter;
private static List<SecurePartSelector> filter(List<SecurePartSelector> securePartSelectors) {
return securePartSelectors.stream().filter(securePartSelector -> {
ElementSelector elementSelector = securePartSelector.getElementSelector();
if (elementSelector instanceof SecurePartElementSelector) {
SecurePart securePart = ((SecurePartElementSelector) elementSelector).getSecurePart();
if (securePart.getName() == null && securePart.getIdToSign() == null && !securePart.isSecureEntireRequest()) {
return false;
}
}
return true;
}).collect(toList());
}

private void initializeOutputProcessor(OutputProcessorChainImpl outputProcessorChain, OutputProcessor outputProcessor, XMLSecurityConstants.Action action) throws XMLSecurityException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public interface OutputProcessor {
* @throws XMLStreamException thrown when a streaming error occurs
* @throws XMLSecurityException thrown when a Security failure occurs
*/
void processNextEvent(XMLSecEvent xmlSecEvent, OutputProcessorChain outputProcessorChain) throws XMLStreamException, XMLSecurityException;
void processEvent(XMLSecEvent xmlSecEvent, OutputProcessorChain outputProcessorChain) throws XMLStreamException, XMLSecurityException;

/**
* Will be called when the whole document is processed.
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/org/apache/xml/security/stax/ext/SecurePart.java
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,16 @@ public boolean isSecureEntireRequest() {
public void setSecureEntireRequest(boolean secureEntireRequest) {
this.secureEntireRequest = secureEntireRequest;
}

@Override
public String toString() {
if (idToSign != null) {
return idToSign;
} else if (name != null) {
return name.toString();
} else if (externalReference != null) {
return externalReference;
}
return super.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.apache.xml.security.stax.ext;

import org.w3c.dom.Element;

import javax.xml.namespace.QName;

/**
* @author Peter De Maeyer
*/
public interface SecurePartFactory {

SecurePart create(QName name, Element element);
}
Loading

0 comments on commit 4f7f5c3

Please sign in to comment.