88 :copyright: (c) 2013-present by Abhinav Singh and contributors.
99 :license: BSD, see LICENSE for more details.
1010"""
11+ import logging
1112import threading
12- import subprocess
1313import os
1414import ssl
1515import socket
1616import time
1717import errno
18- import logging
1918from typing import Optional , List , Union , Dict , cast , Any , Tuple
2019
2120from .plugin import HttpProxyBasePlugin
2827from ...common .types import HasFileno
2928from ...common .constants import PROXY_AGENT_HEADER_VALUE
3029from ...common .utils import build_http_response , text_
30+ from ...common .pki import gen_public_key , gen_csr , sign_csr
3131
3232from ...core .event import eventNames
3333from ...core .connection import TcpServerConnection , TcpConnectionUninitializedException
@@ -279,7 +279,8 @@ def on_request_complete(self) -> Union[socket.socket, bool]:
279279 'BrokenPipeError when wrapping client' )
280280 return True
281281 except OSError as e :
282- logger .exception ('OSError when wrapping client' , exc_info = e )
282+ logger .exception (
283+ 'OSError when wrapping client' , exc_info = e )
283284 return True
284285 # Update all plugin connection reference
285286 for plugin in self .plugins .values ():
@@ -342,6 +343,57 @@ def access_log(self) -> None:
342343 self .response .total_size ,
343344 connection_time_ms ))
344345
346+ def gen_ca_signed_certificate (self , cert_file_path : str ) -> None :
347+ '''CA signing key (default) is used for generating a public key
348+ for common_name, if one already doesn't exist. Using generated
349+ public key a CSR request is generated, which is then signed by
350+ CA key and secret. Again this process only happen if signed
351+ certificate doesn't already exist.
352+
353+ returns signed certificate path.'''
354+ assert (self .request .host and self .flags .ca_cert_dir and self .flags .ca_signing_key_file and
355+ self .flags .ca_key_file and self .flags .ca_cert_file )
356+ public_key_path = os .path .join (self .flags .ca_cert_dir ,
357+ '{0}.{1}' .format (text_ (self .request .host ), 'pub' ))
358+ private_key_path = self .flags .ca_signing_key_file
359+ private_key_password = ''
360+ subject = '/CN={0}' .format (text_ (self .request .host ))
361+ alt_subj_names = [text_ (self .request .host ), ]
362+ validity_in_days = 365 * 2
363+ timeout = 10
364+
365+ # Generate a public key for the common name
366+ if not os .path .isfile (public_key_path ):
367+ logger .debug ('Generating public key %s' , public_key_path )
368+ resp = gen_public_key (public_key_path = public_key_path , private_key_path = private_key_path ,
369+ private_key_password = private_key_password , subject = subject , alt_subj_names = alt_subj_names ,
370+ validity_in_days = validity_in_days , timeout = timeout )
371+ assert (resp is True )
372+
373+ csr_path = os .path .join (self .flags .ca_cert_dir ,
374+ '{0}.{1}' .format (text_ (self .request .host ), 'csr' ))
375+
376+ # Generate a CSR request for this common name
377+ if not os .path .isfile (csr_path ):
378+ logger .debug ('Generating CSR %s' , csr_path )
379+ resp = gen_csr (csr_path = csr_path , key_path = private_key_path , password = private_key_password ,
380+ crt_path = public_key_path , timeout = timeout )
381+ assert (resp is True )
382+
383+ ca_key_path = self .flags .ca_key_file
384+ ca_key_password = ''
385+ ca_crt_path = self .flags .ca_cert_file
386+ serial = self .uid .int
387+
388+ # Sign generated CSR
389+ if not os .path .isfile (cert_file_path ):
390+ logger .debug ('Signing CSR %s' , cert_file_path )
391+ resp = sign_csr (csr_path = csr_path , crt_path = cert_file_path , ca_key_path = ca_key_path ,
392+ ca_key_password = ca_key_password , ca_crt_path = ca_crt_path ,
393+ serial = str (serial ), alt_subj_names = alt_subj_names ,
394+ validity_in_days = validity_in_days , timeout = timeout )
395+ assert (resp is True )
396+
345397 @staticmethod
346398 def generated_cert_file_path (ca_cert_dir : str , host : str ) -> str :
347399 return os .path .join (ca_cert_dir , '%s.pem' % host )
@@ -359,21 +411,7 @@ def generate_upstream_certificate(
359411 self .flags .ca_cert_dir , text_ (self .request .host ))
360412 with self .lock :
361413 if not os .path .isfile (cert_file_path ):
362- logger .debug ('Generating certificates %s' , cert_file_path )
363- # TODO: Parse subject from certificate
364- # Currently we only set CN= field for generated certificates.
365- gen_cert = subprocess .Popen (
366- ['openssl' , 'req' , '-new' , '-key' , self .flags .ca_signing_key_file , '-subj' ,
367- f'/C=/ST=/L=/O=/OU=/CN={ text_ (self .request .host ) } ' ],
368- stdout = subprocess .PIPE ,
369- stderr = subprocess .PIPE )
370- sign_cert = subprocess .Popen (
371- ['openssl' , 'x509' , '-req' , '-days' , '365' , '-CA' , self .flags .ca_cert_file , '-CAkey' ,
372- self .flags .ca_key_file , '-set_serial' , str (self .uid .int ), '-out' , cert_file_path ],
373- stdin = gen_cert .stdout ,
374- stderr = subprocess .PIPE )
375- # TODO: Ensure sign_cert success.
376- sign_cert .communicate (timeout = 10 )
414+ self .gen_ca_signed_certificate (cert_file_path )
377415 return cert_file_path
378416
379417 def wrap_server (self ) -> None :
0 commit comments