// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.tls;

import com.google.inject.Inject;
import com.yahoo.component.AbstractComponent;
import com.yahoo.container.jdisc.secretstore.SecretStore;
import com.yahoo.jdisc.http.ssl.SslContextFactoryProvider;
import com.yahoo.security.KeyStoreBuilder;
import com.yahoo.security.KeyStoreType;
import com.yahoo.security.KeyUtils;
import com.yahoo.security.X509CertificateUtils;
import com.yahoo.vespa.hosted.controller.tls.config.TlsConfig;
import org.eclipse.jetty.util.ssl.SslContextFactory;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Configures the controller's HTTPS connector with certificate and private key from a secret store.
 *
 * @author mpolden
 * @author bjorncs
 */
@SuppressWarnings("unused") // Injected
public class ControllerSslContextFactoryProvider extends AbstractComponent implements SslContextFactoryProvider {

    private final KeyStore truststore;
    private final KeyStore keystore;
    private final Map<Integer, SslContextFactory> sslContextFactories = new ConcurrentHashMap<>();

    @Inject
    public ControllerSslContextFactoryProvider(SecretStore secretStore, TlsConfig config) {
        if (!Files.isReadable(Paths.get(config.caTrustStore()))) {
            throw new IllegalArgumentException("CA trust store file is not readable: " + config.caTrustStore());
        }
        // Trust store containing CA trust store from file
        this.truststore = KeyStoreBuilder.withType(KeyStoreType.JKS)
                .fromFile(Paths.get(config.caTrustStore()))
                .build();
        // Key store containing key pair from secret store
        this.keystore = KeyStoreBuilder.withType(KeyStoreType.JKS)
                .withKeyEntry(getClass().getSimpleName(), privateKey(secretStore, config), certificates(secretStore, config))
                .build();
    }

    @Override
    public SslContextFactory getInstance(String containerId, int port) {
        return sslContextFactories.computeIfAbsent(port, this::createSslContextFactory);
    }

    /** Create a SslContextFactory backed by an in-memory key and trust store */
    private SslContextFactory createSslContextFactory(int port) {
        // TODO Use DefaultTlsContext to configure SslContextFactory (ensure that cipher/protocol configuration is same across all TLS endpoints).

        SslContextFactory.Server factory = new SslContextFactory.Server();
        if (port != 443) {
            factory.setWantClientAuth(true);
        }
        factory.setTrustStore(truststore);
        factory.setKeyStore(keystore);
        factory.setKeyStorePassword("");
        factory.setExcludeProtocols("TLSv1.3"); // TLSv1.3 is broken is multiple OpenJDK 11 versions
        factory.setEndpointIdentificationAlgorithm(null); // disable https hostname verification of clients (must be disabled when using Athenz x509 certificates)
        return factory;
    }

    /** Get private key from secret store **/
    private static PrivateKey privateKey(SecretStore secretStore, TlsConfig config) {
        return KeyUtils.fromPemEncodedPrivateKey(secretStore.getSecret(config.privateKeySecret()));
    }

    /**
     * Get certificate from secret store. If certificate secret contains multiple certificates, e.g. intermediate
     * certificates, the entire chain will be read
     */
    private static List<X509Certificate> certificates(SecretStore secretStore, TlsConfig config) {
        return X509CertificateUtils.certificateListFromPem(secretStore.getSecret(config.certificateSecret()));
    }

}

@@ -197,54 +197,8 @@
 ct;%0A
-import com.yahoo.component.AbstractComponent;%0A
 impo
@@ -283,33 +283,36 @@
 ttp.ssl.
-SslContextFactory
+impl.TlsContextBased
 Provider
@@ -428,24 +428,69 @@
 y.KeyUtils;%0A
+import com.yahoo.security.SslContextBuilder;%0A
 import com.y
@@ -521,24 +521,165 @@
 icateUtils;%0A
+import com.yahoo.security.tls.DefaultTlsContext;%0Aimport com.yahoo.security.tls.PeerAuthentication;%0Aimport com.yahoo.security.tls.TlsContext;%0A
 import com.y
@@ -732,61 +732,8 @@
 fig;
-%0Aimport org.eclipse.jetty.util.ssl.SslContextFactory;
 %0A%0Aim
@@ -1237,54 +1237,23 @@
 nds 
-AbstractComponent implements SslContextFactory
+TlsContextBased
 Prov
@@ -1371,46 +1371,33 @@
 er, 
-Ssl
+Tls
 Context
-Factory%3E sslContextFactories
+%3E tlsContextMap
  = n
@@ -2239,43 +2239,41 @@
    p
-ublic SslContextFactory getInstance
+rotected TlsContext getTlsContext
 (Str
@@ -2320,27 +2320,21 @@
 urn 
-ssl
+tls
 Context
-Factories
+Map
 .com
@@ -2364,33 +2364,26 @@
 ::create
-Ssl
+Tls
 Context
-Factory
 );%0A    %7D
@@ -2388,89 +2388,8 @@
  %7D%0A%0A
-    /** Create a SslContextFactory backed by an in-memory key and trust store */%0A
     
@@ -2400,49 +2400,35 @@
 ate 
-Ssl
+Tls
 Context
-Factory
  create
-Ssl
+Tls
 Context
-Factory
 (int
@@ -2448,19 +2448,18 @@
     
-// TODO Use
+return new
  Def
@@ -2476,161 +2476,25 @@
 text
- to configure SslContextFactory (ensure that cipher/protocol configuration is same across all TLS endpoints).%0A%0A        SslContextFactory.Server factory =
+(%0A               
  new
@@ -2508,25 +2508,17 @@
 text
-Factory.Serv
+Build
 er()
-;
 %0A   
@@ -2526,80 +2526,71 @@
     
-if (port != 443) %7B%0A            factory.setWantClientAuth(true);%0A
+                .withKeyStore(keystore, new char%5B0%5D)%0A  
         
 %7D%0A  
@@ -2585,26 +2585,30 @@
         
-%7D%0A
+      
         
 factory.
@@ -2599,27 +2599,21 @@
         
-factory.set
+.with
 TrustSto
@@ -2622,25 +2622,24 @@
 (truststore)
-;
 %0A        fac
@@ -2639,366 +2639,118 @@
     
-factory.setKeyStore(keystore);%0A        factory.setKeyStorePassword(%22%22);%0A        factory.setExcludeProtocols(%22TLSv1.3%22); // TLSv1.3 is broken is multiple OpenJDK 11 versions%0A        factory.setEndpointIdentificationAlgorithm(null); // disable https hostname verification of clients (must be disabled when using Athenz x509 certificates)%0A        return factory
+                .build(),%0A                port != 443 ? PeerAuthentication.WANT : PeerAuthentication.DISABLED)
 ;%0A  

Use TlsContextBasedProvider in ControllerSslContextFactoryProvider