1import sys 2import logging 3import os 4import ssl 5import hashlib 6import random 7 8from Config import config 9from util import helper 10 11 12class CryptConnectionManager: 13 def __init__(self): 14 if sys.platform.startswith("win"): 15 self.openssl_bin = "tools\\openssl\\openssl.exe" 16 elif config.dist_type.startswith("bundle_linux"): 17 self.openssl_bin = "../runtime/bin/openssl" 18 else: 19 self.openssl_bin = "openssl" 20 21 self.openssl_env = { 22 "OPENSSL_CONF": "src/lib/openssl/openssl.cnf", 23 "RANDFILE": config.data_dir + "/openssl-rand.tmp" 24 } 25 26 self.crypt_supported = [] # Supported cryptos 27 28 self.cacert_pem = config.data_dir + "/cacert-rsa.pem" 29 self.cakey_pem = config.data_dir + "/cakey-rsa.pem" 30 self.cert_pem = config.data_dir + "/cert-rsa.pem" 31 self.cert_csr = config.data_dir + "/cert-rsa.csr" 32 self.key_pem = config.data_dir + "/key-rsa.pem" 33 34 self.log = logging.getLogger("CryptConnectionManager") 35 self.log.debug("Version: %s" % ssl.OPENSSL_VERSION) 36 37 self.fakedomains = [ 38 "yahoo.com", "amazon.com", "live.com", "microsoft.com", "mail.ru", "csdn.net", "bing.com", 39 "amazon.co.jp", "office.com", "imdb.com", "msn.com", "samsung.com", "huawei.com", "ztedevices.com", 40 "godaddy.com", "w3.org", "gravatar.com", "creativecommons.org", "hatena.ne.jp", 41 "adobe.com", "opera.com", "apache.org", "rambler.ru", "one.com", "nationalgeographic.com", 42 "networksolutions.com", "php.net", "python.org", "phoca.cz", "debian.org", "ubuntu.com", 43 "nazwa.pl", "symantec.com" 44 ] 45 46 def createSslContexts(self): 47 ciphers = "ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:AES128-SHA256:AES256-SHA:" 48 ciphers += "!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK" 49 50 if hasattr(ssl, "PROTOCOL_TLS"): 51 protocol = ssl.PROTOCOL_TLS 52 else: 53 protocol = ssl.PROTOCOL_TLSv1_2 54 self.context_client = ssl.SSLContext(protocol) 55 self.context_client.check_hostname = False 56 self.context_client.verify_mode = ssl.CERT_NONE 57 58 self.context_server = ssl.SSLContext(protocol) 59 self.context_server.load_cert_chain(self.cert_pem, self.key_pem) 60 61 for ctx in (self.context_client, self.context_server): 62 ctx.set_ciphers(ciphers) 63 ctx.options |= ssl.OP_NO_COMPRESSION 64 try: 65 ctx.set_alpn_protocols(["h2", "http/1.1"]) 66 ctx.set_npn_protocols(["h2", "http/1.1"]) 67 except Exception: 68 pass 69 70 # Select crypt that supported by both sides 71 # Return: Name of the crypto 72 def selectCrypt(self, client_supported): 73 for crypt in self.crypt_supported: 74 if crypt in client_supported: 75 return crypt 76 return False 77 78 # Wrap socket for crypt 79 # Return: wrapped socket 80 def wrapSocket(self, sock, crypt, server=False, cert_pin=None): 81 if crypt == "tls-rsa": 82 if server: 83 sock_wrapped = self.context_server.wrap_socket(sock, server_side=True) 84 else: 85 sock_wrapped = self.context_client.wrap_socket(sock, server_hostname=random.choice(self.fakedomains)) 86 if cert_pin: 87 cert_hash = hashlib.sha256(sock_wrapped.getpeercert(True)).hexdigest() 88 if cert_hash != cert_pin: 89 raise Exception("Socket certificate does not match (%s != %s)" % (cert_hash, cert_pin)) 90 return sock_wrapped 91 else: 92 return sock 93 94 def removeCerts(self): 95 if config.keep_ssl_cert: 96 return False 97 for file_name in ["cert-rsa.pem", "key-rsa.pem", "cacert-rsa.pem", "cakey-rsa.pem", "cacert-rsa.srl", "cert-rsa.csr", "openssl-rand.tmp"]: 98 file_path = "%s/%s" % (config.data_dir, file_name) 99 if os.path.isfile(file_path): 100 os.unlink(file_path) 101 102 # Load and create cert files is necessary 103 def loadCerts(self): 104 if config.disable_encryption: 105 return False 106 107 if self.createSslRsaCert() and "tls-rsa" not in self.crypt_supported: 108 self.crypt_supported.append("tls-rsa") 109 110 # Try to create RSA server cert + sign for connection encryption 111 # Return: True on success 112 def createSslRsaCert(self): 113 casubjects = [ 114 "/C=US/O=Amazon/OU=Server CA 1B/CN=Amazon", 115 "/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3", 116 "/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA", 117 "/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA" 118 ] 119 self.openssl_env['CN'] = random.choice(self.fakedomains) 120 121 if os.path.isfile(self.cert_pem) and os.path.isfile(self.key_pem): 122 self.createSslContexts() 123 return True # Files already exits 124 125 import subprocess 126 127 # Generate CAcert and CAkey 128 cmd_params = helper.shellquote( 129 self.openssl_bin, 130 self.openssl_env["OPENSSL_CONF"], 131 random.choice(casubjects), 132 self.cakey_pem, 133 self.cacert_pem 134 ) 135 cmd = "%s req -new -newkey rsa:2048 -days 3650 -nodes -x509 -config %s -subj %s -keyout %s -out %s -batch" % cmd_params 136 self.log.debug("Generating RSA CAcert and CAkey PEM files...") 137 self.log.debug("Running: %s" % cmd) 138 proc = subprocess.Popen( 139 cmd, shell=True, stderr=subprocess.STDOUT, 140 stdout=subprocess.PIPE, env=self.openssl_env 141 ) 142 back = proc.stdout.read().strip().decode(errors="replace").replace("\r", "") 143 proc.wait() 144 145 if not (os.path.isfile(self.cacert_pem) and os.path.isfile(self.cakey_pem)): 146 self.log.error("RSA ECC SSL CAcert generation failed, CAcert or CAkey files not exist. (%s)" % back) 147 return False 148 else: 149 self.log.debug("Result: %s" % back) 150 151 # Generate certificate key and signing request 152 cmd_params = helper.shellquote( 153 self.openssl_bin, 154 self.key_pem, 155 self.cert_csr, 156 "/CN=" + self.openssl_env['CN'], 157 self.openssl_env["OPENSSL_CONF"], 158 ) 159 cmd = "%s req -new -newkey rsa:2048 -keyout %s -out %s -subj %s -sha256 -nodes -batch -config %s" % cmd_params 160 self.log.debug("Generating certificate key and signing request...") 161 proc = subprocess.Popen( 162 cmd, shell=True, stderr=subprocess.STDOUT, 163 stdout=subprocess.PIPE, env=self.openssl_env 164 ) 165 back = proc.stdout.read().strip().decode(errors="replace").replace("\r", "") 166 proc.wait() 167 self.log.debug("Running: %s\n%s" % (cmd, back)) 168 169 # Sign request and generate certificate 170 cmd_params = helper.shellquote( 171 self.openssl_bin, 172 self.cert_csr, 173 self.cacert_pem, 174 self.cakey_pem, 175 self.cert_pem, 176 self.openssl_env["OPENSSL_CONF"] 177 ) 178 cmd = "%s x509 -req -in %s -CA %s -CAkey %s -set_serial 01 -out %s -days 730 -sha256 -extensions x509_ext -extfile %s" % cmd_params 179 self.log.debug("Generating RSA cert...") 180 proc = subprocess.Popen( 181 cmd, shell=True, stderr=subprocess.STDOUT, 182 stdout=subprocess.PIPE, env=self.openssl_env 183 ) 184 back = proc.stdout.read().strip().decode(errors="replace").replace("\r", "") 185 proc.wait() 186 self.log.debug("Running: %s\n%s" % (cmd, back)) 187 188 if os.path.isfile(self.cert_pem) and os.path.isfile(self.key_pem): 189 self.createSslContexts() 190 return True 191 else: 192 self.log.error("RSA ECC SSL cert generation failed, cert or key files not exist.") 193 194 195manager = CryptConnectionManager() 196