package ai.vespa.hosted.auth;

import com.yahoo.config.provision.SystemName;
import com.yahoo.security.KeyUtils;
import com.yahoo.security.SslContextBuilder;
import com.yahoo.security.X509CertificateUtils;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.Optional;

import static ai.vespa.hosted.api.Properties.getNonBlankProperty;

/**
 * Authenticates against the hosted Vespa API using private key signatures, and against Vespa applications using mutual TLS.
 *
 * @author jonmv
 */
public class EndpointAuthenticator implements ai.vespa.hosted.api.EndpointAuthenticator {

    /** Don't touch. */
    public EndpointAuthenticator(@SuppressWarnings("unused") SystemName __) { }

    /**
     * If {@code System.getProperty("vespa.test.credentials.root")} is set, key and certificate files
     * "key" and "cert" in that directory are used; otherwise, the system default SSLContext is returned.
     */
    @Override
    public SSLContext sslContext() {
        try {
            Optional<String> credentialsRootProperty = getNonBlankProperty("vespa.test.credentials.root");
            if (credentialsRootProperty.isEmpty())
                return SSLContext.getDefault();

            Path credentialsRoot = Path.of(credentialsRootProperty.get());
            Path certificateFile = credentialsRoot.resolve("cert");
            Path privateKeyFile = credentialsRoot.resolve("key");

            X509Certificate certificate = X509CertificateUtils.fromPem(new String(Files.readAllBytes(certificateFile)));
            if (   Instant.now().isBefore(certificate.getNotBefore().toInstant())
                || Instant.now().isAfter(certificate.getNotAfter().toInstant()))
                throw new IllegalStateException("Certificate at '" + certificateFile + "' is valid between " +
                                                certificate.getNotBefore() + " and " + certificate.getNotAfter() + " — not now.");

            PrivateKey privateKey = KeyUtils.fromPemEncodedPrivateKey(new String(Files.readAllBytes(privateKeyFile)));
            return new SslContextBuilder().withKeyStore(privateKey, certificate).build();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
        catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException(e);
        }
    }

}

@@ -528,16 +528,49 @@
 ptional;
+%0Aimport java.util.logging.Logger;
 %0A%0Aimport
@@ -874,16 +874,115 @@
 ator %7B%0A%0A
+    private static final Logger logger = Logger.getLogger(EndpointAuthenticator.class.getName());%0A%0A
     /** 
@@ -1367,16 +1367,97 @@
   try %7B%0A
+            Path certificateFile = null;%0A            Path privateKeyFile = null;%0A
         
@@ -1601,66 +1601,25 @@
 y.is
-Empty
+Present
 ())
+ %7B
 %0A    
-            return SSLContext.getDefault();%0A%0A
     
@@ -1693,36 +1693,35 @@
 ));%0A            
+ 
-Path
+  
  certificateFile
@@ -1760,36 +1760,35 @@
 %22);%0A            
-Path
+   
  privateKeyFile 
@@ -1817,25 +1817,618 @@
 lve(%22key%22);%0A
-%0A
+            %7D%0A            else %7B%0A                Optional%3CString%3E certificateFileProperty = getNonBlankProperty(%22dataPlaneCertificateFile%22);%0A                if (certificateFileProperty.isPresent())%0A                    certificateFile = Path.of(certificateFileProperty.get());%0A                Optional%3CString%3E privateKeyFileProperty = getNonBlankProperty(%22dataPlaneKeyFile%22);%0A                if (privateKeyFileProperty.isPresent())%0A                    privateKeyFile = Path.of(privateKeyFileProperty.get());%0A            %7D%0A            if (certificateFile != null && privateKeyFile != null) %7B%0A    
             
@@ -2544,24 +2544,28 @@
             
+ 
+   
 if (   Insta
@@ -2638,16 +2638,20 @@
         
+    
 %7C%7C Insta
@@ -2707,16 +2707,20 @@
 ant()))%0A
+    
         
@@ -2862,32 +2862,36 @@
                 
+    
 certificate.getN
@@ -2954,24 +2954,28 @@
 ot now.%22);%0A%0A
+    
             
@@ -3073,32 +3073,36 @@
 vateKeyFile)));%0A
+    
             retu
@@ -3175,16 +3175,770 @@
 uild();%0A
+            %7D%0A            logger.warning(  %22##################################################################################%5Cn%22%0A                           + %22# Data plane key and/or certificate missing; please specify                      #%5Cn%22%0A                           + %22# '-DdataPlaneCertificateFile=/path/to/certificate' and                          #%5Cn%22%0A                           + %22# '-DdataPlaneKeyFile=/path/to/private_key.                                      #%5Cn%22%0A                           + %22# Trying the default SSLContext, but this will most likely cause HTTP error 401. #%5Cn%22%0A                           + %22##################################################################################%22);%0A            return SSLContext.getDefault();%0A
         

Support aliases for key and cert independently, and WARN when nothing found