| chojoyce | b081703 | 2022-01-12 18:29:36 +0800 | [diff] [blame] | 1 | # Copyright 2022 - The Android Open Source Project | 
 | 2 | # | 
 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | 
 | 4 | # you may not use this file except in compliance with the License. | 
 | 5 | # You may obtain a copy of the License at | 
 | 6 | # | 
 | 7 | #     http://www.apache.org/licenses/LICENSE-2.0 | 
 | 8 | # | 
 | 9 | # Unless required by applicable law or agreed to in writing, software | 
 | 10 | # distributed under the License is distributed on an "AS IS" BASIS, | 
 | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | 12 | # See the License for the specific language governing permissions and | 
 | 13 | # limitations under the License. | 
 | 14 | r"""Mkcert entry point. | 
 | 15 |  | 
 | 16 | Mkcert will handle the SSL certificates process to secure WEB browser of | 
 | 17 | a local or remote instance of an Android Virtual Device. | 
 | 18 | """ | 
 | 19 |  | 
 | 20 | import filecmp | 
 | 21 | import logging | 
 | 22 | import os | 
 | 23 | import platform | 
| chojoyce | 32ee70e | 2022-02-11 12:39:53 +0800 | [diff] [blame] | 24 | import shutil | 
| chojoyce | 8c7f7ad | 2022-02-18 19:55:13 +0800 | [diff] [blame] | 25 | import stat | 
| chojoyce | b081703 | 2022-01-12 18:29:36 +0800 | [diff] [blame] | 26 |  | 
 | 27 | from acloud.internal import constants | 
 | 28 | from acloud.internal.lib import utils | 
 | 29 |  | 
 | 30 | logger = logging.getLogger(__name__) | 
 | 31 |  | 
 | 32 | _CA_NAME = constants.SSL_CA_NAME | 
 | 33 | _CERT_DIR = os.path.join(os.path.expanduser("~"), constants.SSL_DIR) | 
 | 34 | _CA_KEY_PATH = os.path.join(_CERT_DIR, f"{_CA_NAME}.key") | 
 | 35 | _CA_CRT_PATH = os.path.join(_CERT_DIR, f"{_CA_NAME}.pem") | 
 | 36 | _CERT_KEY_PATH = os.path.join(_CERT_DIR, "server.key") | 
 | 37 | _CERT_CSR_PATH = os.path.join(_CERT_DIR, "server.csr") | 
 | 38 | _CERT_CRT_PATH = os.path.join(_CERT_DIR, "server.crt") | 
 | 39 | _CA_EXT = "keyUsage=critical,keyCertSign" | 
 | 40 | _CA_SUBJ="/OU=acloud/O=acloud development CA/CN=localhost" | 
 | 41 | _CERT_SUBJ = "/OU=%s/O=acloud development CA" % platform.node() | 
 | 42 | _TRUST_CA_PATH = os.path.join(constants.SSL_TRUST_CA_DIR, | 
 | 43 |                               f"{_CA_NAME}.crt") | 
 | 44 | _CERT_CRT_EXT = ";".join(f"echo \"{ext}\"" for ext in [ | 
 | 45 |     "keyUsage = critical, digitalSignature, keyEncipherment", | 
 | 46 |     "extendedKeyUsage = serverAuth", | 
 | 47 |     "subjectAltName = DNS.1:localhost, IP.1:0.0.0.0, IP.2:::1"]) | 
 | 48 |  | 
 | 49 | # Generate a Root SSL Certificate. | 
 | 50 | _CA_CMD = (f"openssl req -new -x509 -days 9999 -newkey rsa:2048 " | 
 | 51 |            f"-sha256 -nodes -keyout \"{_CA_KEY_PATH}\" " | 
 | 52 |            f"-out \"{_CA_CRT_PATH}\" -extensions v3_ca " | 
 | 53 |            f"-subj \"{_CA_SUBJ}\" -addext \"{_CA_EXT}\"") | 
 | 54 |  | 
 | 55 | # Trust the Root SSL Certificate. | 
