NHRLoadUtils.java

/*
 * Copyright (C) 2022 B3Partners B.V.
 *
 * SPDX-License-Identifier: MIT
 *
 */

package nl.b3p.brmo.nhr.loader.cli;

import java.io.FileInputStream;
import java.security.KeyStore;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.ws.BindingProvider;
import nl.b3p.brmo.loader.BrmoFramework;
import nl.b3p.brmo.loader.util.BrmoException;
import nl.b3p.brmo.nhr.loader.NHRCertificateOptions;
import nl.b3p.brmo.nhr.loader.NHRDatabaseOptions;
import nl.kvk.schemas.schemas.hrip.dataservice._2015._02.Dataservice;
import nl.kvk.schemas.schemas.hrip.dataservice._2015._02.DataserviceService;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.ws.addressing.AddressingProperties;
import org.apache.cxf.ws.addressing.AttributedURIType;
import org.apache.cxf.ws.addressing.EndpointReferenceType;
import org.apache.cxf.ws.addressing.JAXWSAConstants;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
import org.apache.wss4j.common.ext.WSPasswordCallback;
import org.apache.wss4j.dom.handler.WSHandlerConstants;

public class NHRLoadUtils {
  public static BrmoFramework getFramework(NHRDatabaseOptions databaseOptions)
      throws BrmoException {
    BasicDataSource dsStaging = new BasicDataSource();

    dsStaging.setUrl(databaseOptions.getConnectionString());
    dsStaging.setUsername(databaseOptions.getUser());
    dsStaging.setPassword(databaseOptions.getPassword());

    BrmoFramework fw = new BrmoFramework(dsStaging, null, null);
    fw.setOrderBerichten(true);
    return fw;
  }

  private static Properties getCryptoProperties(
      NHRCertificateOptions certificateOptions, String alias) {
    Properties props = new Properties();

    props.setProperty(
        "org.apache.ws.security.crypto.provider",
        "org.apache.ws.security.components.crypto.Merlin");
    props.setProperty(
        "org.apache.ws.security.crypto.merlin.keystore.file", certificateOptions.getKeystore());
    props.setProperty(
        "org.apache.ws.security.crypto.merlin.keystore.password",
        certificateOptions.getKeystorePassword());
    props.setProperty("org.apache.ws.security.crypto.merlin.keystore.type", "PKCS12");
    props.setProperty("org.apache.ws.security.crypto.merlin.keystore.alias", alias);

    return props;
  }

  public static Dataservice getDataservice(
      String targetLocation, boolean preprod, NHRCertificateOptions certificateOptions)
      throws Exception {
    DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
    documentBuilderFactory.setNamespaceAware(true);

    DataserviceService dataServiceService = new DataserviceService();
    Dataservice dataService = dataServiceService.getDataserviceSoap11();
    Client client = ClientProxy.getClient(dataService);
    Endpoint endpoint = client.getEndpoint();
    HTTPConduit http = (HTTPConduit) client.getConduit();

    // Set up TLS certificates/keys
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
    KeyStore trustStore = KeyStore.getInstance("PKCS12");

    try (FileInputStream trustStoreFile = new FileInputStream(certificateOptions.getTruststore())) {
      trustStore.load(trustStoreFile, certificateOptions.getTruststorePassword().toCharArray());
    }
    trustManagerFactory.init(trustStore);

    KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
    KeyStore keyStore = KeyStore.getInstance("PKCS12");

    try (FileInputStream keyStoreFile = new FileInputStream(certificateOptions.getKeystore())) {
      keyStore.load(keyStoreFile, certificateOptions.getKeystorePassword().toCharArray());
    }
    String alias = certificateOptions.getKeystoreAlias();
    if (alias == null || alias.isBlank()) {
      alias = keyStore.aliases().nextElement();
    }
    keyManagerFactory.init(keyStore, certificateOptions.getKeystorePassword().toCharArray());

    SSLContext context = SSLContext.getInstance("TLSv1.2");
    context.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);

    TLSClientParameters tlsClientParameters = new TLSClientParameters();
    tlsClientParameters.setSSLSocketFactory(context.getSocketFactory());
    http.setTlsClientParameters(tlsClientParameters);

    Map<String, Object> props = new HashMap<>();

    props.put(
        WSHandlerConstants.ACTION,
        WSHandlerConstants.TIMESTAMP + " " + WSHandlerConstants.SIGNATURE);
    props.put(WSHandlerConstants.INCLUDE_SIGNATURE_TOKEN, "true");
    props.put(WSHandlerConstants.SIG_KEY_ID, "DirectReference");
    props.put(WSHandlerConstants.USE_SINGLE_CERTIFICATE, "false");

    props.put(WSHandlerConstants.SIG_PROP_REF_ID, "signatureProperties");
    props.put("signatureProperties", getCryptoProperties(certificateOptions, alias));

    props.put(
        WSHandlerConstants.SIGNATURE_PARTS,
        "{}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;"
            + "{}{http://schemas.xmlsoap.org/soap/envelope/}Body;"
            + "{}{http://www.w3.org/2005/08/addressing}To;"
            + "{}{http://www.w3.org/2005/08/addressing}ReplyTo;"
            + "{}{http://www.w3.org/2005/08/addressing}MessageID;"
            + "{}{http://www.w3.org/2005/08/addressing}Action");

    props.put(WSHandlerConstants.USER, alias);
    props.put(
        WSHandlerConstants.PW_CALLBACK_REF,
        (CallbackHandler)
            callbacks -> {
              for (Callback callback : callbacks) {
                ((WSPasswordCallback) callback)
                    .setPassword(certificateOptions.getKeystorePassword());
                return;
              }
            });

    WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor(props);
    endpoint.getOutInterceptors().add(wssOut);

    // Set up necessary WS-Addressing fields.
    AddressingProperties maps = new AddressingProperties();
    EndpointReferenceType anonref = new EndpointReferenceType();
    AttributedURIType anonymous = new AttributedURIType();
    anonymous.setValue("http://www.w3.org/2005/08/addressing/anonymous");
    anonref.setAddress(anonymous);
    maps.setReplyTo(anonref);
    maps.setFaultTo(anonref);

    // <wsa:To> needs to point at a predefined value.
    EndpointReferenceType toval = new EndpointReferenceType();
    AttributedURIType target = new AttributedURIType();
    target.setValue(
        preprod
            ? "http://es.kvk.nl/kvk-DataservicePP/2015/02"
            : "http://es.kvk.nl/kvk-Dataservice/2015/02");
    toval.setAddress(target);
    maps.setTo(toval);

    BindingProvider bindingProvider = (BindingProvider) dataService;
    bindingProvider
        .getRequestContext()
        .put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, targetLocation);
    bindingProvider.getRequestContext().put(JAXWSAConstants.CLIENT_ADDRESSING_PROPERTIES, maps);

    return dataService;
  }
}