diff --git a/src/main/java/digital/slovensko/autogram/core/XDCTransformer.java b/src/main/java/digital/slovensko/autogram/core/XDCTransformer.java index d284cce46..42737d8a5 100644 --- a/src/main/java/digital/slovensko/autogram/core/XDCTransformer.java +++ b/src/main/java/digital/slovensko/autogram/core/XDCTransformer.java @@ -26,6 +26,7 @@ import java.util.Base64; public class XDCTransformer { + public enum DestinationMediaType { TXT, HTML, XHTML } @@ -70,6 +71,19 @@ private XDCTransformer(String identifier, String xsdSchema, String xsltSchema, S this.mediaDestinationTypeDescription = mediaDestinationTypeDescription; } + public XDCTransformer(String xsdSchema, String xsltSchema, String canonicalizationMethod, + DigestAlgorithm digestAlgorithm, Document document) { + this.xsdSchema = xsdSchema; + this.xsltSchema = xsltSchema; + this.canonicalizationMethod = canonicalizationMethod; + this.digestAlgorithm = digestAlgorithm; + this.identifierUri = null; + this.identifierVersion = null; + this.containerXmlns = null; + this.mediaDestinationTypeDescription = null; + this.document = document; + } + public DSSDocument transform(DSSDocument dssDocument) { try { var xmlByteArrayInput = dssDocument.openStream().readAllBytes(); @@ -230,4 +244,36 @@ public static DOMSource extractFromXDC(Document document, DocumentBuilderFactory return new DOMSource(document); } + + private String getDigestValueFromElement(String elementLocalName) { + var xdc = document.getDocumentElement(); + + var xmlData = xdc.getElementsByTagNameNS("http://data.gov.sk/def/container/xmldatacontainer+xml/1.1", elementLocalName) + .item(0); + if (xmlData == null) + throw new RuntimeException("XMLData not found in XDC"); + + var attributes = xmlData.getAttributes(); + if (attributes == null) + throw new RuntimeException("Attributes not found"); + + var digestValue = attributes.getNamedItem("DigestValue"); + if (digestValue == null) + throw new RuntimeException("DigestValue not found"); + + return digestValue.getNodeValue(); + } + + public boolean validateXsd() { + String xsdSchemaHash = computeDigest(xsdSchema); + String xsdDigestValue = getDigestValueFromElement("UsedXSDReference"); + return xsdSchemaHash.equals(xsdDigestValue); + } + + public boolean validateXslt() { + String xsltSchemaHash = computeDigest(xsltSchema); + String xsltDigestValue = getDigestValueFromElement("UsedPresentationSchemaReference"); + return xsltSchemaHash.equals(xsltDigestValue); + } + } diff --git a/src/main/java/digital/slovensko/autogram/server/dto/SignRequestBody.java b/src/main/java/digital/slovensko/autogram/server/dto/SignRequestBody.java index 3632c50fe..0b2f6f326 100644 --- a/src/main/java/digital/slovensko/autogram/server/dto/SignRequestBody.java +++ b/src/main/java/digital/slovensko/autogram/server/dto/SignRequestBody.java @@ -15,6 +15,7 @@ import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.*; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; @@ -64,16 +65,59 @@ public SigningParameters getParameters() throws RequestValidationException { parameters.validate(getDocument().getMimeType()); SigningParameters signingParameters = parameters.getSigningParameters(isBase64()); - - if (!validateXml(getXmlContent(), signingParameters.getSchema())) - throw new RequestValidationException("XML validation against XSD failed", ""); + + validateXml(signingParameters); return signingParameters; } - private String getXmlContent() { - boolean isXdc = getDocument().getMimeType().equals(AutogramMimeType.XML_DATACONTAINER); - return isXdc ? getContentFromXdc() : getDecodedContent(); + private boolean isBase64() { + return payloadMimeType.contains("base64"); + } + + private void validateXml(SigningParameters signingParameters) { + String xsdSchema = signingParameters.getSchema(); + String xmlContent; + + if (isXdc()) { + if (!validateXsdAndXsltHashes(signingParameters)) { + throw new RequestValidationException("XML validation failed", "Invalid xsd scheme or xslt transformation"); + } + + xmlContent = getContentFromXdc(); + if (xmlContent == null) { + throw new RequestValidationException("XML validation failed", "Unable to get content from xdc container"); + } + } else { + xmlContent = getDecodedContent(); + } + + if (!validateXmlContentAgainstXsd(xmlContent, xsdSchema)) { + throw new RequestValidationException("XML validation failed", "XML validation against XSD failed"); + } + } + + private boolean isXdc() { + return getDocument().getMimeType().equals(AutogramMimeType.XML_DATACONTAINER); + } + + private boolean validateXsdAndXsltHashes(SigningParameters sp) { + try { + var builderFactory = DocumentBuilderFactory.newInstance(); + builderFactory.setNamespaceAware(true); + + XDCTransformer xdcTransformer = new XDCTransformer( + sp.getSchema(), + sp.getTransformation(), + sp.getPropertiesCanonicalization(), + sp.getDigestAlgorithm(), + builderFactory.newDocumentBuilder().parse(new InputSource(getDocument().openStream())) + ); + + return xdcTransformer.validateXsd() && xdcTransformer.validateXslt(); + } catch (Exception e) { + return false; + } } private String getContentFromXdc() { @@ -84,7 +128,7 @@ private String getContentFromXdc() { var element = XDCTransformer.extractFromXDC(document, builderFactory); return transformElementToString(element); } catch (Exception e) { - throw new RequestValidationException("Unable to get XML content from XDC container", ""); + return null; } } @@ -97,7 +141,11 @@ private static String transformElementToString(DOMSource element) throws Transfo return writer.toString(); } - private boolean validateXml(String xmlContent, String xsdSchema) { + private String getDecodedContent() { + return isBase64() ? new String(Base64.getDecoder().decode(document.getContent())) : document.getContent(); + } + + private boolean validateXmlContentAgainstXsd(String xmlContent, String xsdSchema) { if (xsdSchema == null) { return true; } @@ -111,12 +159,4 @@ private boolean validateXml(String xmlContent, String xsdSchema) { return false; } } - - private boolean isBase64() { - return payloadMimeType.contains("base64"); - } - - private String getDecodedContent() { - return isBase64() ? new String(Base64.getDecoder().decode(document.getContent())) : document.getContent(); - } }