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