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