Generate client certificate in runtime

1. Generate client certificate in runtime
2. Write it to app data
3. (guest) mount 'internal' which refers to app data files
4. (guest) ttyd uses the cert from that

Bug: 363235314
Bug: 372171883
Test: Run terminal, and then check if it works well
Change-Id: Ibe2b38637781fee9a1c142d4c8fe063ae3c8612b
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 0b130f5..9aa089c 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -61,18 +61,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,
@@ -179,29 +175,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