Forking what is left of ZeroNet and hopefully adding an AT Proto Frontend/Proxy
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()