| chojoyce | 71855af | 2022-02-16 17:18:32 +0800 | [diff] [blame] | 56 | _TRUST_CA_COPY_CMD = f"sudo cp -p {_CA_CRT_PATH} {_TRUST_CA_PATH}" | 
| chojoyce | b081703 | 2022-01-12 18:29:36 +0800 | [diff] [blame] | 57 | _UPDATE_TRUST_CA_CMD = "sudo update-ca-certificates" | 
 | 58 | _TRUST_CHROME_CMD = ( | 
 | 59 |     "certutil -d sql:$HOME/.pki/nssdb -A -t TC " | 
 | 60 |     f"-n \"{_CA_NAME}\" -i \"{_TRUST_CA_PATH}\"") | 
 | 61 |  | 
 | 62 | # Generate an SSL SAN Certificate with the Root Certificate. | 
 | 63 | _CERT_KEY_CMD = f"openssl genrsa -out \"{_CERT_KEY_PATH}\" 2048" | 
 | 64 | _CERT_CSR_CMD = (f"openssl req -new -key \"{_CERT_KEY_PATH}\" " | 
 | 65 |                  f"-out \"{_CERT_CSR_PATH}\" -subj \"{_CERT_SUBJ}\"") | 
 | 66 | _CERT_CRT_CMD = ( | 
 | 67 |     f"openssl x509 -req -days 9999 -in \"{_CERT_CSR_PATH}\" " | 
 | 68 |     f"-CA \"{_CA_CRT_PATH}\" -CAkey \"{_CA_KEY_PATH}\" " | 
 | 69 |     f"-CAcreateserial -out \"{_CERT_CRT_PATH}\" " | 
 | 70 |     f"-extfile <({_CERT_CRT_EXT};)") | 
 | 71 |  | 
 | 72 | # UnInstall the Root SSL Certificate. | 
 | 73 | _UNDO_TRUST_CA_CMD = f"sudo rm {_TRUST_CA_PATH}" | 
 | 74 | _UNDO_TRUST_CHROME_CMD = f"certutil -D -d sql:$HOME/.pki/nssdb -n \"{_CA_NAME}\"" | 
 | 75 |  | 
 | 76 |  | 
 | 77 | def Install(): | 
 | 78 |     """Install Root SSL Certificates by the openssl tool. | 
 | 79 |  | 
 | 80 |     Generates a Root SSL Certificates and setup the host environment | 
 | 81 |     to build a secure browser for WebRTC AVD. | 
 | 82 |  | 
 | 83 |     Returns: | 
 | 84 |         True when the Root SSL Certificates are generated and setup. | 
 | 85 |     """ | 
| chojoyce | 32ee70e | 2022-02-11 12:39:53 +0800 | [diff] [blame] | 86 |     if os.path.isdir(_CERT_DIR): | 
 | 87 |         shutil.rmtree(_CERT_DIR) | 
 | 88 |     os.mkdir(_CERT_DIR) | 
| chojoyce | b081703 | 2022-01-12 18:29:36 +0800 | [diff] [blame] | 89 |  | 
 | 90 |     if os.path.exists(_TRUST_CA_PATH): | 
 | 91 |         UnInstall() | 
 | 92 |  | 
| chojoyce | 32ee70e | 2022-02-11 12:39:53 +0800 | [diff] [blame] | 93 |     utils.Popen(_CA_CMD, shell=True) | 
| chojoyce | 8c7f7ad | 2022-02-18 19:55:13 +0800 | [diff] [blame] | 94 |     # The rootCA.pem file should grant READ permission to others. | 
 | 95 |     if not os.stat(_CA_CRT_PATH).st_mode & stat.S_IROTH: | 
 | 96 |         os.chmod(_CA_CRT_PATH, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) | 
| chojoyce | 32ee70e | 2022-02-11 12:39:53 +0800 | [diff] [blame] | 97 |     utils.Popen(_TRUST_CA_COPY_CMD, shell=True) | 
 | 98 |     utils.Popen(_UPDATE_TRUST_CA_CMD, shell=True) | 
 | 99 |     utils.Popen(_TRUST_CHROME_CMD, shell=True) | 
