DataObject
of an XML-Signature
* created by the security layer command CreateXMLSignature
.
*
* @author mcentner
*/
public class DataObject {
/**
* Logging facility.
*/
private static Log log = LogFactory.getLog(DataObject.class);
/**
* DOM Implementation.
*/
private static final String DOM_LS_3_0 = "LS 3.0";
/**
* The array of the default preferred MIME type order.
*/
private static final String[] DEFAULT_PREFFERED_MIME_TYPES =
new String[] {
"application/xhtml+xml",
"text/plain"
};
/**
* The DOM implementation used.
*/
private DOMImplementationLS domImplLS;
/**
* The signature context.
*/
private SignatureContext ctx;
/**
* The Reference for this DataObject.
*/
private XSECTReference reference;
/**
* The XMLObject for this DataObject.
*/
private XMLObject xmlObject;
/**
* The MIME-Type of the digest input.
*/
private String mimeType;
/**
* An optional description of the digest input.
*/
private String description;
/**
* Creates a new instance.
*
* @param document the document of the target signature
*/
public DataObject(SignatureContext signatureContext) {
this.ctx = signatureContext;
DOMImplementationRegistry registry;
try {
registry = DOMImplementationRegistry.newInstance();
} catch (Exception e) {
log.error("Failed to get DOMImplementationRegistry.", e);
throw new SLRuntimeException("Failed to get DOMImplementationRegistry.");
}
domImplLS = (DOMImplementationLS) registry.getDOMImplementation(DOM_LS_3_0);
if (domImplLS == null) {
log.error("Failed to get DOMImplementation " + DOM_LS_3_0);
throw new SLRuntimeException("Failed to get DOMImplementation " + DOM_LS_3_0);
}
}
/**
* @return the reference
*/
public Reference getReference() {
return reference;
}
/**
* @return the xmlObject
*/
public XMLObject getXmlObject() {
return xmlObject;
}
/**
* @return the mimeType
*/
public String getMimeType() {
return mimeType;
}
/**
* @return the description
*/
public String getDescription() {
return description;
}
/**
* Configures this DataObject with the information provided within the given
* sl:DataObjectInfo
.
*
* @param dataObjectInfo
* the sl:DataObjectInfo
*
* @throws SLCommandException
* if configuring this DataObject with the information provided in
* the sl:DataObjectInfo
fails.
* @throws SLRequestException
* if the information provided in the sl:DataObjectInfo
* does not conform to the security layer specification.
* @throws NullPointerException
* if dataObjectInfo
is null
*/
public void setDataObjectInfo(DataObjectInfoType dataObjectInfo) throws SLCommandException, SLRequestException {
Base64XMLLocRefOptRefContentType dataObject = dataObjectInfo.getDataObject();
String structure = dataObjectInfo.getStructure();
// select and unmarshal an appropriate transformation path if provided
// and set the final data meta information
XSECTTransforms transforms = createTransformsAndSetFinalDataMetaInfo(dataObjectInfo.getTransformsInfo());
if ("enveloping".equals(structure)) {
// configure this DataObject as an enveloped DataObject
setEnvelopedDataObject(dataObject, transforms);
} else if ("detached".equals(structure)) {
// configure this DataObject as an detached DataObject
setDetachedDataObject(dataObject, transforms);
}
// other values are not allowed by the schema and are therefore ignored
}
/**
* Configures this DataObject as an enveloped DataObject with the information
* provided within the given sl:DataObject
.
*
* @param dataObject
* the sl:DataObject
* @param transforms
* an optional Transforms
element (may be
* null
)
*
* @throws SLCommandException
* if configuring this DataObject with the information provided in
* the sl:DataObject
fails.
* @throws SLRequestException
* if the information provided in the sl:DataObject
* does not conform to the security layer specification.
* @throws NullPointerException
* if dataObject
is null
*/
private void setEnvelopedDataObject(
Base64XMLLocRefOptRefContentType dataObject, XSECTTransforms transforms)
throws SLCommandException, SLRequestException {
String reference = dataObject.getReference();
if (reference == null) {
//
// case A
//
// The Reference attribute is not used; the content of sl:DataObject represents the data object.
// If the data object is XML-coded (the sl:XMLContent element is used in sl:DataObject), then it
// must be incorporated in the signature structure as parsed XML.
//
if (dataObject.getBase64Content() != null) {
log.debug("Adding DataObject (Base64Content) without a reference URI.");
// create XMLObject
XMLObject xmlObject = createXMLObject(new ByteArrayInputStream(dataObject.getBase64Content()));
setXMLObjectAndReferenceBase64(xmlObject, transforms);
} else if (dataObject.getXMLContent() != null) {
log.debug("Adding DataObject (XMLContent) without a reference URI.");
// create XMLObject
DocumentFragment content = parseDataObject((XMLContentType) dataObject.getXMLContent());
XMLObject xmlObject = createXMLObject(content);
setXMLObjectAndReferenceXML(xmlObject, transforms);
} else if (dataObject.getLocRefContent() != null) {
log.debug("Adding DataObject (LocRefContent) without a reference URI.");
setEnvelopedDataObject(dataObject.getLocRefContent(), transforms);
} else {
// not allowed
log.info("XML structure of the command request contains an " +
"invalid combination of optional elements or attributes. " +
"DataObject of structure='enveloped' without a reference must contain content.");
throw new SLRequestException(3003);
}
} else {
if (dataObject.getBase64Content() == null &&
dataObject.getXMLContent() == null &&
dataObject.getLocRefContent() == null) {
//
// case B
//
// The Reference attribute contains a URI that must be resolved by the
// Citizen Card Environment to obtain the data object.
// The content of sl:DataObject remains empty
//
log.debug("Adding DataObject from reference URI '" + reference + "'.");
setEnvelopedDataObject(reference, transforms);
} else {
// not allowed
log.info("XML structure of the command request contains an " +
"invalid combination of optional elements or attributes. " +
"DataObject of structure='enveloped' with reference must not contain content.");
throw new SLRequestException(3003);
}
}
}
/**
* Configures this DataObject as an enveloped DataObject with the content to
* be dereferenced from the given reference
.
*
* @param reference
* the reference
URI
* @param transforms
* an optional Transforms
element (may be
* null
)
*
* @throws SLCommandException
* if dereferencing the given reference
fails, or if
* configuring this DataObject with the data dereferenced from the
* given reference
fails.
* @throws NullPointerException
* if reference
is null
*/
private void setEnvelopedDataObject(String reference, XSECTTransforms transforms) throws SLCommandException {
if (reference == null) {
throw new NullPointerException("Argument 'reference' must not be null.");
}
// dereference URL
URLDereferencer dereferencer = URLDereferencer.getInstance();
StreamData streamData;
try {
streamData = dereferencer.dereference(reference, ctx.getDereferencerContext());
} catch (IOException e) {
log.info("Failed to dereference XMLObject from '" + reference + "'.", e);
throw new SLCommandException(4110);
}
Node childNode;
String contentType = streamData.getContentType();
if (contentType.startsWith("text/xml")) {
// If content type is text/xml parse content.
String charset = HttpUtil.getCharset(contentType, true);
Document doc = parseDataObject(streamData.getStream(), charset);
childNode = doc.getDocumentElement();
if (childNode == null) {
log.info("Failed to parse XMLObject from '" + reference + "'.");
throw new SLCommandException(4111);
}
XMLObject xmlObject = createXMLObject(childNode);
setXMLObjectAndReferenceXML(xmlObject, transforms);
} else {
// Include content Base64 encoded.
XMLObject xmlObject = createXMLObject(streamData.getStream());
setXMLObjectAndReferenceBase64(xmlObject, transforms);
}
}
/**
* Configures this DataObject as an detached DataObject with the information
* provided in the given sl:DataObject
and optionally
* transforms
.
*
* @param dataObject
* the sl:DataObject
* @param transforms
* an optional Transforms object, may be null
*
* @throws SLCommandException
* if configuring this DataObject with the information provided in
* the sl:DataObject
fails.
* @throws SLRequestException
* if the information provided in the sl:DataObject
* does not conform to the security layer specification.
* @throws NullPointerException
* if dataObject
is null
*/
private void setDetachedDataObject(
Base64XMLLocRefOptRefContentType dataObject, XSECTTransforms transforms)
throws SLCommandException, SLRequestException {
String referenceURI = dataObject.getReference();
if (referenceURI == null) {
// not allowed
log.info("XML structure of the command request contains an " +
"invalid combination of optional elements or attributes. " +
"DataObject of structure='detached' must contain a reference.");
throw new SLRequestException(3003);
} else {
DigestMethod dm;
try {
dm = ctx.getAlgorithmMethodFactory().createDigestMethod(ctx);
} catch (NoSuchAlgorithmException e) {
log.error("Failed to get DigestMethod.", e);
throw new SLCommandException(4006);
} catch (InvalidAlgorithmParameterException e) {
log.error("Failed to get DigestMethod.", e);
throw new SLCommandException(4006);
}
String idValue = ctx.getIdValueFactory().createIdValue("Reference");
reference = new XSECTReference(referenceURI, dm, transforms, null, idValue);
// case D:
//
// The Reference attribute contains a URI that is used by the Citizen Card
// Environment to code the reference to the data object as part of the XML
// signature (attribute URI in the dsig:Reference) element. The content of
// sl:DataObject represents the data object.
if (dataObject.getLocRefContent() != null) {
String locRef = dataObject.getLocRefContent();
try {
this.reference.setDereferencer(new LocRefDereferencer(ctx.getDereferencerContext(), locRef));
} catch (URISyntaxException e) {
log.info("Invalid URI '" + locRef + "' in DataObject.", e);
throw new SLCommandException(4003);
} catch (IllegalArgumentException e) {
log.info("LocRef URI of '" + locRef + "' not supported in DataObject. ", e);
throw new SLCommandException(4003);
}
} else if (dataObject.getBase64Content() != null) {
byte[] base64Content = dataObject.getBase64Content();
this.reference.setDereferencer(new ByteArrayDereferencer(base64Content));
} else if (dataObject.getXMLContent() != null) {
XMLContentType xmlContent = (XMLContentType) dataObject.getXMLContent();
byte[] bytes = xmlContent.getRedirectedStream().toByteArray();
this.reference.setDereferencer(new ByteArrayDereferencer(bytes));
} else {
// case C:
//
// The Reference attribute contains a URI that must be resolved by the
// Citizen Card Environment to obtain the data object. The Reference
// attribute contains a URI that is used by the Citizen Card Environment
// to code the reference to the data object as part of the XML signature
// (attribute URI in the dsig:Reference) element. The content of
// sl:DataObject remains empty.
}
}
}
/**
* Returns the preferred sl:TransformInfo
from the given list of
* transformInfos
, or null
if none of the given
* transformInfos
is preferred over the others.
*
* @param transformsInfos
* a list of sl:TransformInfo
s
*
* @return the selected sl:TransformInfo
or null
, if
* none is preferred over the others
*/
private TransformsInfoType selectPreferredTransformsInfo(Listds:Transforms
from the given
* sl:TransformsInfo
.
*
* @param transformsInfo
* the sl:TransformsInfo
*
* @return a corresponding unmarshalled ds:Transforms
, or
* null
if the given sl:TransformsInfo
does
* not contain a dsig:Transforms
element
*
* @throws SLRequestException
* if the ds:Transforms
in the given
* transformsInfo
are not valid or cannot be parsed.
*
* @throws MarshalException
* if the ds:Transforms
in the given
* transformsInfo
cannot be unmarshalled.
*/
private XSECTTransforms createTransforms(TransformsInfoType transformsInfo) throws SLRequestException, MarshalException {
ByteArrayOutputStream redirectedStream = ((at.gv.egiz.slbinding.impl.TransformsInfoType) transformsInfo).getRedirectedStream();
byte[] transformBytes = (redirectedStream != null) ? redirectedStream.toByteArray() : null;
if (transformBytes != null && transformBytes.length > 0) {
// debug
if (log.isTraceEnabled()) {
StringBuilder sb = new StringBuilder();
sb.append("Trying to parse transforms:\n");
sb.append(new String(transformBytes, Charset.forName("UTF-8")));
log.trace(sb);
}
DOMImplementationLS domImplLS = DOMUtils.getDOMImplementationLS();
LSInput input = domImplLS.createLSInput();
input.setByteStream(new ByteArrayInputStream(transformBytes));
LSParser parser = domImplLS.createLSParser(
DOMImplementationLS.MODE_SYNCHRONOUS, null);
DOMConfiguration domConfig = parser.getDomConfig();
SimpleDOMErrorHandler errorHandler = new SimpleDOMErrorHandler();
domConfig.setParameter("error-handler", errorHandler);
domConfig.setParameter("validate", Boolean.FALSE);
Document document;
try {
document = parser.parse(input);
} catch (DOMException e) {
log.info("Failed to parse dsig:Transforms.", e);
throw new SLRequestException(3002);
} catch (LSException e) {
log.info("Failed to parse dsig:Transforms.", e);
throw new SLRequestException(3002);
}
// adopt ds:Transforms
Element documentElement = document.getDocumentElement();
Node adoptedTransforms = ctx.getDocument().adoptNode(documentElement);
DOMCryptoContext context = new DOMCryptoContext();
// unmarshall ds:Transforms
return new XSECTTransforms(context, adoptedTransforms);
} else {
return null;
}
}
/**
* Sets the mimeType
and the description
value
* for this DataObject.
*
* @param metaInfoType the sl:FinalMetaDataInfo
*
* @throws NullPointerException if metaInfoType
is null
*/
private void setFinalDataMetaInfo(MetaInfoType metaInfoType) {
this.mimeType = metaInfoType.getMimeType();
this.description = metaInfoType.getDescription();
}
/**
* Selects an appropriate transformation path (if present) from the given list
* of sl:TransformInfos
, sets the corresponding final data meta info and
* returns the corresponding unmarshalled ds:Transforms
.
*
* @param transformsInfos the sl:TransformInfos
*
* @return the unmarshalled ds:Transforms
, or null
if
* no transformation path has been selected.
*
* @throws SLRequestException if the given list ds:TransformsInfo
contains
* an invalid ds:Transforms
element, or no suitable transformation path
* can be found.
*/
private XSECTTransforms createTransformsAndSetFinalDataMetaInfo(
Listcontent
.
*
* @param content
* the to-be Base64 encoded content
* @return an XMLObject with the Base64 encoded content
*/
private XMLObject createXMLObject(InputStream content) {
Text textNode;
try {
textNode = at.gv.egiz.dom.DOMUtils.createBase64Text(content, ctx.getDocument());
} catch (IOException e) {
log.error(e);
throw new SLRuntimeException(e);
}
DOMStructure structure = new DOMStructure(textNode);
String idValue = ctx.getIdValueFactory().createIdValue("Object");
return ctx.getSignatureFactory().newXMLObject(Collections.singletonList(structure), idValue, null, null);
}
/**
* Create an XMLObject with the given content
node.
*
* @param content the content node
*
* @return an XMLObject with the given content
*/
private XMLObject createXMLObject(Node content) {
String idValue = ctx.getIdValueFactory().createIdValue("Object");
ListxmlObject
and creates and sets a corresponding
* Reference
.
*
* A transform to Base64-decode the xmlObject's content is inserted at the top
* of to the optional transforms
if given, or to a newly created
* Transforms
element if transforms
is
* null
.
*
* @param xmlObject
* the XMLObject
* @param transforms
* an optional Transforms
element (may be
* null
)
*
* @throws SLCommandException
* if creating the Reference fails
* @throws NullPointerException
* if xmlObject
is null
*/
private void setXMLObjectAndReferenceBase64(XMLObject xmlObject, XSECTTransforms transforms) throws SLCommandException {
// create reference URI
//
// NOTE: the ds:Object can be referenced directly, as the Base64 transform
// operates on the text() of the input nodelist.
//
String referenceURI = "#" + xmlObject.getId();
// create Base64 Transform
Transform transform;
try {
transform = ctx.getSignatureFactory().newTransform(Transform.BASE64, (TransformParameterSpec) null);
} catch (NoSuchAlgorithmException e) {
// algorithm must be present
throw new SLRuntimeException(e);
} catch (InvalidAlgorithmParameterException e) {
// algorithm does not take parameters
throw new SLRuntimeException(e);
}
if (transforms == null) {
transforms = new XSECTTransforms(Collections.singletonList(transform));
} else {
transforms.insertTransform(transform);
}
DigestMethod dm;
try {
dm = ctx.getAlgorithmMethodFactory().createDigestMethod(ctx);
} catch (NoSuchAlgorithmException e) {
log.error("Failed to get DigestMethod.", e);
throw new SLCommandException(4006);
} catch (InvalidAlgorithmParameterException e) {
log.error("Failed to get DigestMethod.", e);
throw new SLCommandException(4006);
}
String id = ctx.getIdValueFactory().createIdValue("Reference");
this.xmlObject = xmlObject;
this.reference = new XSECTReference(referenceURI, dm, transforms, null, id);
}
/**
* Sets the given xmlObject
and creates and sets a corresponding
* Reference
.
*
* A transform to select the xmlObject's content is inserted at the top of to
* the optional transforms
if given, or to a newly created
* Transforms
element if transforms
is
* null
.
*
Transforms
element (may be
* null
)
*
* @throws SLCommandException
* if creating the Reference fails
* @throws NullPointerException
* if xmlObject
is null
*/
private void setXMLObjectAndReferenceXML(XMLObject xmlObject, XSECTTransforms transforms) throws SLCommandException {
// create reference URI
String referenceURI = "#" + xmlObject.getId();
// create Transform to select ds:Object's children
Transform xpathTransform;
Transform c14nTransform;
try {
XPathType xpath = new XPathType("id(\"" + xmlObject.getId() + "\")/node()", XPathType.Filter.INTERSECT);
ListxmlContent
and returns a corresponding
* document fragment.
*
*
* The to-be parsed content is surrounded by
xmlContent
fails
*
* @throws NullPointerException
* if xmlContent
is null
*/
private DocumentFragment parseDataObject(XMLContentType xmlContent) throws SLCommandException {
ByteArrayOutputStream redirectedStream = xmlContent.getRedirectedStream();
// Note: We can assume a fixed character encoding of UTF-8 for the
// content of the redirect stream as the content has already been parsed
// and serialized again to the redirect stream.
ListinputStream
using the given
* encoding
and returns the parsed document.
*
* @param inputStream
* the to-be parsed input
*
* @param encoding
* the encoding to be used for parsing the given
* inputStream
*
* @return the parsed document
*
* @throws SLCommandException
* if parsing the inputStream
fails.
*
* @throws NullPointerException
* if inputStram
is null
*/
private Document parseDataObject(InputStream inputStream, String encoding) throws SLCommandException {
LSInput input = domImplLS.createLSInput();
input.setByteStream(inputStream);
if (encoding != null) {
input.setEncoding(encoding);
}
LSParser parser = domImplLS.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null);
DOMConfiguration domConfig = parser.getDomConfig();
SimpleDOMErrorHandler errorHandler = new SimpleDOMErrorHandler();
domConfig.setParameter("error-handler", errorHandler);
domConfig.setParameter("validate", Boolean.FALSE);
Document doc;
try {
doc = parser.parse(input);
} catch (DOMException e) {
log.info("Existing XML document cannot be parsed.", e);
throw new SLCommandException(4111);
} catch (LSException e) {
log.info("Existing XML document cannot be parsed. ", e);
throw new SLCommandException(4111);
}
if (errorHandler.hasErrors()) {
// log errors
if (log.isInfoEnabled()) {
List