/* ==================================================================== | |
Licensed to the Apache Software Foundation (ASF) under one or more | |
contributor license agreements. See the NOTICE file distributed with | |
this work for additional information regarding copyright ownership. | |
The ASF licenses this file to You under the Apache License, Version 2.0 | |
(the "License"); you may not use this file except in compliance with | |
the License. You may obtain a copy of the License at | |
http://www.apache.org/licenses/LICENSE-2.0 | |
Unless required by applicable law or agreed to in writing, software | |
distributed under the License is distributed on an "AS IS" BASIS, | |
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
See the License for the specific language governing permissions and | |
limitations under the License. | |
==================================================================== */ | |
/* ==================================================================== | |
This product contains an ASLv2 licensed version of the OOXML signer | |
package from the eID Applet project | |
http://code.google.com/p/eid-applet/source/browse/trunk/README.txt | |
Copyright (C) 2008-2014 FedICT. | |
================================================================= */ | |
package org.apache.poi.poifs.crypt.dsig.facets; | |
import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS; | |
import static org.apache.poi.poifs.crypt.dsig.facets.XAdESSignatureFacet.insertXChild; | |
import java.io.ByteArrayInputStream; | |
import java.io.ByteArrayOutputStream; | |
import java.io.IOException; | |
import java.math.BigInteger; | |
import java.security.cert.CRLException; | |
import java.security.cert.CertificateEncodingException; | |
import java.security.cert.CertificateException; | |
import java.security.cert.CertificateFactory; | |
import java.security.cert.X509CRL; | |
import java.security.cert.X509Certificate; | |
import java.util.ArrayList; | |
import java.util.Calendar; | |
import java.util.Collections; | |
import java.util.List; | |
import java.util.Locale; | |
import java.util.TimeZone; | |
import java.util.UUID; | |
import javax.xml.crypto.MarshalException; | |
import org.apache.poi.poifs.crypt.dsig.services.RevocationData; | |
import org.apache.poi.util.POILogFactory; | |
import org.apache.poi.util.POILogger; | |
import org.apache.xml.security.c14n.Canonicalizer; | |
import org.apache.xmlbeans.XmlException; | |
import org.bouncycastle.asn1.ASN1InputStream; | |
import org.bouncycastle.asn1.ASN1Integer; | |
import org.bouncycastle.asn1.ASN1OctetString; | |
import org.bouncycastle.asn1.DERTaggedObject; | |
import org.bouncycastle.asn1.ocsp.ResponderID; | |
import org.bouncycastle.asn1.x500.X500Name; | |
import org.bouncycastle.asn1.x509.Extension; | |
import org.bouncycastle.cert.ocsp.BasicOCSPResp; | |
import org.bouncycastle.cert.ocsp.OCSPResp; | |
import org.bouncycastle.cert.ocsp.RespID; | |
import org.etsi.uri.x01903.v13.CRLIdentifierType; | |
import org.etsi.uri.x01903.v13.CRLRefType; | |
import org.etsi.uri.x01903.v13.CRLRefsType; | |
import org.etsi.uri.x01903.v13.CRLValuesType; | |
import org.etsi.uri.x01903.v13.CertIDListType; | |
import org.etsi.uri.x01903.v13.CertIDType; | |
import org.etsi.uri.x01903.v13.CertificateValuesType; | |
import org.etsi.uri.x01903.v13.CompleteCertificateRefsType; | |
import org.etsi.uri.x01903.v13.CompleteRevocationRefsType; | |
import org.etsi.uri.x01903.v13.DigestAlgAndValueType; | |
import org.etsi.uri.x01903.v13.EncapsulatedPKIDataType; | |
import org.etsi.uri.x01903.v13.OCSPIdentifierType; | |
import org.etsi.uri.x01903.v13.OCSPRefType; | |
import org.etsi.uri.x01903.v13.OCSPRefsType; | |
import org.etsi.uri.x01903.v13.OCSPValuesType; | |
import org.etsi.uri.x01903.v13.QualifyingPropertiesDocument; | |
import org.etsi.uri.x01903.v13.QualifyingPropertiesType; | |
import org.etsi.uri.x01903.v13.ResponderIDType; | |
import org.etsi.uri.x01903.v13.RevocationValuesType; | |
import org.etsi.uri.x01903.v13.UnsignedPropertiesType; | |
import org.etsi.uri.x01903.v13.UnsignedSignaturePropertiesType; | |
import org.etsi.uri.x01903.v13.XAdESTimeStampType; | |
import org.etsi.uri.x01903.v14.ValidationDataType; | |
import org.w3.x2000.x09.xmldsig.CanonicalizationMethodType; | |
import org.w3c.dom.Document; | |
import org.w3c.dom.Node; | |
import org.w3c.dom.NodeList; | |
/** | |
* XAdES-X-L v1.4.1 signature facet. This signature facet implementation will | |
* upgrade a given XAdES-BES/EPES signature to XAdES-X-L. | |
* | |
* We don't inherit from XAdESSignatureFacet as we also want to be able to use | |
* this facet out of the context of a signature creation. This signature facet | |
* assumes that the signature is already XAdES-BES/EPES compliant. | |
* | |
* This implementation has been tested against an implementation that | |
* participated multiple ETSI XAdES plugtests. | |
* | |
* @author Frank Cornelis | |
* @see XAdESSignatureFacet | |
*/ | |
public class XAdESXLSignatureFacet extends SignatureFacet { | |
private static final POILogger LOG = POILogFactory.getLogger(XAdESXLSignatureFacet.class); | |
private final CertificateFactory certificateFactory; | |
public XAdESXLSignatureFacet() { | |
try { | |
this.certificateFactory = CertificateFactory.getInstance("X.509"); | |
} catch (CertificateException e) { | |
throw new RuntimeException("X509 JCA error: " + e.getMessage(), e); | |
} | |
} | |
@Override | |
public void postSign(Document document) throws MarshalException { | |
LOG.log(POILogger.DEBUG, "XAdES-X-L post sign phase"); | |
QualifyingPropertiesDocument qualDoc = null; | |
QualifyingPropertiesType qualProps = null; | |
// check for XAdES-BES | |
NodeList qualNl = document.getElementsByTagNameNS(XADES_132_NS, "QualifyingProperties"); | |
if (qualNl.getLength() == 1) { | |
try { | |
qualDoc = QualifyingPropertiesDocument.Factory.parse(qualNl.item(0), DEFAULT_XML_OPTIONS); | |
} catch (XmlException e) { | |
throw new MarshalException(e); | |
} | |
qualProps = qualDoc.getQualifyingProperties(); | |
} else { | |
throw new MarshalException("no XAdES-BES extension present"); | |
} | |
// create basic XML container structure | |
UnsignedPropertiesType unsignedProps = qualProps.getUnsignedProperties(); | |
if (unsignedProps == null) { | |
unsignedProps = qualProps.addNewUnsignedProperties(); | |
} | |
UnsignedSignaturePropertiesType unsignedSigProps = unsignedProps.getUnsignedSignatureProperties(); | |
if (unsignedSigProps == null) { | |
unsignedSigProps = unsignedProps.addNewUnsignedSignatureProperties(); | |
} | |
// create the XAdES-T time-stamp | |
NodeList nlSigVal = document.getElementsByTagNameNS(XML_DIGSIG_NS, "SignatureValue"); | |
if (nlSigVal.getLength() != 1) { | |
throw new IllegalArgumentException("SignatureValue is not set."); | |
} | |
RevocationData tsaRevocationDataXadesT = new RevocationData(); | |
LOG.log(POILogger.DEBUG, "creating XAdES-T time-stamp"); | |
XAdESTimeStampType signatureTimeStamp = createXAdESTimeStamp | |
(Collections.singletonList(nlSigVal.item(0)), tsaRevocationDataXadesT); | |
// marshal the XAdES-T extension | |
unsignedSigProps.addNewSignatureTimeStamp().set(signatureTimeStamp); | |
// xadesv141::TimeStampValidationData | |
if (tsaRevocationDataXadesT.hasRevocationDataEntries()) { | |
ValidationDataType validationData = createValidationData(tsaRevocationDataXadesT); | |
insertXChild(unsignedSigProps, validationData); | |
} | |
if (signatureConfig.getRevocationDataService() == null) { | |
/* | |
* Without revocation data service we cannot construct the XAdES-C | |
* extension. | |
*/ | |
return; | |
} | |
// XAdES-C: complete certificate refs | |
CompleteCertificateRefsType completeCertificateRefs = | |
unsignedSigProps.addNewCompleteCertificateRefs(); | |
CertIDListType certIdList = completeCertificateRefs.addNewCertRefs(); | |
/* | |
* We skip the signing certificate itself according to section | |
* 4.4.3.2 of the XAdES 1.4.1 specification. | |
*/ | |
List<X509Certificate> certChain = signatureConfig.getSigningCertificateChain(); | |
int chainSize = certChain.size(); | |
if (chainSize > 1) { | |
for (X509Certificate cert : certChain.subList(1, chainSize)) { | |
CertIDType certId = certIdList.addNewCert(); | |
XAdESSignatureFacet.setCertID(certId, signatureConfig, false, cert); | |
} | |
} | |
// XAdES-C: complete revocation refs | |
CompleteRevocationRefsType completeRevocationRefs = | |
unsignedSigProps.addNewCompleteRevocationRefs(); | |
RevocationData revocationData = signatureConfig.getRevocationDataService() | |
.getRevocationData(certChain); | |
if (revocationData.hasCRLs()) { | |
CRLRefsType crlRefs = completeRevocationRefs.addNewCRLRefs(); | |
completeRevocationRefs.setCRLRefs(crlRefs); | |
for (byte[] encodedCrl : revocationData.getCRLs()) { | |
CRLRefType crlRef = crlRefs.addNewCRLRef(); | |
X509CRL crl; | |
try { | |
crl = (X509CRL) this.certificateFactory | |
.generateCRL(new ByteArrayInputStream(encodedCrl)); | |
} catch (CRLException e) { | |
throw new RuntimeException("CRL parse error: " | |
+ e.getMessage(), e); | |
} | |
CRLIdentifierType crlIdentifier = crlRef.addNewCRLIdentifier(); | |
String issuerName = crl.getIssuerDN().getName().replace(",", ", "); | |
crlIdentifier.setIssuer(issuerName); | |
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("Z"), Locale.ROOT); | |
cal.setTime(crl.getThisUpdate()); | |
crlIdentifier.setIssueTime(cal); | |
crlIdentifier.setNumber(getCrlNumber(crl)); | |
DigestAlgAndValueType digestAlgAndValue = crlRef.addNewDigestAlgAndValue(); | |
XAdESSignatureFacet.setDigestAlgAndValue(digestAlgAndValue, encodedCrl, signatureConfig.getDigestAlgo()); | |
} | |
} | |
if (revocationData.hasOCSPs()) { | |
OCSPRefsType ocspRefs = completeRevocationRefs.addNewOCSPRefs(); | |
for (byte[] ocsp : revocationData.getOCSPs()) { | |
try { | |
OCSPRefType ocspRef = ocspRefs.addNewOCSPRef(); | |
DigestAlgAndValueType digestAlgAndValue = ocspRef.addNewDigestAlgAndValue(); | |
XAdESSignatureFacet.setDigestAlgAndValue(digestAlgAndValue, ocsp, signatureConfig.getDigestAlgo()); | |
OCSPIdentifierType ocspIdentifier = ocspRef.addNewOCSPIdentifier(); | |
OCSPResp ocspResp = new OCSPResp(ocsp); | |
BasicOCSPResp basicOcspResp = (BasicOCSPResp)ocspResp.getResponseObject(); | |
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("Z"), Locale.ROOT); | |
cal.setTime(basicOcspResp.getProducedAt()); | |
ocspIdentifier.setProducedAt(cal); | |
ResponderIDType responderId = ocspIdentifier.addNewResponderID(); | |
RespID respId = basicOcspResp.getResponderId(); | |
ResponderID ocspResponderId = respId.toASN1Primitive(); | |
DERTaggedObject derTaggedObject = (DERTaggedObject)ocspResponderId.toASN1Primitive(); | |
if (2 == derTaggedObject.getTagNo()) { | |
ASN1OctetString keyHashOctetString = (ASN1OctetString)derTaggedObject.getObject(); | |
byte key[] = keyHashOctetString.getOctets(); | |
responderId.setByKey(key); | |
} else { | |
X500Name name = X500Name.getInstance(derTaggedObject.getObject()); | |
String nameStr = name.toString(); | |
responderId.setByName(nameStr); | |
} | |
} catch (Exception e) { | |
throw new RuntimeException("OCSP decoding error: " + e.getMessage(), e); | |
} | |
} | |
} | |
// marshal XAdES-C | |
// XAdES-X Type 1 timestamp | |
List<Node> timeStampNodesXadesX1 = new ArrayList<Node>(); | |
timeStampNodesXadesX1.add(nlSigVal.item(0)); | |
timeStampNodesXadesX1.add(signatureTimeStamp.getDomNode()); | |
timeStampNodesXadesX1.add(completeCertificateRefs.getDomNode()); | |
timeStampNodesXadesX1.add(completeRevocationRefs.getDomNode()); | |
RevocationData tsaRevocationDataXadesX1 = new RevocationData(); | |
LOG.log(POILogger.DEBUG, "creating XAdES-X time-stamp"); | |
XAdESTimeStampType timeStampXadesX1 = createXAdESTimeStamp | |
(timeStampNodesXadesX1, tsaRevocationDataXadesX1); | |
if (tsaRevocationDataXadesX1.hasRevocationDataEntries()) { | |
ValidationDataType timeStampXadesX1ValidationData = createValidationData(tsaRevocationDataXadesX1); | |
insertXChild(unsignedSigProps, timeStampXadesX1ValidationData); | |
} | |
// marshal XAdES-X | |
unsignedSigProps.addNewSigAndRefsTimeStamp().set(timeStampXadesX1); | |
// XAdES-X-L | |
CertificateValuesType certificateValues = unsignedSigProps.addNewCertificateValues(); | |
for (X509Certificate certificate : certChain) { | |
EncapsulatedPKIDataType encapsulatedPKIDataType = certificateValues.addNewEncapsulatedX509Certificate(); | |
try { | |
encapsulatedPKIDataType.setByteArrayValue(certificate.getEncoded()); | |
} catch (CertificateEncodingException e) { | |
throw new RuntimeException("certificate encoding error: " + e.getMessage(), e); | |
} | |
} | |
RevocationValuesType revocationValues = unsignedSigProps.addNewRevocationValues(); | |
createRevocationValues(revocationValues, revocationData); | |
// marshal XAdES-X-L | |
Node n = document.importNode(qualProps.getDomNode(), true); | |
qualNl.item(0).getParentNode().replaceChild(n, qualNl.item(0)); | |
} | |
public static byte[] getC14nValue(List<Node> nodeList, String c14nAlgoId) { | |
ByteArrayOutputStream c14nValue = new ByteArrayOutputStream(); | |
try { | |
for (Node node : nodeList) { | |
/* | |
* Re-initialize the c14n else the namespaces will get cached | |
* and will be missing from the c14n resulting nodes. | |
*/ | |
Canonicalizer c14n = Canonicalizer.getInstance(c14nAlgoId); | |
c14nValue.write(c14n.canonicalizeSubtree(node)); | |
} | |
} catch (RuntimeException e) { | |
throw e; | |
} catch (Exception e) { | |
throw new RuntimeException("c14n error: " + e.getMessage(), e); | |
} | |
return c14nValue.toByteArray(); | |
} | |
private BigInteger getCrlNumber(X509CRL crl) { | |
byte[] crlNumberExtensionValue = crl.getExtensionValue(Extension.cRLNumber.getId()); | |
if (null == crlNumberExtensionValue) { | |
return null; | |
} | |
try { | |
ASN1InputStream asn1IS1 = null, asn1IS2 = null; | |
try { | |
asn1IS1 = new ASN1InputStream(crlNumberExtensionValue); | |
ASN1OctetString octetString = (ASN1OctetString)asn1IS1.readObject(); | |
byte[] octets = octetString.getOctets(); | |
asn1IS2 = new ASN1InputStream(octets); | |
ASN1Integer integer = (ASN1Integer)asn1IS2.readObject(); | |
return integer.getPositiveValue(); | |
} finally { | |
asn1IS2.close(); | |
asn1IS1.close(); | |
} | |
} catch (IOException e) { | |
throw new RuntimeException("I/O error: " + e.getMessage(), e); | |
} | |
} | |
private XAdESTimeStampType createXAdESTimeStamp( | |
List<Node> nodeList, | |
RevocationData revocationData) { | |
byte[] c14nSignatureValueElement = getC14nValue(nodeList, signatureConfig.getXadesCanonicalizationMethod()); | |
return createXAdESTimeStamp(c14nSignatureValueElement, revocationData); | |
} | |
private XAdESTimeStampType createXAdESTimeStamp(byte[] data, RevocationData revocationData) { | |
// create the time-stamp | |
byte[] timeStampToken; | |
try { | |
timeStampToken = signatureConfig.getTspService().timeStamp(data, revocationData); | |
} catch (Exception e) { | |
throw new RuntimeException("error while creating a time-stamp: " | |
+ e.getMessage(), e); | |
} | |
// create a XAdES time-stamp container | |
XAdESTimeStampType xadesTimeStamp = XAdESTimeStampType.Factory.newInstance(); | |
xadesTimeStamp.setId("time-stamp-" + UUID.randomUUID().toString()); | |
CanonicalizationMethodType c14nMethod = xadesTimeStamp.addNewCanonicalizationMethod(); | |
c14nMethod.setAlgorithm(signatureConfig.getXadesCanonicalizationMethod()); | |
// embed the time-stamp | |
EncapsulatedPKIDataType encapsulatedTimeStamp = xadesTimeStamp.addNewEncapsulatedTimeStamp(); | |
encapsulatedTimeStamp.setByteArrayValue(timeStampToken); | |
encapsulatedTimeStamp.setId("time-stamp-token-" + UUID.randomUUID().toString()); | |
return xadesTimeStamp; | |
} | |
private ValidationDataType createValidationData( | |
RevocationData revocationData) { | |
ValidationDataType validationData = ValidationDataType.Factory.newInstance(); | |
RevocationValuesType revocationValues = validationData.addNewRevocationValues(); | |
createRevocationValues(revocationValues, revocationData); | |
return validationData; | |
} | |
private void createRevocationValues( | |
RevocationValuesType revocationValues, RevocationData revocationData) { | |
if (revocationData.hasCRLs()) { | |
CRLValuesType crlValues = revocationValues.addNewCRLValues(); | |
for (byte[] crl : revocationData.getCRLs()) { | |
EncapsulatedPKIDataType encapsulatedCrlValue = crlValues.addNewEncapsulatedCRLValue(); | |
encapsulatedCrlValue.setByteArrayValue(crl); | |
} | |
} | |
if (revocationData.hasOCSPs()) { | |
OCSPValuesType ocspValues = revocationValues.addNewOCSPValues(); | |
for (byte[] ocsp : revocationData.getOCSPs()) { | |
EncapsulatedPKIDataType encapsulatedOcspValue = ocspValues.addNewEncapsulatedOCSPValue(); | |
encapsulatedOcspValue.setByteArrayValue(ocsp); | |
} | |
} | |
} | |
} |