/* * Copyright 2003 Federal Chancellery Austria * MOA-SPSS has been developed in a cooperation between BRZ, the Federal * Chancellery Austria - ICT staff unit, and Graz University of Technology. * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * http://www.osor.eu/eupl/ * * Unless required by applicable law or agreed to in writing, software * distributed under the Licence is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and * limitations under the Licence. * * This product combines work with different licenses. See the "NOTICE" text * file for details on the various modules and licenses. * The "NOTICE" text file is part of the distribution. Any derivative works * that you distribute must include a readable copy of the "NOTICE" text file. */ package at.gv.egovernment.moa.spss.server.invoke; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Date; import javax.xml.crypto.OctetStreamData; import javax.xml.crypto.dsig.CanonicalizationMethod; import javax.xml.crypto.dsig.spec.ExcC14NParameterSpec; import org.w3c.dom.DocumentFragment; import org.w3c.dom.NodeList; import at.gv.egovernment.moa.spss.MOAApplicationException; import at.gv.egovernment.moa.spss.api.SPSSFactory; import at.gv.egovernment.moa.spss.api.common.CheckResult; import at.gv.egovernment.moa.spss.api.common.Content; import at.gv.egovernment.moa.spss.api.common.ExtendedCertificateCheckResult; import at.gv.egovernment.moa.spss.api.common.InputData; import at.gv.egovernment.moa.spss.api.common.SignerInfo; import at.gv.egovernment.moa.spss.api.common.TslInfos; import at.gv.egovernment.moa.spss.api.impl.InputDataBinaryImpl; import at.gv.egovernment.moa.spss.api.impl.InputDataXMLImpl; import at.gv.egovernment.moa.spss.api.xmlverify.ManifestRefsCheckResultInfo; import at.gv.egovernment.moa.spss.api.xmlverify.ReferencesCheckResult; import at.gv.egovernment.moa.spss.api.xmlverify.ReferencesCheckResultInfo; import at.gv.egovernment.moa.spss.api.xmlverify.VerifyXMLSignatureResponse; import at.gv.egovernment.moaspss.util.CollectionUtils; import at.gv.egovernment.moaspss.util.DOMUtils; import at.gv.egovernment.moaspss.util.NodeListAdapter; import iaik.server.modules.xml.BinaryDataObject; import iaik.server.modules.xml.DataObject; import iaik.server.modules.xml.XMLDataObject; import iaik.server.modules.xml.XMLNodeListDataObject; import iaik.server.modules.xmlverify.CertificateValidationResult; import iaik.server.modules.xmlverify.DsigManifest; import iaik.server.modules.xmlverify.HashUnavailableException; import iaik.server.modules.xmlverify.ReferenceData; import iaik.server.modules.xmlverify.ReferenceInfo; import iaik.server.modules.xmlverify.SecurityLayerManifest; import iaik.server.modules.xmlverify.XMLSignatureVerificationProfile; import iaik.server.modules.xmlverify.XMLSignatureVerificationResult; import iaik.x509.X509Certificate; import iaik.xml.crypto.alg.transform.C14NTransformService; import iaik.xml.crypto.dsig.CanonicalizationMethodImpl; /** * A class to build a VerifyXMLSignatureResponse object. * *

Via a call to addResult() the only result of the * signature verification must be added.

* *

The getResponseElement() method then returns the * VerifyXMLSignatureResponse built so far.

* * @author Patrick Peck * @version $Id$ */ public class VerifyXMLSignatureResponseBuilder { /** The SPSSFactory for creating API objects. */ private SPSSFactory factory = SPSSFactory.getInstance(); /** Information about the signer certificate. */ private SignerInfo signerInfo; /** The hash input data. */ private List hashInputDatas; /** The reference input data. */ private List referenceInputDatas; /** The result of the signature check. */ private ReferencesCheckResult signatureCheck; /** The result of the signature manifest check. */ private ReferencesCheckResult signatureManifestCheck; /** The result of the XMLDsig manifest check. */ private List xmlDsigManifestChecks; /** The result of the certificate check. */ private CheckResult certificateCheck; private List adesFormResults = null; private ExtendedCertificateCheckResult extCheckResult = null; private Date signingTime; private String signatureAlgorithm = null; /** * Get the VerifyMLSignatureResponse built so far. * * @return The VerifyXMLSignatureResponse built so far. */ public VerifyXMLSignatureResponse getResponse() { return factory.createVerifyXMLSignatureResponse( signerInfo, hashInputDatas, referenceInputDatas, signatureCheck, signatureManifestCheck, xmlDsigManifestChecks, certificateCheck, adesFormResults, extCheckResult, signatureAlgorithm); } public void setExtendedCertificateCheckResult(ExtendedCertificateCheckResult extCheckResult) { this.extCheckResult = extCheckResult; } public void setAdESFormResults(List adesForm) { this.adesFormResults = adesForm; } /** * Sets the verification result to the response. * * This method must be called exactly once to ensure a valid * VerifyXMLSignatureResponse. * * @param result The result to set for the response. * @param profile The profile used for verifying the signature. * @param transformsSignatureManifestCheck The overall result for the signature * manifest check. * @param certificateCheck The overall result for the certificate check. * @param checkQC true, if the certificate is QC, otherwise false. * @param qcSourceTSL true, if the QC information comes from the TSL, * otherwise false. * @param checkSSCD true, if the signature is created by an SSCD, otherwise false. * @param sscdSourceTSL true, if the SSCD information comes from the TSL, * otherwise false. * @throws MOAApplicationException An error occurred adding the result. */ public void setResult( XMLSignatureVerificationResult result, XMLSignatureVerificationProfile profile, ReferencesCheckResult transformsSignatureManifestCheck, CheckResult certificateCheck, boolean checkQC, boolean qcSourceTSL, boolean checkSSCD, boolean sscdSourceTSL, boolean isTSLEnabledTrustprofile, String issuerCountryCode, TslInfos tslInfos, boolean isExtendedValidation) throws MOAApplicationException { CertificateValidationResult certResult = result.getCertificateValidationResult(); List referenceDataList; ReferenceData referenceData; List dsigManifestList; ReferencesCheckResultInfo checkResultInfo; int[] failedReferences; Iterator iter; boolean qualifiedCertificate = false; qualifiedCertificate = checkQC; if (isExtendedValidation) signatureAlgorithm = result.getSignatureAlgorithmName(); // create the SignerInfo; signerInfo = factory.createSignerInfo( (X509Certificate) certResult.getCertificateChain().get(0), qualifiedCertificate, qcSourceTSL, certResult.isPublicAuthorityCertificate(), certResult.getPublicAuthorityID(), checkSSCD, sscdSourceTSL, issuerCountryCode, result.getSigningTime(), tslInfos); //TODO: add hash algo. infos // Create HashInputData Content objects referenceDataList = result.getReferenceDataList(); if (profile.includeHashInputData()) { hashInputDatas = new ArrayList(); // Include SignedInfo references addHashInputDatas( hashInputDatas, referenceDataList, InputData.CONTAINER_SIGNEDINFO_, InputData.REFERER_NONE_); // Include XMLDSIGManifest references List xMLDSIGManifests = result.getDsigManifestList(); for (iter = xMLDSIGManifests.iterator(); iter.hasNext();) { DsigManifest currentMF = (DsigManifest) iter.next(); List xMLDSIGMFReferenceDataList = currentMF.getReferenceDataList(); addHashInputDatas( hashInputDatas, xMLDSIGMFReferenceDataList, InputData.CONTAINER_XMLDSIGMANIFEST_, currentMF.getReferringReferenceInfo().getReferenceIndex()); } } // Create the ReferenceInputData Content objects if (profile.includeReferenceInputData()) { referenceInputDatas = new ArrayList(); // Include SignedInfo references addReferenceInputDatas( referenceInputDatas, referenceDataList, InputData.CONTAINER_SIGNEDINFO_, InputData.REFERER_NONE_); // Include XMLDSIGManifest references List xMLDSIGManifests = result.getDsigManifestList(); for (iter = xMLDSIGManifests.iterator(); iter.hasNext();) { DsigManifest currentMF = (DsigManifest) iter.next(); List xMLDSIGMFReferenceDataList = currentMF.getReferenceDataList(); addReferenceInputDatas( referenceInputDatas, xMLDSIGMFReferenceDataList, InputData.CONTAINER_XMLDSIGMANIFEST_, currentMF.getReferringReferenceInfo().getReferenceIndex()); } } // create the signature check failedReferences = buildFailedReferences(result.getReferenceDataList()); checkResultInfo = failedReferences != null ? factory.createReferencesCheckResultInfo(null, failedReferences) : null; signatureCheck = factory.createReferencesCheckResult( result.getSignatureValueVerificationCode().intValue(), checkResultInfo); // create the signature manifest check if (profile.checkSecurityLayerManifest()) { if (transformsSignatureManifestCheck.getCode() == 1) { // checking the transforms failed signatureManifestCheck = transformsSignatureManifestCheck; } else if (result.isSecurityLayerManifestRequired()) { if (!result.containsSecurityLayerManifest()) { // required security layer manifest is missing in signature signatureManifestCheck = factory.createReferencesCheckResult(2, null); } else { // security layer manifest exists, so we have to check its validity SecurityLayerManifest slManifest = result.getSecurityLayerManifest(); int verificationResult = slManifest.getManifestVerificationResult().intValue(); if (SecurityLayerManifest.CODE_MANIFEST_VALID.intValue() == verificationResult) { // security layer manifest exists and is free of errors signatureManifestCheck = factory.createReferencesCheckResult(0, null); } else { // security layer manifest exists, but has errors failedReferences = buildFailedReferences(slManifest.getReferenceDataList()); checkResultInfo = (failedReferences != null) ? factory.createReferencesCheckResultInfo(null, failedReferences) : null; if (SecurityLayerManifest.CODE_MANIFEST_INCOMPLETE.intValue() == verificationResult) { signatureManifestCheck = factory.createReferencesCheckResult(3, checkResultInfo); } else if (SecurityLayerManifest.CODE_REFERENCE_HASH_INVALID.intValue() == verificationResult) { signatureManifestCheck = factory.createReferencesCheckResult(4, checkResultInfo); } else { // Should not happen throw new RuntimeException("Unexpected result from security layer manifest verification."); } } } } else { // no security layer manifest is required, so the signature manifest check is ok signatureManifestCheck = factory.createReferencesCheckResult(0, null); } } // create the xmlDsigManifestCheck if (profile.checkXMLDsigManifests()) { xmlDsigManifestChecks = new ArrayList(); dsigManifestList = result.getDsigManifestList(); for (iter = dsigManifestList.iterator(); iter.hasNext();) { DsigManifest dsigManifest = (DsigManifest) iter.next(); int refIndex = dsigManifest.getReferringReferenceInfo().getReferenceIndex(); ManifestRefsCheckResultInfo manifestCheckResultInfo; failedReferences = buildFailedReferences(dsigManifest.getReferenceDataList()); manifestCheckResultInfo = factory.createManifestRefsCheckResultInfo( null, failedReferences, refIndex); xmlDsigManifestChecks.add( factory.createManifestRefsCheckResult( dsigManifest.getManifestVerificationResult().intValue(), manifestCheckResultInfo)); } } // create the certificate check this.certificateCheck = certificateCheck; this.signingTime = result.getSigningTime(); } /** * Adds {@link InputData} entries to the specified inputDatas list. The content of the entry will * be created from {@link ReferenceData#getHashInputData()}. * * @param inputDatas The list to be amended. * * @param referenceDataList The list of {@link ReferenceData} objects to be investigated. * * @param containerType The type of container of the {@link InputData} objects to be created. * * @param refererNumber The number of the referring reference for the {@link InputData} objects to be created. * * @throws MOAApplicationException if creating an {@link InputData} fails. */ private void addHashInputDatas(List inputDatas, List referenceDataList, String containerType, int refererNumber) throws MOAApplicationException { for (Iterator iter = referenceDataList.iterator(); iter.hasNext();) { ReferenceData referenceData = (ReferenceData) iter.next(); inputDatas.add(buildInputData( referenceData.getHashInputData(), containerType, refererNumber, referenceData.getHashAlgorithmName())); } } /** * Adds {@link InputData} entries to the specified inputDatas list. The content of the entry will * be created from {@link ReferenceData#getReferenceInputData()}. * * @param inputDatas The list to be amended. * * @param referenceDataList The list of {@link ReferenceData} objects to be investigated. * * @param containerType The type of container of the {@link InputData} objects to be created. * * @param refererNumber The number of the referring reference for the {@link InputData} objects to be created. * * @throws MOAApplicationException if creating an {@link InputData} fails. */ private void addReferenceInputDatas(List inputDatas, List referenceDataList, String containerType, int refererNumber) throws MOAApplicationException { for (Iterator iter = referenceDataList.iterator(); iter.hasNext();) { ReferenceData referenceData = (ReferenceData) iter.next(); inputDatas.add(buildInputData( referenceData.getReferenceInputData(), containerType, refererNumber, referenceData.getHashAlgorithmName())); } } /** * Build a InputDataBinaryImpl or an InputDataXMLImpl * object from the given DataObject and the given attributes. * * @param dataObject The DataObject from which to build the result. * Based on the type of this parameter, the type of the result will either be * InputDataBinaryImpl or InputDataXMLImpl. * * @param partof see {@link InputData} * * @param referringReferenceNumber see {@link InputData} * * @param hashAlg see {@link InputData} * * @return The corresponinding input data implementation. * @throws MOAApplicationException An error occurred creating the result. */ private Content buildInputData(DataObject dataObject, String partOf, int referringReferenceNumber, String hashAlg) throws MOAApplicationException { if (dataObject instanceof BinaryDataObject) { BinaryDataObject binaryData = (BinaryDataObject) dataObject; return new InputDataBinaryImpl( factory.createContent(binaryData.getInputStream(), null), partOf, referringReferenceNumber, hashAlg); } else if (dataObject instanceof XMLDataObject) { XMLDataObject xmlData = (XMLDataObject) dataObject; List nodes = new ArrayList(); nodes.add(xmlData.getElement()); return new InputDataXMLImpl( factory.createContent(new NodeListAdapter(nodes), null), partOf, referringReferenceNumber, hashAlg); } else { // dataObject instanceof XMLNodeListDataObject // if the data in the NodeList can be converted back to valid XML, // write it as XMLContent; otherwise, write it as Base64Content XMLNodeListDataObject nodeData = (XMLNodeListDataObject) dataObject; NodeList nodes = nodeData.getNodeList(); if (DOMUtils.checkAttributeParentsInNodeList(nodes)) { // insert as XMLContent try { DocumentFragment fragment = DOMUtils.nodeList2DocumentFragment(nodes); return new InputDataXMLImpl( factory.createContent(fragment.getChildNodes(), null), partOf, referringReferenceNumber, hashAlg); } catch (Exception e) { // not successful -> fall through to the Base64Content } } // insert canonicalized NodeList as binary content try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); for(int i = 0; i < nodes.getLength(); i++) { baos.write(DOMUtils.nodeToByteArray(nodes.item(i))); } baos.close(); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); OctetStreamData inputData = new OctetStreamData(bais); CanonicalizationMethodImpl canonicalizationMethodImpl = new CanonicalizationMethodImpl( CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS, new ExcC14NParameterSpec()); OctetStreamData data = (OctetStreamData)canonicalizationMethodImpl.transform(inputData, null); bais.close(); //CanonicalizationAlgorithm c14n = //new CanonicalizationAlgorithmImplExclusiveCanonicalXMLWithComments(); InputStream is = data.getOctetStream(); //c14n.setInput(nodes); //is = c14n.canonicalize(); return new InputDataBinaryImpl( factory.createContent(is, null), partOf, referringReferenceNumber, hashAlg); } catch (Exception e) { throw new MOAApplicationException("2200", null); } } } /** * Build the failed references. * * Failed references are references for which the isHashValid() * method returns false. * * @param refInfos A List containing the * ReferenceInfo objects to be checked. * @return The indexes of the failed references. */ private int[] buildFailedReferences(List refInfos) { List failedReferencesList = new ArrayList(); int i; // find out the failed references for (i = 0; i < refInfos.size(); i++) { ReferenceInfo refInfo = (ReferenceInfo) refInfos.get(i); try { if (refInfo.isHashCalculated() && !refInfo.isHashValid()) { failedReferencesList.add(new Integer(i + 1)); } } catch (HashUnavailableException e) { // nothing to do here because we called refInfo.isHashCalculated first } } // convert to an int array if (failedReferencesList.isEmpty()) { return null; } else { int[] failedReferences = CollectionUtils.toIntArray(failedReferencesList); return failedReferences; } } }