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