/* * XAdES4j - A Java library for generation and verification of XAdES signatures. * Copyright (C) 2010 Luis Goncalves. * * XAdES4j is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or any later version. * * XAdES4j is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License along * with XAdES4j. If not, see <http://www.gnu.org/licenses/>. */ package xades4j.verification; import java.security.cert.X509CertSelector; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import javax.security.auth.x500.X500Principal; import org.apache.xml.security.exceptions.XMLSecurityException; import org.apache.xml.security.keys.KeyInfo; import org.apache.xml.security.keys.content.X509Data; import org.apache.xml.security.keys.content.x509.XMLX509IssuerSerial; import org.apache.xml.security.signature.Reference; import org.apache.xml.security.signature.SignedInfo; import org.apache.xml.security.signature.XMLSignature; import org.apache.xml.security.signature.XMLSignatureException; import org.apache.xml.security.transforms.Transforms; import org.w3c.dom.Element; import org.w3c.dom.Node; import xades4j.XAdES4jXMLSigException; import xades4j.algorithms.GenericAlgorithm; import xades4j.properties.QualifyingProperty; import xades4j.providers.CertificateValidationException; import xades4j.utils.DOMHelper; /** * * @author Luís */ class SignatureUtils { private SignatureUtils() { } /**/ static class KeyInfoRes { List<X509Certificate> keyInfoCerts; X509CertSelector certSelector; XMLX509IssuerSerial issuerSerial; KeyInfoRes( List<X509Certificate> keyInfoCerts, X509CertSelector certSelector, XMLX509IssuerSerial issuerSerial) { this.keyInfoCerts = keyInfoCerts; this.certSelector = certSelector; this.issuerSerial = issuerSerial; } } static KeyInfoRes processKeyInfo( KeyInfo keyInfo) throws CertificateValidationException { if (null == keyInfo || !keyInfo.containsX509Data()) { throw new InvalidKeyInfoDataException("No X509Data to identify the leaf certificate"); } List<X509Certificate> keyInfoCerts = new ArrayList<X509Certificate>(1); XMLX509IssuerSerial issuerSerial = null; X509CertSelector certSelector = new X509CertSelector(); // XML-DSIG 4.4.4: "Any X509IssuerSerial, X509SKI, and X509SubjectName elements // that appear MUST refer to the certificate or certificates containing the // validation key." // "All certificates appearing in an X509Data element MUST relate to the // validation key by either containing it or being part of a certification // chain that terminates in a certificate containing the validation key". // Scan ds:X509Data to find ds:IssuerSerial or ds:SubjectName elements. The // first to be found is used to select the leaf certificate. If none of those // elements is present, the first ds:X509Certificate is assumed as the signing // certificate. boolean hasSelectionCriteria = false; try { for (int i = 0; i < keyInfo.lengthX509Data(); ++i) { X509Data x509Data = keyInfo.itemX509Data(i); if(!hasSelectionCriteria) { if (x509Data.containsIssuerSerial()) { issuerSerial = x509Data.itemIssuerSerial(0); certSelector.setIssuer(new X500Principal(issuerSerial.getIssuerName())); certSelector.setSerialNumber(issuerSerial.getSerialNumber()); hasSelectionCriteria = true; } else if (x509Data.containsSubjectName()) { certSelector.setSubject(new X500Principal(x509Data.itemSubjectName(0).getSubjectName())); hasSelectionCriteria = true; } } // Collect all certificates as they may be needed to build the cert path. if (x509Data.containsCertificate()) { for (int j = 0; j < x509Data.lengthCertificate(); ++j) { keyInfoCerts.add(x509Data.itemCertificate(j).getX509Certificate()); } } } if(!hasSelectionCriteria) { if(keyInfoCerts.isEmpty()) { // No criteria to select the leaf certificate. // Improvement: search the SigningCertiticate property and try to // find the "bottom" certificate. throw new InvalidKeyInfoDataException("No criteria to select the leaf certificate"); } certSelector.setCertificate(keyInfoCerts.get(0)); } } catch (XMLSecurityException ex) { throw new InvalidKeyInfoDataException("Cannot process X509Data", ex); } return new KeyInfoRes(keyInfoCerts, certSelector, issuerSerial); } /**************************************************************************/ static class ReferencesRes { /** * In signature order. */ List<RawDataObjectDesc> dataObjsReferences; Reference signedPropsReference; ReferencesRes( List<RawDataObjectDesc> dataObjsReferences, Reference signedPropsReference) { this.dataObjsReferences = Collections.unmodifiableList(dataObjsReferences); this.signedPropsReference = signedPropsReference; } } static ReferencesRes processReferences( XMLSignature signature) throws QualifyingPropertiesIncorporationException, XAdES4jXMLSigException { SignedInfo signedInfo = signature.getSignedInfo(); List<RawDataObjectDesc> dataObjsReferences = new ArrayList<RawDataObjectDesc>(signedInfo.getLength() - 1); Reference signedPropsRef = null; for (int i = 0; i < signedInfo.getLength(); i++) { Reference ref; try { ref = signedInfo.item(i); } catch (XMLSecurityException ex) { throw new XAdES4jXMLSigException(String.format("Cannot process the %dth reference", i), ex); } String refTypeUri = ref.getType(); // XAdES 6.3.1: "In order to protect the properties with the signature, // a ds:Reference element MUST be added to the XMLDSIG signature (...) // composed in such a way that it uses the SignedProperties element (...) // as the input for computing its corresponding digest. Additionally, // (...) use the Type attribute of this particular ds:Reference element, // with its value set to: http://uri.etsi.org/01903#SignedProperties." if (QualifyingProperty.SIGNED_PROPS_TYPE_URI.equals(refTypeUri)) { if (signedPropsRef != null) { throw new QualifyingPropertiesIncorporationException("Multiple references to SignedProperties"); } signedPropsRef = ref; } else { RawDataObjectDesc dataObj = new RawDataObjectDesc(ref); dataObjsReferences.add(dataObj); try { Transforms transfs = ref.getTransforms(); if (transfs != null) { for (int j = 0; j < transfs.getLength(); ++j) { dataObj.withTransform(new GenericAlgorithm(transfs.item(j).getURI())); } } } catch (XMLSecurityException ex) { throw new XAdES4jXMLSigException("Cannot process transfroms", ex); } } } if (null == signedPropsRef) // !!! // Still may be a XAdES signature, if the signing certificate is // protected. For now, that scenario is not supported. { throw new QualifyingPropertiesIncorporationException("SignedProperties reference not found"); } return new ReferencesRes(dataObjsReferences, signedPropsRef); } /***************************************************************************/ static Element getQualifyingPropertiesElement(XMLSignature signature) throws QualifyingPropertiesIncorporationException { boolean foundXAdESContainerObject = false; Element qualifyingPropsElem = null; for (int i = 0; i < signature.getObjectLength(); ++i) { Element objElem = signature.getObjectItem(i).getElement(); Collection<Element> xadesElems = getXAdESChildElements(objElem); if (!xadesElems.isEmpty()) { // XAdES 6.3: "all instances of the QualifyingProperties and the // QualifyingPropertiesReference elements MUST occur within a single // ds:Object element". This could be tested with qualifyingPropsNode // because I'm only supporting QualifyingProperties. Anyway, the // exception message is more specific this way. if (foundXAdESContainerObject) { throw new QualifyingPropertiesIncorporationException("All instances of the QualifyingProperties element must occur within a single ds:Object element"); } // If this Object had XAdES elements, it is "the Object". The for // cycle over the Objects is not interrupted because I need to // check the correct incorporation of properties (XAdES G.2.2.1). foundXAdESContainerObject = true; for (Element e : xadesElems) { if (e.getLocalName().equals(QualifyingProperty.QUALIFYING_PROPS_TAG)) { // XAdES 6.3: "at most one instance of the QualifyingProperties // element MAY occur within this ds:Object element". if (qualifyingPropsElem != null) { throw new QualifyingPropertiesIncorporationException("Only a single QualifyingProperties element is allowed inside the ds:Object element"); } qualifyingPropsElem = e; } else // QualifyingPropertiesReference is not supported, so // nothing else on this namespace should appear. { throw new QualifyingPropertiesIncorporationException("Only the QualifyingProperties element is supported"); } } } } if (!foundXAdESContainerObject) { throw new QualifyingPropertiesIncorporationException("Couldn't find any XAdES elements"); } return qualifyingPropsElem; } private static Collection<Element> getXAdESChildElements( Element xmlObjectElem) { Collection<Element> xadesElems = new ArrayList<Element>(1); Node child = xmlObjectElem.getFirstChild(); while (child != null) { if (child.getNodeType() == Node.ELEMENT_NODE && QualifyingProperty.XADES_XMLNS.equals(child.getNamespaceURI())) { xadesElems.add((Element) child); } child = child.getNextSibling(); } return xadesElems; } static void checkSignedPropertiesIncorporation(Element qualifyingPropsElem, Reference signedPropsRef) throws QualifyingPropertiesIncorporationException { Element signedPropsElem = DOMHelper.getFirstChildElement(qualifyingPropsElem); if (signedPropsElem == null || !signedPropsElem.getLocalName().equals(QualifyingProperty.SIGNED_PROPS_TAG) || !signedPropsElem.getNamespaceURI().equals(QualifyingProperty.XADES_XMLNS)) { throw new QualifyingPropertiesIncorporationException("SignedProperties not found as the first child of QualifyingProperties."); } DOMHelper.useIdAsXmlId(signedPropsElem); // Only QualifyingProperties in the signature's document are supported. // XML-DSIG 4.3.3.2: "a same-document reference is defined as a URI-Reference // that consists of a hash sign ('#') followed by a fragment" if (!signedPropsRef.getURI().startsWith("#")) { throw new QualifyingPropertiesIncorporationException("Only QualifyingProperties in the signature's document are supported"); } try { // Node sPropsNode = signedPropsRef.getNodesetBeforeFirstCanonicalization().getSubNode(); // FIXME: Use line on top after xmlsec fixes issue SANTUARIO-462, probably on the 2.0.9 release // URL: https://issues.apache.org/jira/browse/SANTUARIO-462 Node sPropsNode = signedPropsRef.getContentsBeforeTransformation().getSubNode(); if (sPropsNode == null || sPropsNode.getNodeType() != Node.ELEMENT_NODE) { throw new QualifyingPropertiesIncorporationException("The supposed reference over signed properties doesn't cover an element."); } // The referenced signed properties element must be the child of qualifying properties. Element referencedSignedPropsElem = (Element) sPropsNode; if (referencedSignedPropsElem != signedPropsElem) { throw new QualifyingPropertiesIncorporationException("The referenced SignedProperties are not contained by the proper QualifyingProperties element"); } } catch (XMLSignatureException ex) { throw new QualifyingPropertiesIncorporationException("Cannot get the referenced SignedProperties", ex); } } }