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. 14r"""Mkcert entry point. 15 16Mkcert will handle the SSL certificates process to secure WEB browser of 17a local or remote instance of an Android Virtual Device. 18""" 19 20import filecmp 21import logging 22import os 23import platform 24import shutil 25import stat 26 27from acloud.internal import constants 28from acloud.internal.lib import utils 29 30logger = 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. 56_TRUST_CA_COPY_CMD = f"sudo cp -p {_CA_CRT_PATH} {_TRUST_CA_PATH}" 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 77def 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 """ 86 if os.path.isdir(_CERT_DIR): 87 shutil.rmtree(_CERT_DIR) 88 os.mkdir(_CERT_DIR) 89 90 if os.path.exists(_TRUST_CA_PATH): 91 UnInstall() 92 93 utils.Popen(_CA_CMD, shell=True) 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) 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) 100 101 return IsRootCAReady() 102 103 104def 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 126def 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 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 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 148def 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 162def 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) 170