mirror of https://github.com/apache/poi.git
#65908 - XAdES-XL modifications due to specification check errors
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1898287 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
8c6172c772
commit
a881c381db
|
@ -20,22 +20,33 @@ package org.apache.poi.poifs.crypt.dsig;
|
|||
import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.OO_DIGSIG_NS;
|
||||
import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.XADES_132_NS;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.Provider;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.xml.crypto.URIDereferencer;
|
||||
import javax.xml.crypto.dsig.CanonicalizationMethod;
|
||||
|
@ -58,8 +69,10 @@ import org.apache.poi.poifs.crypt.dsig.facets.XAdESSignatureFacet;
|
|||
import org.apache.poi.poifs.crypt.dsig.services.RevocationDataService;
|
||||
import org.apache.poi.poifs.crypt.dsig.services.SignaturePolicyService;
|
||||
import org.apache.poi.poifs.crypt.dsig.services.TSPTimeStampService;
|
||||
import org.apache.poi.poifs.crypt.dsig.services.TimeStampHttpClient;
|
||||
import org.apache.poi.poifs.crypt.dsig.services.TimeStampService;
|
||||
import org.apache.poi.poifs.crypt.dsig.services.TimeStampServiceValidator;
|
||||
import org.apache.poi.poifs.crypt.dsig.services.TimeStampSimpleHttpClient;
|
||||
import org.apache.poi.util.Internal;
|
||||
import org.apache.poi.util.LocaleUtil;
|
||||
import org.apache.poi.util.Removal;
|
||||
|
@ -73,6 +86,29 @@ import org.apache.xml.security.signature.XMLSignature;
|
|||
*/
|
||||
@SuppressWarnings({"unused","WeakerAccess"})
|
||||
public class SignatureConfig {
|
||||
public static class CRLEntry {
|
||||
private final String crlURL;
|
||||
private final String certCN;
|
||||
private final byte[] crlBytes;
|
||||
|
||||
public CRLEntry(String crlURL, String certCN, byte[] crlBytes) {
|
||||
this.crlURL = crlURL;
|
||||
this.certCN = certCN;
|
||||
this.crlBytes = crlBytes;
|
||||
}
|
||||
|
||||
public String getCrlURL() {
|
||||
return crlURL;
|
||||
}
|
||||
|
||||
public String getCertCN() {
|
||||
return certCN;
|
||||
}
|
||||
|
||||
public byte[] getCrlBytes() {
|
||||
return crlBytes;
|
||||
}
|
||||
}
|
||||
|
||||
public static final String SIGNATURE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
||||
|
||||
|
@ -116,6 +152,9 @@ public class SignatureConfig {
|
|||
* the time-stamp service used for XAdES-T and XAdES-X.
|
||||
*/
|
||||
private TimeStampService tspService = new TSPTimeStampService();
|
||||
private TimeStampHttpClient tspHttpClient = new TimeStampSimpleHttpClient();
|
||||
|
||||
|
||||
/**
|
||||
* timestamp service provider URL
|
||||
*/
|
||||
|
@ -137,7 +176,7 @@ public class SignatureConfig {
|
|||
|
||||
/**
|
||||
* the optional revocation data service used for XAdES-C and XAdES-X-L.
|
||||
* When <code>null</code> the signature will be limited to XAdES-T only.
|
||||
* When {@code null} the signature will be limited to XAdES-T only.
|
||||
*/
|
||||
private RevocationDataService revocationDataService;
|
||||
/**
|
||||
|
@ -156,7 +195,7 @@ public class SignatureConfig {
|
|||
|
||||
/**
|
||||
* The signature Id attribute value used to create the XML signature. A
|
||||
* <code>null</code> value will trigger an automatically generated signature Id.
|
||||
* {@code null} value will trigger an automatically generated signature Id.
|
||||
*/
|
||||
private String packageSignatureId = "idPackageSignature";
|
||||
|
||||
|
@ -221,6 +260,23 @@ public class SignatureConfig {
|
|||
|
||||
private String commitmentType = "Created and approved this document";
|
||||
|
||||
/**
|
||||
* Swtich to enable/disable automatic CRL download - by default the download is with all https hostname
|
||||
* and certificate verifications disabled.
|
||||
*
|
||||
* @since POI 5.3.0
|
||||
*/
|
||||
private boolean allowCRLDownload = false;
|
||||
|
||||
/**
|
||||
* List of cached / saved CRL entries
|
||||
*/
|
||||
private final List<CRLEntry> crlEntries = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Keystore used for cached certificates
|
||||
*/
|
||||
private final KeyStore keyStore = emptyKeyStore();
|
||||
|
||||
public SignatureConfig() {
|
||||
// OOo doesn't like ds namespaces so per default prefixing is off.
|
||||
|
@ -490,7 +546,7 @@ public class SignatureConfig {
|
|||
|
||||
/**
|
||||
* @param packageSignatureId The signature Id attribute value used to create the XML signature.
|
||||
* A <code>null</code> value will trigger an automatically generated signature Id.
|
||||
* A {@code null} value will trigger an automatically generated signature Id.
|
||||
*/
|
||||
public void setPackageSignatureId(String packageSignatureId) {
|
||||
this.packageSignatureId = nvl(packageSignatureId,"xmldsig-"+UUID.randomUUID());
|
||||
|
@ -536,7 +592,7 @@ public class SignatureConfig {
|
|||
|
||||
/**
|
||||
* @param tspDigestAlgo the algorithm to be used for the timestamp entry.
|
||||
* if <code>null</code>, the hash algorithm of the main entry
|
||||
* if {@code null}, the hash algorithm of the main entry
|
||||
*/
|
||||
public void setTspDigestAlgo(HashAlgorithm tspDigestAlgo) {
|
||||
this.tspDigestAlgo = tspDigestAlgo;
|
||||
|
@ -572,6 +628,24 @@ public class SignatureConfig {
|
|||
this.tspService = tspService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the http client used for timestamp server connections
|
||||
*
|
||||
* @since POI 5.3.0
|
||||
*/
|
||||
public TimeStampHttpClient getTspHttpClient() {
|
||||
return tspHttpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tspHttpClient the http client used for timestamp server connections
|
||||
*
|
||||
* @since POI 5.3.0
|
||||
*/
|
||||
public void setTspHttpClient(TimeStampHttpClient tspHttpClient) {
|
||||
this.tspHttpClient = tspHttpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the user id for the timestamp service - currently only basic authorization is supported
|
||||
*/
|
||||
|
@ -616,7 +690,7 @@ public class SignatureConfig {
|
|||
|
||||
/**
|
||||
* @return the optional revocation data service used for XAdES-C and XAdES-X-L.
|
||||
* When <code>null</code> the signature will be limited to XAdES-T only.
|
||||
* When {@code null} the signature will be limited to XAdES-T only.
|
||||
*/
|
||||
public RevocationDataService getRevocationDataService() {
|
||||
return revocationDataService;
|
||||
|
@ -624,7 +698,7 @@ public class SignatureConfig {
|
|||
|
||||
/**
|
||||
* @param revocationDataService the optional revocation data service used for XAdES-C and XAdES-X-L.
|
||||
* When <code>null</code> the signature will be limited to XAdES-T only.
|
||||
* When {@code null} the signature will be limited to XAdES-T only.
|
||||
*/
|
||||
public void setRevocationDataService(RevocationDataService revocationDataService) {
|
||||
this.revocationDataService = revocationDataService;
|
||||
|
@ -639,7 +713,7 @@ public class SignatureConfig {
|
|||
|
||||
/**
|
||||
* @param xadesDigestAlgo hash algorithm used for XAdES.
|
||||
* When <code>null</code>, defaults to {@link #getDigestAlgo()}
|
||||
* When {@code null}, defaults to {@link #getDigestAlgo()}
|
||||
*/
|
||||
public void setXadesDigestAlgo(HashAlgorithm xadesDigestAlgo) {
|
||||
this.xadesDigestAlgo = xadesDigestAlgo;
|
||||
|
@ -647,7 +721,7 @@ public class SignatureConfig {
|
|||
|
||||
/**
|
||||
* @param xadesDigestAlgo hash algorithm used for XAdES.
|
||||
* When <code>null</code>, defaults to {@link #getDigestAlgo()}
|
||||
* When {@code null}, defaults to {@link #getDigestAlgo()}
|
||||
*
|
||||
* @since POI 4.0.0
|
||||
*/
|
||||
|
@ -671,7 +745,7 @@ public class SignatureConfig {
|
|||
|
||||
/**
|
||||
* @return the asn.1 object id for the tsp request policy.
|
||||
* Defaults to <code>1.3.6.1.4.1.13762.3</code>
|
||||
* Defaults to {@code 1.3.6.1.4.1.13762.3}
|
||||
*/
|
||||
public String getTspRequestPolicy() {
|
||||
return tspRequestPolicy;
|
||||
|
@ -729,15 +803,15 @@ public class SignatureConfig {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return the xades role element. If <code>null</code> the claimed role element is omitted.
|
||||
* Defaults to <code>null</code>
|
||||
* @return the xades role element. If {@code null} the claimed role element is omitted.
|
||||
* Defaults to {@code null}
|
||||
*/
|
||||
public String getXadesRole() {
|
||||
return xadesRole;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param xadesRole the xades role element. If <code>null</code> the claimed role element is omitted.
|
||||
* @param xadesRole the xades role element. If {@code null} the claimed role element is omitted.
|
||||
*/
|
||||
public void setXadesRole(String xadesRole) {
|
||||
this.xadesRole = xadesRole;
|
||||
|
@ -745,7 +819,7 @@ public class SignatureConfig {
|
|||
|
||||
/**
|
||||
* @return the Id for the XAdES SignedProperties element.
|
||||
* Defaults to <code>idSignedProperties</code>
|
||||
* Defaults to {@code idSignedProperties}
|
||||
*/
|
||||
public String getXadesSignatureId() {
|
||||
return nvl(xadesSignatureId, "idSignedProperties");
|
||||
|
@ -753,7 +827,7 @@ public class SignatureConfig {
|
|||
|
||||
/**
|
||||
* @param xadesSignatureId the Id for the XAdES SignedProperties element.
|
||||
* When <code>null</code> defaults to <code>idSignedProperties</code>
|
||||
* When {@code null} defaults to {@code idSignedProperties}
|
||||
*/
|
||||
public void setXadesSignatureId(String xadesSignatureId) {
|
||||
this.xadesSignatureId = xadesSignatureId;
|
||||
|
@ -761,7 +835,7 @@ public class SignatureConfig {
|
|||
|
||||
/**
|
||||
* @return when true, include the policy-implied block.
|
||||
* Defaults to <code>true</code>
|
||||
* Defaults to {@code true}
|
||||
*/
|
||||
public boolean isXadesSignaturePolicyImplied() {
|
||||
return xadesSignaturePolicyImplied;
|
||||
|
@ -1027,7 +1101,7 @@ public class SignatureConfig {
|
|||
|
||||
/**
|
||||
* @return the cannonicalization method for XAdES-XL signing.
|
||||
* Defaults to <code>EXCLUSIVE</code>
|
||||
* Defaults to {@code EXCLUSIVE}
|
||||
* @see <a href="http://docs.oracle.com/javase/7/docs/api/javax/xml/crypto/dsig/CanonicalizationMethod.html">javax.xml.crypto.dsig.CanonicalizationMethod</a>
|
||||
*/
|
||||
public String getXadesCanonicalizationMethod() {
|
||||
|
@ -1136,4 +1210,96 @@ public class SignatureConfig {
|
|||
public void setCommitmentType(String commitmentType) {
|
||||
this.commitmentType = commitmentType;
|
||||
}
|
||||
|
||||
|
||||
public CRLEntry addCRL(String crlURL, String certCN, byte[] crlBytes) {
|
||||
CRLEntry ce = new CRLEntry(crlURL, certCN, crlBytes);
|
||||
crlEntries.add(ce);
|
||||
return ce;
|
||||
}
|
||||
|
||||
public List<CRLEntry> getCrlEntries() {
|
||||
return crlEntries;
|
||||
}
|
||||
|
||||
public boolean isAllowCRLDownload() {
|
||||
return allowCRLDownload;
|
||||
}
|
||||
|
||||
public void setAllowCRLDownload(boolean allowCRLDownload) {
|
||||
this.allowCRLDownload = allowCRLDownload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return keystore with cached certificates
|
||||
*/
|
||||
public KeyStore getKeyStore() {
|
||||
return keyStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add certificate into keystore (cache) for further certificate chain lookups
|
||||
* @param alias the alias, or null if alias is taken from common name attribute of certificate
|
||||
* @param x509 the x509 certificate
|
||||
*/
|
||||
public void addCachedCertificate(String alias, X509Certificate x509) throws KeyStoreException {
|
||||
String lAlias = alias;
|
||||
if (lAlias == null) {
|
||||
lAlias = x509.getSubjectX500Principal().getName();
|
||||
}
|
||||
if (keyStore != null) {
|
||||
synchronized (keyStore) {
|
||||
keyStore.setCertificateEntry(lAlias, x509);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addCachedCertificate(String alias, byte[] x509Bytes) throws KeyStoreException, CertificateException {
|
||||
CertificateFactory certFact = CertificateFactory.getInstance("X.509");
|
||||
X509Certificate x509 = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(x509Bytes));
|
||||
addCachedCertificate(null, x509);
|
||||
}
|
||||
|
||||
public X509Certificate getCachedCertificateByPrinicipal(String principalName) {
|
||||
if (keyStore == null) {
|
||||
return null;
|
||||
}
|
||||
// TODO: add synchronized
|
||||
try {
|
||||
for (String a : Collections.list(keyStore.aliases())) {
|
||||
Certificate[] chain = keyStore.getCertificateChain(a);
|
||||
if (chain == null) {
|
||||
Certificate cert = keyStore.getCertificate(a);
|
||||
if (cert == null) {
|
||||
continue;
|
||||
}
|
||||
chain = new Certificate[]{cert};
|
||||
}
|
||||
Optional<X509Certificate> found = Stream.of(chain)
|
||||
.map(X509Certificate.class::cast)
|
||||
.filter(c -> principalName.equalsIgnoreCase(c.getSubjectX500Principal().getName()))
|
||||
.findFirst();
|
||||
if (found.isPresent()) {
|
||||
return found.get();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (KeyStoreException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static KeyStore emptyKeyStore() {
|
||||
try {
|
||||
KeyStore ks = KeyStore.getInstance("PKCS12");
|
||||
ks.load(null, null);
|
||||
return ks;
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
LOG.atError().withThrowable(e).log("unable to create PKCS #12 keystore - XAdES certificate chain lookups disabled");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -17,16 +17,21 @@
|
|||
|
||||
package org.apache.poi.poifs.crypt.dsig;
|
||||
|
||||
import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.MS_DIGSIG_NS;
|
||||
import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.OO_DIGSIG_NS;
|
||||
import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.XML_DIGSIG_NS;
|
||||
import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.XML_NS;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
|
@ -46,6 +51,8 @@ public class SignatureMarshalDefaultListener implements SignatureMarshalListener
|
|||
private static final String OBJECT_TAG = "Object";
|
||||
private static final Set<String> IGNORE_NS = new HashSet<>(Arrays.asList(null, XML_NS, XML_DIGSIG_NS));
|
||||
|
||||
private static final List<String> DIRECT_NS = Arrays.asList(OO_DIGSIG_NS, MS_DIGSIG_NS);
|
||||
|
||||
@Override
|
||||
public void handleElement(SignatureInfo signatureInfo, Document doc, EventTarget target, EventListener parentListener) {
|
||||
// see POI #63712 : because of Santuario change r1853805 in XmlSec 2.1.3,
|
||||
|
@ -58,7 +65,7 @@ public class SignatureMarshalDefaultListener implements SignatureMarshalListener
|
|||
forEachElement(doc.getElementsByTagName(OBJECT_TAG), (o) -> {
|
||||
forEachElement(o.getChildNodes(), (c) -> {
|
||||
getAllNamespaces(traversal, c, prefixCfg, prefixUsed);
|
||||
prefixUsed.forEach((ns, prefix) -> c.setAttributeNS(XML_NS, "xmlns:"+prefix, ns));
|
||||
prefixUsed.forEach((ns, prefix) -> setXmlns(c, prefix, ns));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -93,9 +100,22 @@ public class SignatureMarshalDefaultListener implements SignatureMarshalListener
|
|||
private void setPrefix(Node node, Map<String,String> prefixCfg, Map<String,String> prefixUsed) {
|
||||
String ns = node.getNamespaceURI();
|
||||
String prefix = prefixCfg.get(ns);
|
||||
if (!IGNORE_NS.contains(prefix)) {
|
||||
if (IGNORE_NS.contains(ns)) {
|
||||
return;
|
||||
}
|
||||
if (prefix != null) {
|
||||
node.setPrefix(prefix);
|
||||
}
|
||||
if (DIRECT_NS.contains(ns)) {
|
||||
setXmlns(node, prefix, ns);
|
||||
} else {
|
||||
prefixUsed.put(ns, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setXmlns(Node node, String prefix, String ns) {
|
||||
if (node instanceof Element && !ns.equals(node.getParentNode().getNamespaceURI())) {
|
||||
((Element)node).setAttributeNS(XML_NS, XMLConstants.XMLNS_ATTRIBUTE + (prefix == null ? "" : ":"+prefix), ns);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@ import java.util.Set;
|
|||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.crypto.URIReference;
|
||||
import javax.xml.crypto.XMLStructure;
|
||||
import javax.xml.crypto.dom.DOMStructure;
|
||||
|
@ -261,7 +260,7 @@ public class OOXMLSignatureFacet implements SignatureFacet {
|
|||
|
||||
SignatureInfoV1Document sigV1 = createSignatureInfoV1(signatureInfo);
|
||||
Element n = (Element)document.importNode(sigV1.getSignatureInfoV1().getDomNode(), true);
|
||||
n.setAttributeNS(XML_NS, XMLConstants.XMLNS_ATTRIBUTE, MS_DIGSIG_NS);
|
||||
// n.setAttributeNS(XML_NS, XMLConstants.XMLNS_ATTRIBUTE, MS_DIGSIG_NS);
|
||||
|
||||
List<XMLStructure> signatureInfoContent = new ArrayList<>();
|
||||
signatureInfoContent.add(new DOMStructure(n));
|
||||
|
|
|
@ -36,13 +36,11 @@ 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.Arrays;
|
||||
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;
|
||||
|
||||
|
@ -67,9 +65,11 @@ import org.bouncycastle.cert.ocsp.BasicOCSPResp;
|
|||
import org.bouncycastle.cert.ocsp.OCSPResp;
|
||||
import org.bouncycastle.cert.ocsp.RespID;
|
||||
import org.etsi.uri.x01903.v13.*;
|
||||
import org.etsi.uri.x01903.v14.TimeStampValidationDataDocument;
|
||||
import org.etsi.uri.x01903.v14.ValidationDataType;
|
||||
import org.w3.x2000.x09.xmldsig.CanonicalizationMethodType;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
|
@ -114,12 +114,38 @@ public class XAdESXLSignatureFacet implements SignatureFacet {
|
|||
UnsignedSignaturePropertiesType unsignedSigProps =
|
||||
ofNullable(unsignedProps.getUnsignedSignatureProperties()).orElseGet(unsignedProps::addNewUnsignedSignatureProperties);
|
||||
|
||||
// create the XAdES-T time-stamp
|
||||
NodeList nlSigVal = document.getElementsByTagNameNS(XML_DIGSIG_NS, "SignatureValue");
|
||||
XAdESTimeStampType signatureTimeStamp = addTimestamp(nlSigVal, signatureInfo, unsignedSigProps);
|
||||
final NodeList nlSigVal = document.getElementsByTagNameNS(XML_DIGSIG_NS, "SignatureValue");
|
||||
if (nlSigVal.getLength() != 1) {
|
||||
throw new IllegalArgumentException("SignatureValue is not set.");
|
||||
}
|
||||
final Element sigVal = (Element)nlSigVal.item(0);
|
||||
|
||||
|
||||
// Without revocation data service we cannot construct the XAdES-C extension.
|
||||
RevocationDataService revDataSvc = signatureConfig.getRevocationDataService();
|
||||
if (revDataSvc != null) {
|
||||
// XAdES-X-L
|
||||
addCertificateValues(unsignedSigProps, signatureConfig);
|
||||
}
|
||||
|
||||
LOG.atDebug().log("creating XAdES-T time-stamp");
|
||||
|
||||
// xadesv141::TimeStampValidationData
|
||||
XAdESTimeStampType signatureTimeStamp;
|
||||
try {
|
||||
final RevocationData tsaRevocationDataXadesT = new RevocationData();
|
||||
signatureTimeStamp = createXAdESTimeStamp(signatureInfo, tsaRevocationDataXadesT, sigVal);
|
||||
unsignedSigProps.addNewSignatureTimeStamp().set(signatureTimeStamp);
|
||||
|
||||
if (tsaRevocationDataXadesT.hasRevocationDataEntries()) {
|
||||
TimeStampValidationDataDocument validationData = createValidationData(tsaRevocationDataXadesT);
|
||||
insertXChild(unsignedSigProps, validationData);
|
||||
}
|
||||
} catch (CertificateEncodingException e) {
|
||||
throw new MarshalException("unable to create XAdES signatrue", e);
|
||||
}
|
||||
|
||||
|
||||
if (revDataSvc != null) {
|
||||
// XAdES-C: complete certificate refs
|
||||
CompleteCertificateRefsType completeCertificateRefs = completeCertificateRefs(unsignedSigProps, signatureConfig);
|
||||
|
@ -130,19 +156,30 @@ public class XAdESXLSignatureFacet implements SignatureFacet {
|
|||
addRevocationCRL(completeRevocationRefs, signatureConfig, revocationData);
|
||||
addRevocationOCSP(completeRevocationRefs, signatureConfig, revocationData);
|
||||
|
||||
// XAdES-X Type 1 timestamp
|
||||
addTimestampX(unsignedSigProps, signatureInfo, nlSigVal, signatureTimeStamp, completeCertificateRefs, completeRevocationRefs);
|
||||
|
||||
// XAdES-X-L
|
||||
addCertificateValues(unsignedSigProps, signatureConfig);
|
||||
|
||||
RevocationValuesType revocationValues = unsignedSigProps.addNewRevocationValues();
|
||||
createRevocationValues(revocationValues, revocationData);
|
||||
|
||||
// XAdES-X Type 1 timestamp
|
||||
LOG.atDebug().log("creating XAdES-X time-stamp");
|
||||
revocationData = new RevocationData();
|
||||
XAdESTimeStampType timeStampXadesX1 = createXAdESTimeStamp(signatureInfo, revocationData,
|
||||
sigVal, signatureTimeStamp.getDomNode(), completeCertificateRefs.getDomNode(), completeRevocationRefs.getDomNode());
|
||||
|
||||
// marshal XAdES-X
|
||||
unsignedSigProps.addNewSigAndRefsTimeStamp().set(timeStampXadesX1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// marshal XAdES-X-L
|
||||
Node n = document.importNode(qualProps.getDomNode(), true);
|
||||
qualNl.item(0).getParentNode().replaceChild(n, qualNl.item(0));
|
||||
Element n = (Element)document.importNode(qualProps.getDomNode(), true);
|
||||
NodeList nl = n.getElementsByTagName("TimeStampValidationData");
|
||||
for (int i=0; i<nl.getLength(); i++) {
|
||||
((Element)nl.item(i)).setAttributeNS(XML_NS, "xmlns", "http://uri.etsi.org/01903/v1.4.1#");
|
||||
}
|
||||
Node qualNL0 = qualNl.item(0);
|
||||
qualNL0.getParentNode().replaceChild(n, qualNL0);
|
||||
}
|
||||
|
||||
private QualifyingPropertiesType getQualProps(NodeList qualNl) throws MarshalException {
|
||||
|
@ -160,28 +197,6 @@ public class XAdESXLSignatureFacet implements SignatureFacet {
|
|||
}
|
||||
}
|
||||
|
||||
private XAdESTimeStampType addTimestamp(NodeList nlSigVal, SignatureInfo signatureInfo, UnsignedSignaturePropertiesType unsignedSigProps) {
|
||||
if (nlSigVal.getLength() != 1) {
|
||||
throw new IllegalArgumentException("SignatureValue is not set.");
|
||||
}
|
||||
|
||||
RevocationData tsaRevocationDataXadesT = new RevocationData();
|
||||
LOG.atDebug().log("creating XAdES-T time-stamp");
|
||||
XAdESTimeStampType signatureTimeStamp = createXAdESTimeStamp
|
||||
(signatureInfo, 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);
|
||||
}
|
||||
|
||||
return signatureTimeStamp;
|
||||
}
|
||||
|
||||
private CompleteCertificateRefsType completeCertificateRefs(UnsignedSignaturePropertiesType unsignedSigProps, SignatureConfig signatureConfig) {
|
||||
CompleteCertificateRefsType completeCertificateRefs = unsignedSigProps.addNewCompleteCertificateRefs();
|
||||
|
||||
|
@ -253,11 +268,11 @@ public class XAdESXLSignatureFacet implements SignatureFacet {
|
|||
ResponderID ocspResponderId = respId.toASN1Primitive();
|
||||
DERTaggedObject derTaggedObject = (DERTaggedObject)ocspResponderId.toASN1Primitive();
|
||||
if (2 == derTaggedObject.getTagNo()) {
|
||||
ASN1OctetString keyHashOctetString = (ASN1OctetString)derTaggedObject.getObject();
|
||||
ASN1OctetString keyHashOctetString = (ASN1OctetString)derTaggedObject.getBaseObject();
|
||||
byte[] key = keyHashOctetString.getOctets();
|
||||
responderId.setByKey(key);
|
||||
} else {
|
||||
X500Name name = X500Name.getInstance(derTaggedObject.getObject());
|
||||
X500Name name = X500Name.getInstance(derTaggedObject.getBaseObject());
|
||||
String nameStr = name.toString();
|
||||
responderId.setByName(nameStr);
|
||||
}
|
||||
|
@ -268,38 +283,18 @@ public class XAdESXLSignatureFacet implements SignatureFacet {
|
|||
}
|
||||
}
|
||||
|
||||
private void addTimestampX(UnsignedSignaturePropertiesType unsignedSigProps, SignatureInfo signatureInfo, NodeList nlSigVal, XAdESTimeStampType signatureTimeStamp,
|
||||
CompleteCertificateRefsType completeCertificateRefs, CompleteRevocationRefsType completeRevocationRefs) {
|
||||
|
||||
List<Node> timeStampNodesXadesX1 = new ArrayList<>();
|
||||
timeStampNodesXadesX1.add(nlSigVal.item(0));
|
||||
timeStampNodesXadesX1.add(signatureTimeStamp.getDomNode());
|
||||
timeStampNodesXadesX1.add(completeCertificateRefs.getDomNode());
|
||||
timeStampNodesXadesX1.add(completeRevocationRefs.getDomNode());
|
||||
|
||||
RevocationData tsaRevocationDataXadesX1 = new RevocationData();
|
||||
LOG.atDebug().log("creating XAdES-X time-stamp");
|
||||
XAdESTimeStampType timeStampXadesX1 = createXAdESTimeStamp
|
||||
(signatureInfo, timeStampNodesXadesX1, tsaRevocationDataXadesX1);
|
||||
if (tsaRevocationDataXadesX1.hasRevocationDataEntries()) {
|
||||
ValidationDataType timeStampXadesX1ValidationData = createValidationData(tsaRevocationDataXadesX1);
|
||||
insertXChild(unsignedSigProps, timeStampXadesX1ValidationData);
|
||||
}
|
||||
|
||||
// marshal XAdES-X
|
||||
unsignedSigProps.addNewSigAndRefsTimeStamp().set(timeStampXadesX1);
|
||||
|
||||
}
|
||||
|
||||
private void addCertificateValues(UnsignedSignaturePropertiesType unsignedSigProps, SignatureConfig signatureConfig) {
|
||||
List<X509Certificate> chain = signatureConfig.getSigningCertificateChain();
|
||||
if (chain.size() < 2) {
|
||||
return;
|
||||
}
|
||||
CertificateValuesType certificateValues = unsignedSigProps.addNewCertificateValues();
|
||||
for (X509Certificate certificate : signatureConfig.getSigningCertificateChain()) {
|
||||
EncapsulatedPKIDataType encapsulatedPKIDataType = certificateValues.addNewEncapsulatedX509Certificate();
|
||||
try {
|
||||
encapsulatedPKIDataType.setByteArrayValue(certificate.getEncoded());
|
||||
} catch (CertificateEncodingException e) {
|
||||
throw new RuntimeException("certificate encoding error: " + e.getMessage(), e);
|
||||
try {
|
||||
for (X509Certificate certificate : chain.subList(1, chain.size())) {
|
||||
certificateValues.addNewEncapsulatedX509Certificate().setByteArrayValue(certificate.getEncoded());
|
||||
}
|
||||
} catch (CertificateEncodingException e) {
|
||||
throw new RuntimeException("certificate encoding error: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -341,45 +336,48 @@ public class XAdESXLSignatureFacet implements SignatureFacet {
|
|||
|
||||
private XAdESTimeStampType createXAdESTimeStamp(
|
||||
SignatureInfo signatureInfo,
|
||||
List<Node> nodeList,
|
||||
RevocationData revocationData) {
|
||||
RevocationData revocationData,
|
||||
Node... nodes) {
|
||||
SignatureConfig signatureConfig = signatureInfo.getSignatureConfig();
|
||||
byte[] c14nSignatureValueElement = getC14nValue(nodeList, signatureConfig.getXadesCanonicalizationMethod());
|
||||
byte[] c14nSignatureValueElement = getC14nValue(Arrays.asList(nodes), signatureConfig.getXadesCanonicalizationMethod());
|
||||
|
||||
return createXAdESTimeStamp(signatureInfo, c14nSignatureValueElement, revocationData);
|
||||
}
|
||||
|
||||
private XAdESTimeStampType createXAdESTimeStamp(SignatureInfo signatureInfo, byte[] data, RevocationData revocationData) {
|
||||
SignatureConfig signatureConfig = signatureInfo.getSignatureConfig();
|
||||
// create the time-stamp
|
||||
byte[] timeStampToken;
|
||||
try {
|
||||
timeStampToken = signatureConfig.getTspService().timeStamp(signatureInfo, data, revocationData);
|
||||
timeStampToken = signatureConfig.getTspService().timeStamp(signatureInfo, c14nSignatureValueElement, revocationData);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("error while creating a time-stamp: "
|
||||
+ e.getMessage(), e);
|
||||
+ e.getMessage(), e);
|
||||
}
|
||||
|
||||
// create a XAdES time-stamp container
|
||||
XAdESTimeStampType xadesTimeStamp = XAdESTimeStampType.Factory.newInstance();
|
||||
xadesTimeStamp.setId("time-stamp-" + UUID.randomUUID());
|
||||
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());
|
||||
|
||||
return xadesTimeStamp;
|
||||
}
|
||||
|
||||
private ValidationDataType createValidationData(
|
||||
RevocationData revocationData) {
|
||||
ValidationDataType validationData = ValidationDataType.Factory.newInstance();
|
||||
private TimeStampValidationDataDocument createValidationData(RevocationData revocationData)
|
||||
throws CertificateEncodingException {
|
||||
TimeStampValidationDataDocument doc = TimeStampValidationDataDocument.Factory.newInstance();
|
||||
ValidationDataType validationData = doc.addNewTimeStampValidationData();
|
||||
List<X509Certificate> tspChain = revocationData.getX509chain();
|
||||
|
||||
if (tspChain.size() > 1) {
|
||||
CertificateValuesType cvals = validationData.addNewCertificateValues();
|
||||
for (X509Certificate x509 : tspChain.subList(1, tspChain.size())) {
|
||||
byte[] encoded = x509.getEncoded();
|
||||
cvals.addNewEncapsulatedX509Certificate().setByteArrayValue(encoded);
|
||||
}
|
||||
}
|
||||
RevocationValuesType revocationValues = validationData.addNewRevocationValues();
|
||||
createRevocationValues(revocationValues, revocationData);
|
||||
return validationData;
|
||||
return doc;
|
||||
}
|
||||
|
||||
private void createRevocationValues(
|
||||
|
|
|
@ -26,7 +26,9 @@ package org.apache.poi.poifs.crypt.dsig.services;
|
|||
|
||||
import java.security.cert.CRLException;
|
||||
import java.security.cert.X509CRL;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -34,37 +36,28 @@ import java.util.List;
|
|||
*/
|
||||
public class RevocationData {
|
||||
|
||||
private final List<byte[]> crls;
|
||||
|
||||
private final List<byte[]> ocsps;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public RevocationData() {
|
||||
this.crls = new ArrayList<>();
|
||||
this.ocsps = new ArrayList<>();
|
||||
}
|
||||
private final List<byte[]> crls = new ArrayList<>();
|
||||
private final List<byte[]> ocsps = new ArrayList<>();
|
||||
private final List<X509Certificate> x509chain = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Adds a CRL to this revocation data set.
|
||||
*/
|
||||
public void addCRL(byte[] encodedCrl) {
|
||||
this.crls.add(encodedCrl);
|
||||
if (this.crls.stream().noneMatch(by -> Arrays.equals(by, encodedCrl))) {
|
||||
this.crls.add(encodedCrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a CRL to this revocation data set.
|
||||
*/
|
||||
public void addCRL(X509CRL crl) {
|
||||
byte[] encodedCrl;
|
||||
try {
|
||||
encodedCrl = crl.getEncoded();
|
||||
addCRL(crl.getEncoded());
|
||||
} catch (CRLException e) {
|
||||
throw new IllegalArgumentException("CRL coding error: "
|
||||
+ e.getMessage(), e);
|
||||
throw new IllegalArgumentException("CRL coding error: " + e.getMessage(), e);
|
||||
}
|
||||
addCRL(encodedCrl);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -74,6 +67,10 @@ public class RevocationData {
|
|||
this.ocsps.add(encodedOcsp);
|
||||
}
|
||||
|
||||
public void addCertificate(X509Certificate x509) {
|
||||
x509chain.add(x509);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back a list of all CRLs.
|
||||
*
|
||||
|
@ -120,4 +117,8 @@ public class RevocationData {
|
|||
public boolean hasRevocationDataEntries() {
|
||||
return hasOCSPs() || hasCRLs();
|
||||
}
|
||||
|
||||
public List<X509Certificate> getX509chain() {
|
||||
return x509chain;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,42 +26,49 @@ package org.apache.poi.poifs.crypt.dsig.services;
|
|||
|
||||
import static org.apache.logging.log4j.util.Unbox.box;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509CRL;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.message.SimpleMessage;
|
||||
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
||||
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
||||
import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
|
||||
import org.apache.poi.poifs.crypt.dsig.SignatureConfig.CRLEntry;
|
||||
import org.apache.poi.poifs.crypt.dsig.SignatureInfo;
|
||||
import org.apache.poi.util.HexDump;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.apache.poi.poifs.crypt.dsig.services.TimeStampHttpClient.TimeStampHttpClientResponse;
|
||||
import org.bouncycastle.asn1.ASN1IA5String;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.ASN1Primitive;
|
||||
import org.bouncycastle.asn1.cmp.PKIFailureInfo;
|
||||
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x509.CRLDistPoint;
|
||||
import org.bouncycastle.asn1.x509.DistributionPoint;
|
||||
import org.bouncycastle.asn1.x509.DistributionPointName;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.asn1.x509.GeneralName;
|
||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
|
||||
import org.bouncycastle.cms.DefaultCMSSignatureAlgorithmNameGenerator;
|
||||
import org.bouncycastle.cms.SignerId;
|
||||
import org.bouncycastle.cms.SignerInformationVerifier;
|
||||
|
@ -81,25 +88,6 @@ public class TSPTimeStampService implements TimeStampService {
|
|||
|
||||
private static final Logger LOG = LogManager.getLogger(TSPTimeStampService.class);
|
||||
|
||||
// how large a timestamp response is expected to be
|
||||
// can be overwritten via IOUtils.setByteArrayMaxOverride()
|
||||
private static final int DEFAULT_TIMESTAMP_RESPONSE_SIZE = 10_000_000;
|
||||
private static int MAX_TIMESTAMP_RESPONSE_SIZE = DEFAULT_TIMESTAMP_RESPONSE_SIZE;
|
||||
|
||||
/**
|
||||
* @param maxTimestampResponseSize the max timestamp response size allowed
|
||||
*/
|
||||
public static void setMaxTimestampResponseSize(int maxTimestampResponseSize) {
|
||||
MAX_TIMESTAMP_RESPONSE_SIZE = maxTimestampResponseSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the max timestamp response size allowed
|
||||
*/
|
||||
public static int getMaxTimestampResponseSize() {
|
||||
return MAX_TIMESTAMP_RESPONSE_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the digest algorithm to corresponding OID value.
|
||||
*/
|
||||
|
@ -133,72 +121,16 @@ public class TSPTimeStampService implements TimeStampService {
|
|||
}
|
||||
ASN1ObjectIdentifier digestAlgoOid = mapDigestAlgoToOID(signatureConfig.getTspDigestAlgo());
|
||||
TimeStampRequest request = requestGenerator.generate(digestAlgoOid, digest, nonce);
|
||||
byte[] encodedRequest = request.getEncoded();
|
||||
|
||||
// create the HTTP POST request
|
||||
Proxy proxy = Proxy.NO_PROXY;
|
||||
if (signatureConfig.getProxyUrl() != null) {
|
||||
URL proxyUrl = new URL(signatureConfig.getProxyUrl());
|
||||
String host = proxyUrl.getHost();
|
||||
int port = proxyUrl.getPort();
|
||||
proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(InetAddress.getByName(host), (port == -1 ? 80 : port)));
|
||||
TimeStampHttpClient httpClient = signatureConfig.getTspHttpClient();
|
||||
httpClient.init(signatureConfig);
|
||||
httpClient.setContentTypeIn(signatureConfig.isTspOldProtocol() ? "application/timestamp-request" : "application/timestamp-query");
|
||||
TimeStampHttpClientResponse response = httpClient.post(signatureConfig.getTspUrl(), request.getEncoded());
|
||||
if (!response.isOK()) {
|
||||
throw new IOException("Requesting timestamp data failed");
|
||||
}
|
||||
|
||||
String contentType;
|
||||
HttpURLConnection huc = (HttpURLConnection)new URL(signatureConfig.getTspUrl()).openConnection(proxy);
|
||||
byte[] responseBytes;
|
||||
try {
|
||||
if (signatureConfig.getTspUser() != null) {
|
||||
String userPassword = signatureConfig.getTspUser() + ":" + signatureConfig.getTspPass();
|
||||
String encoding = Base64.getEncoder().encodeToString(userPassword.getBytes(StandardCharsets.ISO_8859_1));
|
||||
huc.setRequestProperty("Authorization", "Basic " + encoding);
|
||||
}
|
||||
|
||||
huc.setRequestMethod("POST");
|
||||
huc.setConnectTimeout(20000);
|
||||
huc.setReadTimeout(20000);
|
||||
huc.setDoOutput(true); // also sets method to POST.
|
||||
huc.setRequestProperty("User-Agent", signatureConfig.getUserAgent());
|
||||
huc.setRequestProperty("Content-Type", signatureConfig.isTspOldProtocol()
|
||||
? "application/timestamp-request"
|
||||
: "application/timestamp-query"); // "; charset=ISO-8859-1");
|
||||
|
||||
OutputStream hucOut = huc.getOutputStream();
|
||||
hucOut.write(encodedRequest);
|
||||
|
||||
// invoke TSP service
|
||||
huc.connect();
|
||||
|
||||
int statusCode = huc.getResponseCode();
|
||||
if (statusCode != 200) {
|
||||
final String message = "Error contacting TSP server " + signatureConfig.getTspUrl() +
|
||||
", had status code " + statusCode + "/" + huc.getResponseMessage();
|
||||
LOG.atError().log(message);
|
||||
throw new IOException(message);
|
||||
}
|
||||
|
||||
// HTTP input validation
|
||||
contentType = huc.getHeaderField("Content-Type");
|
||||
if (null == contentType) {
|
||||
throw new RuntimeException("missing Content-Type header");
|
||||
}
|
||||
|
||||
try (InputStream stream = huc.getInputStream()) {
|
||||
responseBytes = IOUtils.toByteArrayWithMaxLength(stream, getMaxTimestampResponseSize());
|
||||
}
|
||||
LOG.atDebug().log(() -> new SimpleMessage("response content: " + HexDump.dump(responseBytes, 0, 0)));
|
||||
} finally {
|
||||
huc.disconnect();
|
||||
}
|
||||
|
||||
if (!contentType.startsWith(signatureConfig.isTspOldProtocol()
|
||||
? "application/timestamp-response"
|
||||
: "application/timestamp-reply"
|
||||
)) {
|
||||
throw new RuntimeException("invalid Content-Type: " + contentType +
|
||||
// dump the first few bytes
|
||||
": " + HexDump.dump(responseBytes, 0, 0, 200));
|
||||
}
|
||||
byte[] responseBytes = response.getResponseBytes();
|
||||
|
||||
if (responseBytes.length == 0) {
|
||||
throw new RuntimeException("Content-Length is zero");
|
||||
|
@ -229,53 +161,138 @@ public class TSPTimeStampService implements TimeStampService {
|
|||
LOG.atDebug().log("signer cert issuer: {}", signerCertIssuer);
|
||||
|
||||
// TSP signer certificates retrieval
|
||||
Collection<X509CertificateHolder> certificates = timeStampToken.getCertificates().getMatches(null);
|
||||
Map<String, X509CertificateHolder> certificateMap =
|
||||
timeStampToken.getCertificates().getMatches(null).stream()
|
||||
.collect(Collectors.toMap(h -> h.getSubject().toString(), Function.identity()));
|
||||
|
||||
X509CertificateHolder signerCert = null;
|
||||
Map<X500Name, X509CertificateHolder> certificateMap = new HashMap<>();
|
||||
for (X509CertificateHolder certificate : certificates) {
|
||||
if (signerCertIssuer.equals(certificate.getIssuer())
|
||||
&& signerCertSerialNumber.equals(certificate.getSerialNumber())) {
|
||||
signerCert = certificate;
|
||||
}
|
||||
certificateMap.put(certificate.getSubject(), certificate);
|
||||
}
|
||||
|
||||
// TSP signer cert path building
|
||||
if (signerCert == null) {
|
||||
throw new RuntimeException("TSP response token has no signer certificate");
|
||||
}
|
||||
List<X509Certificate> tspCertificateChain = new ArrayList<>();
|
||||
X509CertificateHolder signerCert = certificateMap.values().stream()
|
||||
.filter(h -> signerCertIssuer.equals(h.getIssuer())
|
||||
&& signerCertSerialNumber.equals(h.getSerialNumber()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new RuntimeException("TSP response token has no signer certificate"));
|
||||
|
||||
JcaX509CertificateConverter x509converter = new JcaX509CertificateConverter();
|
||||
x509converter.setProvider("BC");
|
||||
X509CertificateHolder certificate = signerCert;
|
||||
|
||||
// complete certificate chain
|
||||
X509Certificate child = x509converter.getCertificate(signerCert);
|
||||
do {
|
||||
LOG.atDebug().log("adding to certificate chain: {}", certificate.getSubject());
|
||||
tspCertificateChain.add(x509converter.getCertificate(certificate));
|
||||
if (certificate.getSubject().equals(certificate.getIssuer())) {
|
||||
revocationData.addCertificate(child);
|
||||
X500Principal issuer = child.getIssuerX500Principal();
|
||||
if (child.getSubjectX500Principal().equals(issuer)) {
|
||||
break;
|
||||
}
|
||||
certificate = certificateMap.get(certificate.getIssuer());
|
||||
} while (null != certificate);
|
||||
X509CertificateHolder parentHolder = certificateMap.get(issuer.getName());
|
||||
child = (parentHolder != null)
|
||||
? x509converter.getCertificate(parentHolder)
|
||||
: signatureConfig.getCachedCertificateByPrinicipal(issuer.getName());
|
||||
if (child != null) {
|
||||
retrieveCRL(signatureConfig, child).forEach(revocationData::addCRL);
|
||||
}
|
||||
} while (child != null);
|
||||
|
||||
// verify TSP signer signature
|
||||
X509CertificateHolder holder = new X509CertificateHolder(tspCertificateChain.get(0).getEncoded());
|
||||
DefaultCMSSignatureAlgorithmNameGenerator nameGen = new DefaultCMSSignatureAlgorithmNameGenerator();
|
||||
DefaultSignatureAlgorithmIdentifierFinder sigAlgoFinder = new DefaultSignatureAlgorithmIdentifierFinder();
|
||||
DefaultDigestAlgorithmIdentifierFinder hashAlgoFinder = new DefaultDigestAlgorithmIdentifierFinder();
|
||||
BcDigestCalculatorProvider calculator = new BcDigestCalculatorProvider();
|
||||
BcRSASignerInfoVerifierBuilder verifierBuilder = new BcRSASignerInfoVerifierBuilder(nameGen, sigAlgoFinder, hashAlgoFinder, calculator);
|
||||
SignerInformationVerifier verifier = verifierBuilder.build(holder);
|
||||
BcRSASignerInfoVerifierBuilder verifierBuilder = new BcRSASignerInfoVerifierBuilder(
|
||||
new DefaultCMSSignatureAlgorithmNameGenerator(),
|
||||
new DefaultSignatureAlgorithmIdentifierFinder(),
|
||||
new DefaultDigestAlgorithmIdentifierFinder(),
|
||||
new BcDigestCalculatorProvider());
|
||||
SignerInformationVerifier verifier = verifierBuilder.build(signerCert);
|
||||
|
||||
timeStampToken.validate(verifier);
|
||||
|
||||
// verify TSP signer certificate
|
||||
if (signatureConfig.getTspValidator() != null) {
|
||||
signatureConfig.getTspValidator().validate(tspCertificateChain, revocationData);
|
||||
signatureConfig.getTspValidator().validate(revocationData.getX509chain(), revocationData);
|
||||
}
|
||||
|
||||
LOG.atDebug().log("time-stamp token time: {}", timeStampToken.getTimeStampInfo().getGenTime());
|
||||
|
||||
return timeStampToken.getEncoded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if CRL is to be added, check cached CRLs in config and download if necessary.
|
||||
* Can be overriden to suppress the logic
|
||||
* @return empty list, if not found or suppressed, otherwise the list of CRLs as encoded bytes
|
||||
*/
|
||||
protected List<byte[]> retrieveCRL(SignatureConfig signatureConfig, X509Certificate holder) throws IOException {
|
||||
// TODO: add config, if crls should be added
|
||||
final List<CRLEntry> crlEntries = signatureConfig.getCrlEntries();
|
||||
byte[] crlPoints = holder.getExtensionValue(Extension.cRLDistributionPoints.getId());
|
||||
if (crlPoints == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// TODO: check if parse is necessary, or if crlExt.getExtnValue() can be use directly
|
||||
ASN1Primitive extVal = JcaX509ExtensionUtils.parseExtensionValue(crlPoints);
|
||||
return Stream.of(CRLDistPoint.getInstance(extVal).getDistributionPoints())
|
||||
.map(DistributionPoint::getDistributionPoint)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(dpn -> dpn.getType() == DistributionPointName.FULL_NAME)
|
||||
.flatMap(dpn -> Stream.of(GeneralNames.getInstance(dpn.getName()).getNames()))
|
||||
.filter(genName -> genName.getTagNo() == GeneralName.uniformResourceIdentifier)
|
||||
.map(genName -> ASN1IA5String.getInstance(genName.getName()).getString())
|
||||
.flatMap(url -> {
|
||||
List<CRLEntry> ul = crlEntries.stream().filter(ce -> matchCRLbyUrl(ce, holder, url)).collect(Collectors.toList());
|
||||
Stream<CRLEntry> cl = crlEntries.stream().filter(ce -> matchCRLbyCN(ce, holder, url));
|
||||
if (ul.isEmpty()) {
|
||||
CRLEntry ce = downloadCRL(signatureConfig, url);
|
||||
if (ce != null) {
|
||||
ul.add(ce);
|
||||
}
|
||||
}
|
||||
return Stream.concat(ul.stream(), cl).map(CRLEntry::getCrlBytes);
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
protected boolean matchCRLbyUrl(CRLEntry other, X509Certificate holder, String url) {
|
||||
return url.equals(other.getCrlURL());
|
||||
}
|
||||
|
||||
protected boolean matchCRLbyCN(CRLEntry other, X509Certificate holder, String url) {
|
||||
return holder.getSubjectX500Principal().getName().equals(other.getCertCN());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to download a crl in an unsafe way, i.e. without verifying the
|
||||
* https certificates.
|
||||
* Please provide your own method, if you have imported the TSP server CA certificates
|
||||
* in your local keystore
|
||||
*
|
||||
* @return the bytes of the CRL or null if unsuccessful / download is suppressed
|
||||
*/
|
||||
protected CRLEntry downloadCRL(SignatureConfig signatureConfig, String url) {
|
||||
if (!signatureConfig.isAllowCRLDownload()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
TimeStampHttpClient httpClient = signatureConfig.getTspHttpClient();
|
||||
httpClient.init(signatureConfig);
|
||||
httpClient.setBasicAuthentication(null, null);
|
||||
TimeStampHttpClientResponse response;
|
||||
try {
|
||||
response = httpClient.get(url);
|
||||
if (!response.isOK()) {
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
CertificateFactory certFact = CertificateFactory.getInstance("X.509");
|
||||
byte[] crlBytes = response.getResponseBytes();
|
||||
// verify the downloaded bytes, throws Exception if invalid
|
||||
X509CRL crl = (X509CRL)certFact.generateCRL(new ByteArrayInputStream(crlBytes));
|
||||
return signatureConfig.addCRL(url, crl.getIssuerX500Principal().getName(), crlBytes);
|
||||
} catch (GeneralSecurityException e) {
|
||||
LOG.atWarn().withThrowable(e).log("CRL download failed from {}", url);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/* ====================================================================
|
||||
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.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.poifs.crypt.dsig.services;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
|
||||
|
||||
/**
|
||||
* This interface is used to decouple the timestamp service logic from
|
||||
* the actual downloading code and to provide an interface for user code
|
||||
* using a different http client implementation.
|
||||
*
|
||||
* The implementation must be stateless regarding the http connection and
|
||||
* not expect to be called in a certain order, apart from being first initialized.
|
||||
*/
|
||||
public interface TimeStampHttpClient {
|
||||
interface TimeStampHttpClientResponse {
|
||||
default boolean isOK() {
|
||||
return getResponseCode() == HttpURLConnection.HTTP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the http response code
|
||||
*/
|
||||
int getResponseCode();
|
||||
|
||||
/**
|
||||
* @return the http response bytes
|
||||
*/
|
||||
byte[] getResponseBytes();
|
||||
}
|
||||
|
||||
void init(SignatureConfig config);
|
||||
|
||||
/** set request content type */
|
||||
void setContentTypeIn(String contentType);
|
||||
|
||||
/** set expected response content type - use {@code null} if contentType is ignored */
|
||||
void setContentTypeOut(String contentType);
|
||||
|
||||
void setBasicAuthentication(String username, String password);
|
||||
|
||||
TimeStampHttpClientResponse post(String url, byte[] payload) throws IOException;
|
||||
|
||||
TimeStampHttpClientResponse get(String url) throws IOException;
|
||||
|
||||
/**
|
||||
* @return if the connection is reckless ignoring all https certificate trust issues
|
||||
*/
|
||||
boolean isIgnoreHttpsCertificates();
|
||||
|
||||
/**
|
||||
* @param ignoreHttpsCertificates set if the connection is reckless ignoring all https certificate trust issues
|
||||
*/
|
||||
void setIgnoreHttpsCertificates(boolean ignoreHttpsCertificates);
|
||||
|
||||
/**
|
||||
* @return if http redirects are followed once
|
||||
*/
|
||||
boolean isFollowRedirects();
|
||||
|
||||
/**
|
||||
* @param followRedirects set if http redirects are followed once
|
||||
*/
|
||||
void setFollowRedirects(boolean followRedirects);
|
||||
}
|
|
@ -0,0 +1,285 @@
|
|||
/* ====================================================================
|
||||
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.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.poifs.crypt.dsig.services;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.apache.poi.util.RandomSingleton;
|
||||
|
||||
/**
|
||||
* This default implementation is used to decouple the timestamp service logic from
|
||||
* the actual downloading code and to provide a base for user code
|
||||
* using a different http client implementation
|
||||
*/
|
||||
public class TimeStampSimpleHttpClient implements TimeStampHttpClient {
|
||||
protected static final String CONTENT_TYPE = "Content-Type";
|
||||
protected static final String USER_AGENT = "User-Agent";
|
||||
protected static final String BASIC_AUTH = "Authorization";
|
||||
protected static final String REDIRECT_LOCATION = "Location";
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(TSPTimeStampService.class);
|
||||
|
||||
// how large a timestamp response is expected to be
|
||||
// can be overwritten via IOUtils.setByteArrayMaxOverride()
|
||||
private static final int DEFAULT_TIMESTAMP_RESPONSE_SIZE = 10_000_000;
|
||||
private static int MAX_TIMESTAMP_RESPONSE_SIZE = DEFAULT_TIMESTAMP_RESPONSE_SIZE;
|
||||
|
||||
/**
|
||||
* @param maxTimestampResponseSize the max timestamp response size allowed
|
||||
*/
|
||||
public static void setMaxTimestampResponseSize(int maxTimestampResponseSize) {
|
||||
MAX_TIMESTAMP_RESPONSE_SIZE = maxTimestampResponseSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the max timestamp response size allowed
|
||||
*/
|
||||
public static int getMaxTimestampResponseSize() {
|
||||
return MAX_TIMESTAMP_RESPONSE_SIZE;
|
||||
}
|
||||
|
||||
|
||||
private static class TimeStampSimpleHttpClientResponse implements TimeStampHttpClientResponse {
|
||||
private final int responseCode;
|
||||
private final byte[] responseBytes;
|
||||
|
||||
public TimeStampSimpleHttpClientResponse(int responseCode, byte[] responseBytes) {
|
||||
this.responseCode = responseCode;
|
||||
this.responseBytes = responseBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getResponseCode() {
|
||||
return responseCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getResponseBytes() {
|
||||
return responseBytes;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected SignatureConfig config;
|
||||
protected Proxy proxy = Proxy.NO_PROXY;
|
||||
protected final Map<String,String> header = new HashMap<>();
|
||||
protected String contentTypeOut = null;
|
||||
protected boolean ignoreHttpsCertificates = false;
|
||||
protected boolean followRedirects = false;
|
||||
|
||||
@Override
|
||||
public void init(SignatureConfig config) {
|
||||
this.config = config;
|
||||
header.clear();
|
||||
|
||||
header.put(USER_AGENT, config.getUserAgent());
|
||||
|
||||
contentTypeOut = null;
|
||||
// don't reset followRedirects/ignoreHttpsCertificates, as they aren't contained in SignatureConfig by design
|
||||
// followRedirects = false;
|
||||
// ignoreHttpsCertificates = false;
|
||||
|
||||
setProxy(config.getProxyUrl());
|
||||
setBasicAuthentication(config.getTspUser(), config.getTspPass());
|
||||
}
|
||||
|
||||
public void setProxy(String proxyUrl) {
|
||||
if (proxyUrl == null || proxyUrl.isEmpty()) {
|
||||
proxy = Proxy.NO_PROXY;
|
||||
} else {
|
||||
try {
|
||||
URL pUrl = new URL(proxyUrl);
|
||||
String host = pUrl.getHost();
|
||||
int port = pUrl.getPort();
|
||||
proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(InetAddress.getByName(host), (port == -1 ? 80 : port)));
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Proxy getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentTypeIn(String contentType) {
|
||||
header.put(CONTENT_TYPE, contentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentTypeOut(String contentType) {
|
||||
contentTypeOut = contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBasicAuthentication(String username, String password) {
|
||||
if (username == null || username.isEmpty() || password == null || password.isEmpty()) {
|
||||
header.remove(BASIC_AUTH);
|
||||
} else {
|
||||
String userPassword = username + ":" + password;
|
||||
String encoding = Base64.getEncoder().encodeToString(userPassword.getBytes(StandardCharsets.ISO_8859_1));
|
||||
header.put(BASIC_AUTH, "Basic " + encoding);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIgnoreHttpsCertificates() {
|
||||
return ignoreHttpsCertificates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIgnoreHttpsCertificates(boolean ignoreHttpsCertificates) {
|
||||
this.ignoreHttpsCertificates = ignoreHttpsCertificates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFollowRedirects() {
|
||||
return followRedirects;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFollowRedirects(boolean followRedirects) {
|
||||
this.followRedirects = followRedirects;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeStampHttpClientResponse post(String url, byte[] payload) throws IOException {
|
||||
MethodHandler handler = (huc) -> {
|
||||
huc.setRequestMethod("POST");
|
||||
huc.setDoOutput(true);
|
||||
try (OutputStream hucOut = huc.getOutputStream()) {
|
||||
hucOut.write(payload);
|
||||
}
|
||||
};
|
||||
return handleRedirect(url, handler, isFollowRedirects());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeStampHttpClientResponse get(String url) throws IOException {
|
||||
// connection is by default a GET call
|
||||
return handleRedirect(url, (huc) -> {}, isFollowRedirects());
|
||||
}
|
||||
|
||||
protected interface MethodHandler {
|
||||
void handle(HttpURLConnection huc) throws IOException;
|
||||
}
|
||||
|
||||
protected TimeStampHttpClientResponse handleRedirect(String url, MethodHandler handler, boolean followRedirect) throws IOException {
|
||||
HttpURLConnection huc = (HttpURLConnection)new URL(url).openConnection(proxy);
|
||||
if (ignoreHttpsCertificates) {
|
||||
recklessConnection(huc);
|
||||
}
|
||||
huc.setConnectTimeout(20000);
|
||||
huc.setReadTimeout(20000);
|
||||
|
||||
header.forEach(huc::setRequestProperty);
|
||||
|
||||
try {
|
||||
handler.handle(huc);
|
||||
|
||||
huc.connect();
|
||||
|
||||
final int responseCode = huc.getResponseCode();
|
||||
final byte[] responseBytes;
|
||||
|
||||
switch (responseCode) {
|
||||
case HttpURLConnection.HTTP_MOVED_TEMP:
|
||||
case HttpURLConnection.HTTP_MOVED_PERM:
|
||||
case HttpURLConnection.HTTP_SEE_OTHER:
|
||||
String newUrl = huc.getHeaderField(REDIRECT_LOCATION);
|
||||
if (newUrl != null && followRedirect) {
|
||||
LOG.atWarn().log("Received redirect: {} -> {}", url, newUrl);
|
||||
return handleRedirect(newUrl, handler, false);
|
||||
}
|
||||
|
||||
LOG.atWarn().log("Redirect ignored - giving up: {} -> {}", url, newUrl);
|
||||
responseBytes = null;
|
||||
break;
|
||||
case HttpURLConnection.HTTP_OK:
|
||||
// HTTP input validation
|
||||
String contentType = huc.getHeaderField(CONTENT_TYPE);
|
||||
if (contentTypeOut != null && !contentTypeOut.equals(contentType)) {
|
||||
throw new IOException("Content-Type mismatch - expected `" + contentTypeOut + "', received '" + contentType + "'");
|
||||
}
|
||||
|
||||
try (InputStream is = huc.getInputStream()) {
|
||||
responseBytes = IOUtils.toByteArray(is, Integer.MIN_VALUE, getMaxTimestampResponseSize());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
final String message = "Error contacting TSP server " + url +
|
||||
", had status code " + responseCode + "/" + huc.getResponseMessage();
|
||||
LOG.atError().log(message);
|
||||
throw new IOException(message);
|
||||
}
|
||||
|
||||
return new TimeStampSimpleHttpClientResponse(responseCode, responseBytes);
|
||||
} finally {
|
||||
huc.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
protected void recklessConnection(HttpURLConnection conn) throws IOException {
|
||||
if (!(conn instanceof HttpsURLConnection)) {
|
||||
return;
|
||||
}
|
||||
HttpsURLConnection conns = (HttpsURLConnection)conn;
|
||||
|
||||
try {
|
||||
SSLContext sc = SSLContext.getInstance("SSL");
|
||||
sc.init(null, new TrustManager[]{new UnsafeTrustManager()}, RandomSingleton.getInstance());
|
||||
conns.setSSLSocketFactory(sc.getSocketFactory());
|
||||
conns.setHostnameVerifier((hostname, session) -> true);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IOException("Unable to reckless wrap connection.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class UnsafeTrustManager implements X509TrustManager {
|
||||
@Override
|
||||
public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; }
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) { }
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType) { }
|
||||
}
|
||||
}
|
|
@ -37,21 +37,34 @@ import java.security.KeyPair;
|
|||
import java.security.KeyPairGenerator;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.Certificate;
|
||||
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.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.RSAKeyGenParameterSpec;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import org.apache.commons.io.input.UnsynchronizedByteArrayInputStream;
|
||||
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
||||
import org.apache.poi.poifs.storage.RawDataUtil;
|
||||
|
@ -206,14 +219,38 @@ public class DummyKeystore {
|
|||
return new KeyCertPair(getKey(keyAlias, keyPass), keystore.getCertificateChain(keyAlias));
|
||||
}
|
||||
|
||||
public KeyCertPair getKeyPair(int index, String keyPass) throws GeneralSecurityException {
|
||||
Map.Entry<String, PrivateKey> me = getKeyByIndex(index, keyPass);
|
||||
return me != null ? getKeyPair(me.getKey(), keyPass) : null;
|
||||
}
|
||||
|
||||
public PrivateKey getKey(String keyAlias, String keyPass) throws GeneralSecurityException {
|
||||
return (PrivateKey)keystore.getKey(keyAlias, keyPass.toCharArray());
|
||||
}
|
||||
|
||||
public PrivateKey getKey(int index, String keyPass) throws GeneralSecurityException {
|
||||
Map.Entry<String, PrivateKey> me = getKeyByIndex(index, keyPass);
|
||||
return me != null ? me.getValue() : null;
|
||||
}
|
||||
|
||||
public X509Certificate getFirstX509(String alias) throws KeyStoreException {
|
||||
return (X509Certificate)keystore.getCertificate(alias);
|
||||
}
|
||||
|
||||
private Map.Entry<String,PrivateKey> getKeyByIndex(int index, String keyPass) throws GeneralSecurityException {
|
||||
for (String a : Collections.list(keystore.aliases())) {
|
||||
try {
|
||||
PrivateKey pk = (PrivateKey) keystore.getKey(a, keyPass.toCharArray());
|
||||
if (pk != null) {
|
||||
return new AbstractMap.SimpleEntry<>(a, pk);
|
||||
}
|
||||
} catch (UnrecoverableKeyException | KeyStoreException | NoSuchAlgorithmException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void save(File storeFile, String storePass) throws IOException, GeneralSecurityException {
|
||||
try (FileOutputStream fos = new FileOutputStream(storeFile)) {
|
||||
keystore.store(fos, storePass.toCharArray());
|
||||
|
@ -350,4 +387,50 @@ public class DummyKeystore {
|
|||
return ocspRespBuilder.build(OCSPRespBuilder.SUCCESSFUL, basicOCSPResp);
|
||||
}
|
||||
|
||||
public void importX509(File file) throws CertificateException, KeyStoreException, IOException {
|
||||
try (InputStream is = new FileInputStream(file)) {
|
||||
X509Certificate cert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(is);
|
||||
keystore.setCertificateEntry(cert.getSubjectX500Principal().getName(), cert);
|
||||
}
|
||||
}
|
||||
|
||||
public void importKeystore(File file, String storePass, String keyPass, Function<String,String> otherKeyPass) throws GeneralSecurityException, IOException {
|
||||
DummyKeystore dk = new DummyKeystore(file, storePass);
|
||||
|
||||
Map<String,X509Certificate> myCerts = new HashMap<>();
|
||||
for (String a : Collections.list(keystore.aliases())) {
|
||||
Certificate[] chain = keystore.getCertificateChain(a);
|
||||
if (chain == null) {
|
||||
Certificate cert = keystore.getCertificate(a);
|
||||
if (cert == null) {
|
||||
continue;
|
||||
}
|
||||
chain = new Certificate[]{cert};
|
||||
}
|
||||
Arrays.stream(chain)
|
||||
.map(X509Certificate.class::cast)
|
||||
.filter(c -> !myCerts.containsKey(c.getSubjectX500Principal().getName()))
|
||||
.forEach(c -> myCerts.put(c.getSubjectX500Principal().getName(), c));
|
||||
}
|
||||
|
||||
for (String a : Collections.list(dk.keystore.aliases())) {
|
||||
KeyCertPair keyPair = dk.getKeyPair(a, otherKeyPass.apply(a));
|
||||
ArrayList<X509Certificate> chain = new ArrayList<>(keyPair.getX509Chain());
|
||||
Set<String> names = chain.stream().map(X509Certificate::getSubjectX500Principal).map(X500Principal::getName).collect(Collectors.toSet());
|
||||
X509Certificate last = chain.get(chain.size() - 1);
|
||||
do {
|
||||
String issuer = last.getIssuerX500Principal().getName();
|
||||
X509Certificate parent = myCerts.get(issuer);
|
||||
if (names.contains(issuer) || parent == null) {
|
||||
break;
|
||||
} else {
|
||||
chain.add(parent);
|
||||
names.add(issuer);
|
||||
}
|
||||
last = parent;
|
||||
} while (true);
|
||||
|
||||
keystore.setKeyEntry(a, keyPair.getKey(), keyPass.toCharArray(), chain.toArray(new X509Certificate[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -160,7 +160,7 @@ public final class IOUtils {
|
|||
* Reads up to {@code length} bytes from the input stream, and returns the bytes read.
|
||||
*
|
||||
* @param stream The byte stream of data to read.
|
||||
* @param length The maximum length to read, use {@link Integer#MAX_VALUE} to read the stream
|
||||
* @param length The maximum length to read, use {@link Integer#MIN_VALUE} to read the stream
|
||||
* until EOF
|
||||
* @param maxLength if the input is equal to/longer than {@code maxLength} bytes,
|
||||
* then throw an {@link IOException} complaining about the length.
|
||||
|
|
Loading…
Reference in New Issue