/*
* Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
*
* Licensed 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.opensaml.common.impl;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.signature.XMLSignatureException;
import org.apache.xml.security.transforms.Transform;
import org.apache.xml.security.transforms.TransformationException;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xml.security.transforms.params.InclusiveNamespaces;
import org.opensaml.common.SignableSAMLObject;
import org.opensaml.xml.Configuration;
import org.opensaml.xml.Namespace;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.security.SecurityConfiguration;
import org.opensaml.xml.signature.ContentReference;
import org.opensaml.xml.signature.SignatureConstants;
import org.opensaml.xml.util.DatatypeHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A content reference for SAML objects that will be signed. The reference is created per the SAML specification.
*
* <p>
* The default digest algorithm used is the value configured in the global security configuration's
* {@link SecurityConfiguration#getSignatureReferenceDigestMethod()}, if available, otherwise
* it will be {@link SignatureConstants#ALGO_ID_DIGEST_SHA1}.
* </p>
*
* <p>
* The default set of transforms applied consists of {@link SignatureConstants#TRANSFORM_ENVELOPED_SIGNATURE}
* and {@link SignatureConstants#TRANSFORM_C14N_EXCL_WITH_COMMENTS}.
* </p>
*
* <p>
* When generating an exclusive canonicalization transform, an inclusive namespace list is
* generated from the namespaces, retrieved from {@link org.opensaml.xml.XMLObject#getNamespaces()},
* used by the SAML object to be signed and all of it's descendants.
* </p>
*
* <p>
* Note that the SAML specification states that:
* 1) an exclusive canonicalization transform (either with or without comments) SHOULD be used.
* 2) transforms other than enveloped signature and one of the two exclusive canonicalizations
* SHOULD NOT be used.
* Careful consideration should be made before deviating from these recommendations.
* </p>
*
*/
public class SAMLObjectContentReference implements ContentReference {
/** Class logger. */
private final Logger log = LoggerFactory.getLogger(SAMLObjectContentReference.class);
/** SAMLObject this reference refers to. */
private SignableSAMLObject signableObject;
/** Algorithm used to digest the content. */
private String digestAlgorithm;
/** Transforms applied to the content. */
private List<String> transforms;
/**
* Constructor.
*
* @param newSignableObject the SAMLObject this reference refers to
*/
public SAMLObjectContentReference(SignableSAMLObject newSignableObject) {
signableObject = newSignableObject;
transforms = new ArrayList<String>();
// Set defaults
if (Configuration.getGlobalSecurityConfiguration() != null ) {
digestAlgorithm = Configuration.getGlobalSecurityConfiguration().getSignatureReferenceDigestMethod();
}
if (digestAlgorithm == null) {
digestAlgorithm = SignatureConstants.ALGO_ID_DIGEST_SHA1;
}
transforms.add(SignatureConstants.TRANSFORM_ENVELOPED_SIGNATURE);
transforms.add(SignatureConstants.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);
}
/**
* Gets the transforms applied to the content prior to digest generation.
*
* @return the transforms applied to the content prior to digest generation
*/
public List<String> getTransforms() {
return transforms;
}
/**
* Gets the algorithm used to digest the content.
*
* @return the algorithm used to digest the content
*/
public String getDigestAlgorithm() {
return digestAlgorithm;
}
/**
* Sets the algorithm used to digest the content.
*
* @param newAlgorithm the algorithm used to digest the content
*/
public void setDigestAlgorithm(String newAlgorithm) {
digestAlgorithm = newAlgorithm;
}
/** {@inheritDoc} */
public void createReference(XMLSignature signature) {
try {
Transforms dsigTransforms = new Transforms(signature.getDocument());
for (int i=0; i<transforms.size(); i++) {
String transform = transforms.get(i);
dsigTransforms.addTransform(transform);
if (transform.equals(SignatureConstants.TRANSFORM_C14N_EXCL_WITH_COMMENTS) ||
transform.equals(SignatureConstants.TRANSFORM_C14N_EXCL_OMIT_COMMENTS)) {
processExclusiveTransform(signature, dsigTransforms.item(i));
}
}
if ( ! DatatypeHelper.isEmpty(signableObject.getSignatureReferenceID()) ) {
signature.addDocument("#" + signableObject.getSignatureReferenceID(), dsigTransforms, digestAlgorithm);
} else {
log.debug("SignableSAMLObject had no reference ID, signing using whole document Reference URI");
signature.addDocument("" , dsigTransforms, digestAlgorithm);
}
} catch (TransformationException e) {
log.error("Unsupported signature transformation", e);
} catch (XMLSignatureException e) {
log.error("Error adding content reference to signature", e);
}
}
/**
* Populate the inclusive namspace prefixes on the specified Apache (exclusive) transform object.
*
* @param signature the Apache XMLSignature object
* @param transform the Apache Transform object representing an exclusive transform
*/
private void processExclusiveTransform(XMLSignature signature, Transform transform) {
// Namespaces that aren't visibly used, such as those used in QName attribute values, would
// be stripped out by exclusive canonicalization. Need to make sure they aren't by explicitly
// telling the transformer about them.
log.debug("Adding list of inclusive namespaces for signature exclusive canonicalization transform");
HashSet<String> inclusiveNamespacePrefixes = new HashSet<String>();
populateNamespacePrefixes(inclusiveNamespacePrefixes, signableObject);
if (inclusiveNamespacePrefixes != null && inclusiveNamespacePrefixes.size() > 0) {
InclusiveNamespaces inclusiveNamespaces = new InclusiveNamespaces(signature.getDocument(),
inclusiveNamespacePrefixes);
transform.getElement().appendChild(inclusiveNamespaces.getElement());
}
}
/**
* Populates the given set with all the namespaces used by the given XMLObject and all of its descendants.
*
* @param namespacePrefixes the namespace prefix set to be populated
* @param signatureContent the XMLObject whose namespace prefixes will be used to populate the set
*/
private void populateNamespacePrefixes(Set<String> namespacePrefixes, XMLObject signatureContent) {
if (signatureContent.getNamespaces() != null) {
for (Namespace namespace : signatureContent.getNamespaces()) {
if (namespace != null) {
String namespacePrefix = namespace.getNamespacePrefix();
// For the default namespace prefix, exclusive c14n uses the special token "#default".
// Apache xmlsec requires this to be represented in the set with the
// (completely undocumented) string "xmlns".
if (namespacePrefix == null) {
namespacePrefix = "xmlns";
}
namespacePrefixes.add(namespacePrefix);
}
}
}
if (signatureContent.getOrderedChildren() != null) {
for (XMLObject xmlObject : signatureContent.getOrderedChildren()) {
if (xmlObject != null) {
populateNamespacePrefixes(namespacePrefixes, xmlObject);
}
}
}
}
}