| chojoyce | b081703 | 2022-01-12 18:29:36 +0800 | [diff] [blame] | 100 |  | 
 | 101 |     return IsRootCAReady() | 
 | 102 |  | 
 | 103 |  | 
 | 104 | def AllocateLocalHostCert(): | 
 | 105 |     """Allocate localhost certificate by the openssl tool. | 
 | 106 |  | 
 | 107 |     Generate an SSL SAN Certificate with the Root Certificate. | 
 | 108 |  | 
 | 109 |     Returns: | 
 | 110 |         True if the certificates is exist. | 
 | 111 |     """ | 
 | 112 |     if not IsRootCAReady(): | 
 | 113 |         logger.debug("Can't load CA files.") | 
 | 114 |         return False | 
 | 115 |  | 
 | 116 |     if not os.path.exists(_CERT_KEY_PATH): | 
 | 117 |         utils.Popen(_CERT_KEY_CMD, shell=True) | 
 | 118 |     if not os.path.exists(_CERT_CSR_PATH): | 
 | 119 |         utils.Popen(_CERT_CSR_CMD, shell=True) | 
 | 120 |     if not os.path.exists(_CERT_CRT_PATH): | 
 | 121 |         utils.Popen(_CERT_CRT_CMD, shell=True) | 
 | 122 |  | 
 | 123 |     return IsCertificateReady() | 
 | 124 |  | 
 | 125 |  | 
 | 126 | def IsRootCAReady(): | 
 | 127 |     """Check if the Root SSL Certificates are all ready. | 
 | 128 |  | 
 | 129 |     Returns: | 
 | 130 |         True if the Root SSL Certificates are exist. | 
 | 131 |     """ | 
 | 132 |     for cert_file_name in [_CA_KEY_PATH, _CA_CRT_PATH, _TRUST_CA_PATH]: | 
 | 133 |         if not os.path.exists(cert_file_name): | 
 | 134 |             logger.debug("Root SSL Certificate: %s, does not exist", | 
 | 135 |                          cert_file_name) | 
 | 136 |             return False | 
| chojoyce | 8c7f7ad | 2022-02-18 19:55:13 +0800 | [diff] [blame] | 137 |     # TODO: this check can be delete when the mkcert mechanism is stable. | 
 | 138 |     if not os.stat(_TRUST_CA_PATH).st_mode & stat.S_IROTH: | 
 | 139 |         return False | 
| chojoyce | b081703 | 2022-01-12 18:29:36 +0800 | [diff] [blame] | 140 |  | 
 | 141 |     if not filecmp.cmp(_CA_CRT_PATH, _TRUST_CA_PATH): | 
 | 142 |         logger.debug("The trusted CA %s file is not the same with %s ", | 
 | 143 |                      _TRUST_CA_PATH, _CA_CRT_PATH) | 
 | 144 |         return False | 
 | 145 |     return True | 
 | 146 |  | 
 | 147 |  | 
 | 148 | def IsCertificateReady(): | 
 | 149 |     """Check if the SSL SAN Certificates files are all ready. | 
 | 150 |  | 
 | 151 |     Returns: | 
 | 152 |         True if the SSL SAN Certificates files existed. | 
 | 153 |     """ | 
 | 154 |     for cert_file_name in [_CERT_KEY_PATH, _CERT_CRT_PATH]: | 
 | 155 |         if not os.path.exists(cert_file_name): | 
 | 156 |             logger.debug("SSL SAN Certificate: %s, does not exist", | 
 | 157 |                          cert_file_name) | 
 | 158 |             return False | 
 | 159 |     return True | 
 | 160 |  | 
 | 161 |  | 
 | 162 | def UnInstall(): | 
 | 163 |     """Uninstall a Root SSL Certificate. | 
 | 164 |  | 
 | 165 |     Undo the Root SSL Certificate host setup. | 
 | 166 |     """ | 
 | 167 |     utils.Popen(_UNDO_TRUST_CA_CMD, shell=True) | 
 | 168 |     utils.Popen(_UPDATE_TRUST_CA_CMD, shell=True) | 
 | 169 |     utils.Popen(_UNDO_TRUST_CHROME_CMD, shell=True) |