/*
* 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.Date;
import java.util.Iterator;
import java.util.List;
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.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.
*
SPSSFactory
for creating API objects. */
private final 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 {
final CertificateValidationResult certResult =
result.getCertificateValidationResult();
List referenceDataList;
final 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
final List xMLDSIGManifests = result.getDsigManifestList();
for (iter = xMLDSIGManifests.iterator(); iter.hasNext();) {
final DsigManifest currentMF = (DsigManifest) iter.next();
final 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
final List xMLDSIGManifests = result.getDsigManifestList();
for (iter = xMLDSIGManifests.iterator(); iter.hasNext();) {
final DsigManifest currentMF = (DsigManifest) iter.next();
final 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
final SecurityLayerManifest slManifest = result.getSecurityLayerManifest();
final 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();) {
final DsigManifest dsigManifest = (DsigManifest) iter.next();
final 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 (final Object element : referenceDataList) {
final ReferenceData referenceData = (ReferenceData) element;
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 (final Object element : referenceDataList) {
final ReferenceData referenceData = (ReferenceData) element;
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) {
final BinaryDataObject binaryData = (BinaryDataObject) dataObject;
return new InputDataBinaryImpl(
factory.createContent(binaryData.getInputStream(), null),
partOf,
referringReferenceNumber,
hashAlg);
} else if (dataObject instanceof XMLDataObject) {
final XMLDataObject xmlData = (XMLDataObject) dataObject;
final 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
final XMLNodeListDataObject nodeData = (XMLNodeListDataObject) dataObject;
final NodeList nodes = nodeData.getNodeList();
if (DOMUtils.checkAttributeParentsInNodeList(nodes)) {
// insert as XMLContent
try {
final DocumentFragment fragment = DOMUtils.nodeList2DocumentFragment(nodes);
return new InputDataXMLImpl(
factory.createContent(fragment.getChildNodes(), null),
partOf,
referringReferenceNumber,
hashAlg);
} catch (final Exception e) {
// not successful -> fall through to the Base64Content
}
}
// insert canonicalized NodeList as binary content
try {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (int i = 0; i < nodes.getLength(); i++) {
baos.write(DOMUtils.nodeToByteArray(nodes.item(i)));
}
baos.close();
final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
final OctetStreamData inputData = new OctetStreamData(bais);
final CanonicalizationMethodImpl canonicalizationMethodImpl = new CanonicalizationMethodImpl(
CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS, new ExcC14NParameterSpec());
final OctetStreamData data = (OctetStreamData) canonicalizationMethodImpl.transform(inputData, null);
bais.close();
// CanonicalizationAlgorithm c14n =
// new CanonicalizationAlgorithmImplExclusiveCanonicalXMLWithComments();
final InputStream is = data.getOctetStream();
// c14n.setInput(nodes);
// is = c14n.canonicalize();
return new InputDataBinaryImpl(
factory.createContent(is, null),
partOf,
referringReferenceNumber,
hashAlg);
} catch (final 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) {
final List failedReferencesList = new ArrayList();
int i;
// find out the failed references
for (i = 0; i < refInfos.size(); i++) {
final ReferenceInfo refInfo = (ReferenceInfo) refInfos.get(i);
try {
if (refInfo.isHashCalculated() && !refInfo.isHashValid()) {
failedReferencesList.add(new Integer(i + 1));
}
} catch (final HashUnavailableException e) {
// nothing to do here because we called refInfo.isHashCalculated first
}
}
// convert to an int array
if (failedReferencesList.isEmpty()) {
return null;
} else {
final int[] failedReferences = CollectionUtils.toIntArray(failedReferencesList);
return failedReferences;
}
}
}