Merge "Generate client certificate in runtime" into main
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/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index bdc2276..90b5a78 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -62,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,
@@ -181,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() {
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