Merge "Reload for the case of ERROR_FAILED_SSL_HANDSHAKE as well" into main
diff --git a/android/TerminalApp/Android.bp b/android/TerminalApp/Android.bp
index 09287d8..bf93226 100644
--- a/android/TerminalApp/Android.bp
+++ b/android/TerminalApp/Android.bp
@@ -24,9 +24,7 @@
     platform_apis: true,
     privileged: true,
     optimize: {
-        optimize: true,
-        proguard_flags_files: ["proguard.flags"],
-        shrink_resources: true,
+        enabled: false,
     },
     apex_available: [
         "com.android.virt",
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/CertificateUtils.java b/android/TerminalApp/java/com/android/virtualization/terminal/CertificateUtils.java
new file mode 100644
index 0000000..01d2afa
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/CertificateUtils.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * 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.android.virtualization.terminal;
+
+import android.content.Context;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.util.Base64;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+
+public class CertificateUtils {
+    private static final String TAG = "CertificateUtils";
+
+    private static final String ALIAS = "ttyd";
+
+    public static KeyStore.PrivateKeyEntry createOrGetKey() {
+        try {
+            KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
+            ks.load(null);
+
+            if (!ks.containsAlias(ALIAS)) {
+                Log.d(TAG, "there is no keypair, will generate it");
+                createKey();
+            } else if (!(ks.getCertificate(ALIAS) instanceof X509Certificate)) {
+                Log.d(TAG, "certificate isn't X509Certificate or it is invalid");
+                createKey();
+            } else {
+                try {
+                    ((X509Certificate) ks.getCertificate(ALIAS)).checkValidity();
+                } catch (CertificateExpiredException | CertificateNotYetValidException e) {
+                    Log.d(TAG, "certificate is invalid", e);
+                    createKey();
+                }
+            }
+            return ((KeyStore.PrivateKeyEntry) ks.getEntry(ALIAS, null));
+        } catch (Exception e) {
+            Log.e(TAG, "cannot generate or get key", e);
+        }
+        return null;
+    }
+
+    private static void createKey()
+            throws NoSuchAlgorithmException,
+                    NoSuchProviderException,
+                    InvalidAlgorithmParameterException {
+        KeyPairGenerator kpg =
+                KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
+        kpg.initialize(
+                new KeyGenParameterSpec.Builder(
+                                ALIAS, KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                        .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
+                        .build());
+
+        kpg.generateKeyPair();
+    }
+
+    public static void writeCertificateToFile(Context context, Certificate cert) {
+        String certFileName = "ca.crt";
+        File certFile = new File(context.getFilesDir(), certFileName);
+        try (FileOutputStream writer = new FileOutputStream(certFile)) {
+            String cert_begin = "-----BEGIN CERTIFICATE-----\n";
+            String end_cert = "-----END CERTIFICATE-----\n";
+            String output =
+                    cert_begin
+                            + Base64.encodeToString(cert.getEncoded(), Base64.DEFAULT)
+                                    .replaceAll("(.{64})", "$1\n")
+                            + end_cert;
+            writer.write(output.getBytes());
+        } catch (IOException | CertificateEncodingException e) {
+            Log.d(TAG, "cannot write cert", e);
+        }
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
index 428fd91..c8f5bab 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerActivity.java
@@ -23,6 +23,7 @@
 import android.content.ServiceConnection;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.ConditionVariable;
 import android.os.FileUtils;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -39,6 +40,7 @@
     private static final String TAG = "LinuxInstaller";
 
     private static final long ESTIMATED_IMG_SIZE_BYTES = FileUtils.parseSize("350MB");
+    static final String EXTRA_AUTO_DOWNLOAD = "auto_download";
 
     private ExecutorService mExecutorService;
     private CheckBox mWaitForWifiCheckbox;
@@ -48,6 +50,7 @@
     private ServiceConnection mInstallerServiceConnection;
     private InstallProgressListener mInstallProgressListener;
     private boolean mInstallRequested;
+    private ConditionVariable mInstallCompleted = new ConditionVariable();
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -72,11 +75,17 @@
                     requestInstall();
                 });
 
+        if (getIntent().getBooleanExtra(EXTRA_AUTO_DOWNLOAD, false)) {
+            Log.i(TAG, "Auto downloading");
+            requestInstall();
+        }
+
         Intent intent = new Intent(this, InstallerService.class);
         mInstallerServiceConnection = new InstallerServiceConnection(this);
         if (!bindService(intent, mInstallerServiceConnection, Context.BIND_AUTO_CREATE)) {
             handleCriticalError(new Exception("Failed to connect to installer service"));
         }
+
     }
 
     @Override
@@ -89,6 +98,10 @@
         super.onDestroy();
     }
 
+    public boolean waitForInstallCompleted(long timeoutMillis) {
+        return mInstallCompleted.block(timeoutMillis);
+    }
+
     public void handleCriticalError(Exception e) {
         if (Build.isDebuggable()) {
             Toast.makeText(
@@ -102,6 +115,9 @@
     }
 
     private void finishWithResult(int resultCode) {
+        if (resultCode == RESULT_OK) {
+            mInstallCompleted.open();
+        }
         setResult(resultCode);
         finish();
     }
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index 9a2f7ff..57b6ff2 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -29,6 +29,7 @@
 import android.net.Uri;
 import android.net.http.SslError;
 import android.os.Bundle;
+import android.os.ConditionVariable;
 import android.os.Environment;
 import android.provider.Settings;
 import android.system.ErrnoException;
@@ -61,18 +62,14 @@
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.RandomAccessFile;
 import java.net.InetAddress;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.UnknownHostException;
-import java.security.Key;
 import java.security.KeyStore;
 import java.security.PrivateKey;
-import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
-import java.util.Enumeration;
 
 public class MainActivity extends BaseActivity
         implements VmLauncherServices.VmLauncherServiceCallback,
@@ -88,6 +85,7 @@
     private PrivateKey mPrivateKey;
     private WebView mWebView;
     private AccessibilityManager mAccessibilityManager;
+    private ConditionVariable mBootCompleted = new ConditionVariable();
     private static final int POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE = 101;
     private ActivityResultLauncher<Intent> manageExternalStorageActivityResultLauncher;
 
@@ -179,29 +177,11 @@
     }
 
     private void readClientCertificate() {
-        // TODO(b/363235314): instead of using the key in asset, it should be generated in runtime
-        // and then provisioned in the vm via virtio-fs
-        try (InputStream keystoreFileStream =
-                getClass().getResourceAsStream("/assets/client.p12")) {
-            KeyStore keyStore = KeyStore.getInstance("PKCS12");
-            String password = "1234";
-
-            keyStore.load(keystoreFileStream, password != null ? password.toCharArray() : null);
-            Enumeration<String> enumeration = keyStore.aliases();
-            while (enumeration.hasMoreElements()) {
-                String alias = enumeration.nextElement();
-                Key key = keyStore.getKey(alias, password.toCharArray());
-                if (key instanceof PrivateKey) {
-                    mPrivateKey = (PrivateKey) key;
-                    Certificate cert = keyStore.getCertificate(alias);
-                    mCertificates = new X509Certificate[1];
-                    mCertificates[0] = (X509Certificate) cert;
-                    return;
-                }
-            }
-        } catch (Exception e) {
-            Log.e(TAG, e.getMessage());
-        }
+        KeyStore.PrivateKeyEntry pke = CertificateUtils.createOrGetKey();
+        CertificateUtils.writeCertificateToFile(this, pke.getCertificate());
+        mPrivateKey = pke.getPrivateKey();
+        mCertificates = new X509Certificate[1];
+        mCertificates[0] = (X509Certificate) pke.getCertificate();
     }
 
     private void connectToTerminalService() {
@@ -256,6 +236,7 @@
                                             findViewById(R.id.boot_progress)
                                                     .setVisibility(View.GONE);
                                             view.setVisibility(View.VISIBLE);
+                                            mBootCompleted.open();
                                         }
                                     }
                                 });
@@ -524,6 +505,10 @@
         VmLauncherServices.startVmLauncherService(this, this, notification);
     }
 
+    public boolean waitForBootCompleted(long timeoutMillis) {
+        return mBootCompleted.block(timeoutMillis);
+    }
+
     private long roundUpDiskSize(long diskSize) {
         // Round up every disk_size_round_up_step_size_in_mb MB
         int disk_size_step = getResources().getInteger(
diff --git a/build/debian/fai_config/files/etc/systemd/system/ttyd.service/AVF b/build/debian/fai_config/files/etc/systemd/system/ttyd.service/AVF
index 5c7ff9c..a2516ff 100644
--- a/build/debian/fai_config/files/etc/systemd/system/ttyd.service/AVF
+++ b/build/debian/fai_config/files/etc/systemd/system/ttyd.service/AVF
@@ -2,8 +2,9 @@
 Description=TTYD
 After=syslog.target
 After=network.target
+After=virtiofs_internal.service
 [Service]
-ExecStart=/usr/local/bin/ttyd --ssl --ssl-cert /etc/ttyd/server.crt --ssl-key /etc/ttyd/server.key --ssl-ca /etc/ttyd/ca.crt -W login -f droid
+ExecStart=/usr/local/bin/ttyd --ssl --ssl-cert /etc/ttyd/server.crt --ssl-key /etc/ttyd/server.key --ssl-ca /mnt/internal/ca.crt -W login -f droid
 Type=simple
 Restart=always
 User=root
diff --git a/build/debian/fai_config/files/etc/systemd/system/virtiofs_internal.service/AVF b/build/debian/fai_config/files/etc/systemd/system/virtiofs_internal.service/AVF
new file mode 100644
index 0000000..d27f3d2
--- /dev/null
+++ b/build/debian/fai_config/files/etc/systemd/system/virtiofs_internal.service/AVF
@@ -0,0 +1,13 @@
+[Unit]
+Description=Mount virtiofs terminal app internal file path
+After=network.target
+
+[Service]
+Type=oneshot
+User=root
+Group=root
+ExecStart=/bin/bash -c 'mkdir -p /mnt/internal; chown 1000:100 /mnt/internal; mount -t virtiofs internal /mnt/internal'
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
diff --git a/build/debian/fai_config/files/etc/ttyd/ca.crt/AVF b/build/debian/fai_config/files/etc/ttyd/ca.crt/AVF
deleted file mode 100644
index 90d8c0e..0000000
--- a/build/debian/fai_config/files/etc/ttyd/ca.crt/AVF
+++ /dev/null
@@ -1,21 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDhzCCAm+gAwIBAgIUQkvURjf6sU5aJ7oK9usHnJHsc/owDQYJKoZIhvcNAQEL
-BQAwUzELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkdEMQswCQYDVQQHDAJTWjETMBEG
-A1UECgwKQWNtZSwgSW5jLjEVMBMGA1UEAwwMQWNtZSBSb290IENBMB4XDTI0MTAx
-NDAxMjgzN1oXDTI1MTAxNDAxMjgzN1owUzELMAkGA1UEBhMCQ04xCzAJBgNVBAgM
-AkdEMQswCQYDVQQHDAJTWjETMBEGA1UECgwKQWNtZSwgSW5jLjEVMBMGA1UEAwwM
-QWNtZSBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtjgS
-ePtWI6xARLzM1bUMvqtWwY4ci4TzcOcfLfV5Eqbb135NSBKQ+Q2IAguc2Bl3ZVRE
-08GhQ9XJOo+mp2SUY/8+SJpCVhVlWvF6LwXd8X5pZ9GCem0FXY02kMr5ZiTs/CN2
-LZIyJKgXCT/5208on+BbiNp0pk2Pz1nDOdpxvkDJ8UKRWLwqCAEM/rcN1Lc00aln
-N/Rfi/CQE+MDAmhuy/nxr37ldqhkN+xM4bhNs1bjyVposKtbmFUY/SD3ca5CMawU
-E3l5hZ5kfua7lelEPVhvNYJcxffVO0fPNEbUKr1WsPLrnidqegcU8bml1BoCphgA
-qzoxD0rZniqMsom/vwIDAQABo1MwUTAdBgNVHQ4EFgQUZOHF7/arn8ODqEj1Wifk
-dEA5TFkwHwYDVR0jBBgwFoAUZOHF7/arn8ODqEj1WifkdEA5TFkwDwYDVR0TAQH/
-BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVets3IybnIycAtajxpJygdji/95t
-ikdyWbi8lrszC0E5bCR9XPQKnqx/svKYrEVQNihH/nZ6TlTv0f3b77+92sVlmQfl
-a3KKI6qIgcqNEO2lHYsS+cPeBmaM6WXcEPe6gEnan1i5N16B9g9ntY4lOg8Z4roR
-2lVVCCNwabyBxb5oQDsN1IDeJ7JRRZqGGduDSZTvdd36GqNhMvXQjluyJCCFd1Hv
-IwwJmAR2GMUQU8Eoa+zGzW1Inf1YJytTu8SeQ+hYy2QCG88vZigJdifmhETDDz9Q
-xQjp1SCNIBxFHY2voqtiJtfupN5pVieECZS42pbVHMIAUOk7BmNcEWnSKw==
------END CERTIFICATE-----
diff --git a/build/debian/fai_config/scripts/AVF/10-systemd b/build/debian/fai_config/scripts/AVF/10-systemd
index 4a96c3a..1605381 100755
--- a/build/debian/fai_config/scripts/AVF/10-systemd
+++ b/build/debian/fai_config/scripts/AVF/10-systemd
@@ -8,3 +8,4 @@
 ln -s /etc/systemd/system/ip_addr_reporter.service $target/etc/systemd/system/multi-user.target.wants/ip_addr_reporter.service
 ln -s /etc/systemd/system/virtiofs.service $target/etc/systemd/system/multi-user.target.wants/virtiofs.service
 ln -s /etc/systemd/system/forwarder_guest_launcher.service $target/etc/systemd/system/multi-user.target.wants/forwarder_guest_launcher.service
+ln -s /etc/systemd/system/virtiofs_internal.service $target/etc/systemd/system/multi-user.target.wants/virtiofs_internal.service