All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.helger.smpclient.httpclient.SMPHttpResponseHandlerSigned Maven / Gradle / Ivy

There is a newer version: 9.6.0
Show newest version
/*
 * Copyright (C) 2015-2024 Philip Helger
 * philip[at]helger[dot]com
 *
 * Licensed 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 com.helger.smpclient.httpclient;

import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.util.Iterator;
import java.util.List;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.WillNotClose;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;

import org.apache.hc.core5.http.HttpEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.collection.ArrayHelper;
import com.helger.commons.io.stream.NonBlockingByteArrayInputStream;
import com.helger.commons.io.stream.StreamHelper;
import com.helger.commons.state.ESuccess;
import com.helger.jaxb.GenericJAXBMarshaller;
import com.helger.smpclient.exception.SMPClientBadResponseException;
import com.helger.smpclient.security.TrustStoreBasedX509KeySelector;
import com.helger.xml.serialize.read.DOMReader;

/**
 * This is the Apache HTTP client response handler to verify signed HTTP
 * response messages.
 * 

* Note: this class is also licensed under Apache 2 license, as it was not part * of the original implementation *

* * @author Philip Helger * @param * The type of object to be handled. */ public class SMPHttpResponseHandlerSigned extends AbstractSMPResponseHandler { public static final boolean DEFAULT_VERIFY_SIGNATURE = true; public static final boolean DEFAULT_SECURE_VALIDATION = true; private static final Logger LOGGER = LoggerFactory.getLogger (SMPHttpResponseHandlerSigned.class); private final GenericJAXBMarshaller m_aMarshaller; private boolean m_bVerifySignature = DEFAULT_VERIFY_SIGNATURE; private boolean m_bSecureValidation = DEFAULT_SECURE_VALIDATION; private KeyStore m_aTrustStore; /** * Constructor * * @param aMarshaller * The JAXB marshaller to be used. May not be null. * @param aTrustStore * The trust store to be used. May be null. * @since 8.1.1 */ public SMPHttpResponseHandlerSigned (@Nonnull final GenericJAXBMarshaller aMarshaller, @Nullable final KeyStore aTrustStore) { m_aMarshaller = ValueEnforcer.notNull (aMarshaller, "Marshaller"); m_aTrustStore = aTrustStore; } /** * @return true if SMP client response certificate checking is * enabled, false if it is disabled. By default this * check is enabled (see {@link #DEFAULT_VERIFY_SIGNATURE}). * @since 8.0.3 */ public final boolean isVerifySignature () { return m_bVerifySignature; } /** * Check the certificate retrieved from a signed SMP response? This may be * helpful for debugging and testing of SMP client connections!
* Uses the trust store configured in the SMP client configuration. * * @param bVerifySignature * true to enable SMP response checking (on by default) or * false to disable it. * @return this for chaining * @since 8.0.3 */ @Nonnull public final SMPHttpResponseHandlerSigned setVerifySignature (final boolean bVerifySignature) { m_bVerifySignature = bVerifySignature; return this; } /** * @return true if SMP client response certificate checking * should use secure validation, false if validation also * allows deprecated algorithms. By default this check is enabled (see * {@link #DEFAULT_SECURE_VALIDATION}). * @since 9.0.5 */ public final boolean isSecureValidation () { return m_bSecureValidation; } /** * Enable or disable the usage of secure XMLDsig validation. By default secure * validation is enabled. Java 17 disables the usage of SHA-1 in XMLDsig by * default, as documented in https://bugs.openjdk.org/browse/JDK-8261246. * Currently the Peppol SMP still uses SHA-1 so you might want to disable this * for the sake of sanity. * * @param bSecureValidation * true to enable SMP secure certificate validation * (enabled by default) or false to disable it. * @return this for chaining * @since 9.0.5 */ @Nonnull public final SMPHttpResponseHandlerSigned setSecureValidation (final boolean bSecureValidation) { m_bSecureValidation = bSecureValidation; return this; } /** * @return The trust store to be used for verifying the signature. May be * null if an invalid trust store is configured. * @since 8.1.1 */ @Nullable public final KeyStore getTrustStore () { return m_aTrustStore; } /** * Set the trust store to be used. If signature verification is enabled, a * trust store MUST be preset. * * @param aTrustStore * The trust store to be used. May be null. * @return this for chaining * @since 8.1.1 */ @Nonnull public final SMPHttpResponseHandlerSigned setTrustStore (@Nullable final KeyStore aTrustStore) { m_aTrustStore = aTrustStore; return this; } @Nonnull public static ESuccess checkSignature (@Nonnull final Document aDocument, @Nonnull final KeySelector aKeySelector, final boolean bSecureValidation) throws MarshalException, XMLSignatureException { // We make sure that the XML is a Signed. If not, we don't have to check // any certificates. // Find all "Signature" elements final NodeList aNodeList = aDocument.getElementsByTagNameNS (XMLSignature.XMLNS, "Signature"); if (aNodeList == null || aNodeList.getLength () == 0) throw new IllegalArgumentException ("Element not found in SMP XML response"); final int nSignatureCount = aNodeList.getLength (); if (LOGGER.isDebugEnabled ()) LOGGER.debug ("Found " + nSignatureCount + " " + (nSignatureCount == 1 ? "element" : "elements") + " to verify, using " + (bSecureValidation ? "secure" : "regular") + " validation"); final XMLSignatureFactory aSignatureFactory = XMLSignatureFactory.getInstance ("DOM"); ESuccess eSuccess = ESuccess.SUCCESS; // OASIS BDXR SMP v2 can have more than one signature for (int nSignatureIndex = 0; nSignatureIndex < nSignatureCount; ++nSignatureIndex) { // Create a DOMValidateContext and specify a KeySelector final DOMValidateContext aValidateContext = new DOMValidateContext (aKeySelector, aNodeList.item (nSignatureIndex)); aValidateContext.setProperty ("org.jcp.xml.dsig.secureValidation", Boolean.valueOf (bSecureValidation)); final String sSignatureDebug = (nSignatureIndex + 1) + "/" + nSignatureCount; // Unmarshal the XMLSignature. final XMLSignature aSignature = aSignatureFactory.unmarshalXMLSignature (aValidateContext); // Validate the XMLSignature. final boolean bCoreValid = aSignature.validate (aValidateContext); if (bCoreValid) { if (LOGGER.isDebugEnabled ()) LOGGER.debug ("Signature[" + sSignatureDebug + "] validation was successful"); } else { eSuccess = ESuccess.FAILURE; // This code block is for debugging purposes only - it has no semantical // influence LOGGER.warn ("Signature[" + sSignatureDebug + "] failed core validation"); final boolean bSignatureValueValid = aSignature.getSignatureValue ().validate (aValidateContext); if (bSignatureValueValid) { LOGGER.info (" Signature[" + sSignatureDebug + "] SignatureValue validity status: valid"); } else { LOGGER.warn (" Signature[" + sSignatureDebug + "] SignatureValue validity status: NOT valid!"); } { // Check the validation status of each Reference. final List aRefs = aSignature.getSignedInfo ().getReferences (); final int nRefCount = aRefs.size (); int nRefIndex = 0; final Iterator i = aRefs.iterator (); while (i.hasNext ()) { final String sRefDebug = (nRefIndex + 1) + "/" + nRefCount; final Reference aRef = (Reference) i.next (); if (aRef.getTransforms ().size () != 1) LOGGER.warn (" Signature[" + sSignatureDebug + "] Reference[" + sRefDebug + "] has an invalid number of Transforms. Expected 1 but having " + aRef.getTransforms ().size ()); final boolean bRefValid = aRef.validate (aValidateContext); if (bRefValid) { LOGGER.info (" Signature[" + sSignatureDebug + "] Reference[" + sRefDebug + "] validity status: valid"); } else { LOGGER.warn (" Signature[" + sSignatureDebug + "] Reference[" + sRefDebug + "] validity status: NOT valid!"); } ++nRefIndex; } } } } return eSuccess; } @Nonnull private static ESuccess _checkSignature (@Nonnull @WillNotClose final InputStream aEntityInputStream, @Nonnull final KeyStore aTrustStore, final boolean bSecureValidation) throws MarshalException, XMLSignatureException { // Get response from servlet final Document aDocument = DOMReader.readXMLDOM (aEntityInputStream); if (aDocument == null) throw new IllegalArgumentException ("The SMP response is not XML"); final TrustStoreBasedX509KeySelector aKeySelector = new TrustStoreBasedX509KeySelector (aTrustStore); return checkSignature (aDocument, aKeySelector, bSecureValidation); } @Override @Nonnull public T handleEntity (@Nonnull final HttpEntity aEntity) throws SMPClientBadResponseException, IOException { // Get complete response as one big byte buffer final byte [] aResponseBytes = StreamHelper.getAllBytes (aEntity.getContent ()); if (ArrayHelper.isEmpty (aResponseBytes)) throw new SMPClientBadResponseException ("SMP server response content is empty/could not be read"); if (LOGGER.isDebugEnabled ()) LOGGER.debug ("Signed SMP response has " + aResponseBytes.length + " bytes"); if (m_bVerifySignature) { if (m_aTrustStore == null) throw new SMPClientBadResponseException ("No trust store was configured - cannot verify signatures"); try (final InputStream aIS = new NonBlockingByteArrayInputStream (aResponseBytes)) { // Check the signature if (_checkSignature (aIS, m_aTrustStore, m_bSecureValidation).isFailure ()) throw new SMPClientBadResponseException ("Signature returned from SMP server was not valid with " + (m_bSecureValidation ? "secure" : "regular") + " validation"); if (LOGGER.isDebugEnabled ()) LOGGER.debug ("Successfully verified signature of signed SMP response"); } catch (final SMPClientBadResponseException ex) { // Avoid double wrapping throw ex; } catch (final Exception ex) { throw new SMPClientBadResponseException ("Error in validating signature returned from SMP server", ex); } } else { LOGGER.warn ("SMP response signature verification is disabled. This should not happen in production systems!"); } // Finally convert to domain object final T ret = m_aMarshaller.read (aResponseBytes); if (ret == null) throw new SMPClientBadResponseException ("Malformed XML document returned from SMP server"); if (LOGGER.isDebugEnabled ()) LOGGER.debug ("Successfully parsed signed SMP HTTP response"); return ret; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy