Repository: abhinavsingh/proxy.py

Issue:
[Ubuntu] Cannot use TLS interception
**Describe the bug**
Iam not able to use the TLS interception feature, as provided in the readme section. Even the basic

**To Reproduce**
Steps to reproduce the behavior:
1. [Host machine] Install proxy==2.1.2  ```pip install proxy.py```
2. [Host machine] create ssl files
```bash
export CA_KEY_FILE_PATH=ca-key.pem
export CA_CERT_FILE_PATH=ca-cert.pem
export CA_SIGNING_KEY_FILE_PATH=ca-signing-key.pem
python -m proxy.common.pki gen_private_key --private-key-path $CA_KEY_FILE_PATH
python -m proxy.common.pki remove_passphrase --private-key-path $CA_KEY_FILE_PATH
python -m proxy.common.pki gen_public_key --private-key-path $CA_KEY_FILE_PATH --public-key-path $CA_CERT_FILE_PATH
python -m proxy.common.pki gen_private_key --private-key-path $CA_SIGNING_KEY_FILE_PATH
python -m proxy.common.pki remove_passphrase --private-key-path
```
3. [Host machine] Run proxy.py
```bash
$ proxy --plugins proxy.plugin.CacheResponsesPlugin --ca-key-file ca-key.pem --ca-cert-file ca-cert.pem --ca-signing-key-file ca-signing-key.pem --host 0.0.0.0 --log-level d
2020-03-02 11:11:41,970 - pid:20523 [I] load_plugins:525 - Loaded plugin proxy.http.proxy.HttpProxyPlugin
2020-03-02 11:11:41,971 - pid:20523 [I] load_plugins:525 - Loaded plugin proxy.plugin.CacheResponsesPlugin
2020-03-02 11:11:41,972 - pid:20523 [I] listen:63 - Listening on 0.0.0.0:8899
2020-03-02 11:11:41,975 - pid:20523 [D] start_workers:81 - Started acceptor#0 process 20525
2020-03-02 11:11:41,976 - pid:20523 [I] start_workers:84 - Started 1 workers
```
4. [Client machine] Send request from client system
```bash
$ curl --proxy http://host_ip:8899 --cacert ~/work/varnish_docker_virtual/squid_docker/upstream_haproxy_certs/haproxy-ca-cert.pem https://httpbin.org/ip -vvv
*   Trying host_ip...
* TCP_NODELAY set
* Connected to host_ip (host_ip) port 8899 (#0)
* allocate connect buffer!
* Establish HTTP proxy tunnel to httpbin.org:443
> CONNECT httpbin.org:443 HTTP/1.1
> Host: httpbin.org:443
> User-Agent: curl/7.58.0
> Proxy-Connection: Keep-Alive
> 
< HTTP/1.1 200 Connection established
< 
* Proxy replied 200 to CONNECT request
* CONNECT phase completed!
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /home/jitesh/work/varnish_docker_virtual/squid_docker/upstream_haproxy_certs/haproxy-ca-cert.pem
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CONNECT phase completed!
* CONNECT phase completed!
* OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to httpbin.org:443 
* stopped the pause stream!
* Closing connection 0
curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to httpbin.org:443 
```
5. [Host machine] Check error log at the host
```bash
2020-03-02 11:11:47,320 - pid:20525 [D] initialize:145 - Handling connection <socket.socket fd=9, family=AddressFamily.AF_INET, type=2049, proto=0, laddr=('host_ip', 8899), raddr=('gateway_ip/client_ip', 19673)>
2020-03-02 11:11:47,322 - pid:20525 [D] handle_readables:302 - Client is ready for reads, reading
2020-03-02 11:11:47,322 - pid:20525 [D] recv:65 - received 114 bytes from client
2020-03-02 11:11:47,324 - pid:20525 [D] connect_upstream:420 - Connecting to upstream httpbin.org:443
2020-03-02 11:11:47,363 - pid:20525 [D] connect_upstream:425 - Connected to upstream httpbin.org:443
2020-03-02 11:11:47,402 - pid:20525 [D] generate_upstream_certificate:362 - Generating certificates /home/ubuntu/.proxy.py/certificates/httpbin.org.pem
2020-03-02 11:11:47,422 - pid:20525 [D] flush:91 - flushed 39 bytes to client
2020-03-02 11:11:47,423 - pid:20525 [E] on_request_complete:278 - OSError when wrapping client
2020-03-02 11:11:47,423 - pid:20525 [I] access_log:332 - gateway_ip/client_ip:19673 - CONNECT httpbin.org:443 - 0 bytes - 105.42 ms
2020-03-02 11:11:47,424 - pid:20525 [I] close:48 - Cached response at /tmp/httpbin.org-89311d71dce24450b200947c9ef8ac1f.txt
2020-03-02 11:11:47,424 - pid:20525 [D] on_client_connection_close:189 - Closed server connection, has buffer False
2020-03-02 11:11:47,424 - pid:20525 [D] shutdown:217 - Closing client connection <socket.socket fd=9, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('host_ip', 8899), raddr=('gateway_ip/client_ip', 19673)> at address ('gateway_ip/client_ip', 19673) has buffer False
2020-03-02 11:11:47,425 - pid:20525 [D] shutdown:225 - Client connection shutdown successful
2020-03-02 11:11:47,425 - pid:20525 [D] shutdown:230 - Client connection closed
```

**Expected behavior**
The expected outcome of https://github.com/abhinavsingh/proxy.py#tls-interception

**Version information**
 - OS: Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-88-generic x86_64)
 - Curl: curl 7.58.0 (x86_64-pc-linux-gnu) libcurl/7.58.0 OpenSSL/1.1.1 zlib/1.2.11 libidn2/2.0.4 libpsl/0.19.1 (+libidn2/2.0.4) nghttp2/1.30.0 librtmp/2.3
 - proxy.py Version 2.1.2

**Additional context**
I am able to get response of http://httpbin.org/ip without any issues. So the problem only occurs at the HTTPS section.
Do let me know if there are any procedures that I missed.

Discussion:
whitespots: My steps:
1. Installing requirements
2. ```make ca-certificates```
3. Running on Mac OS with ```--ca-file``` key

```
python3.8 -m proxy \
--hostname 0.0.0.0 \
--port 8899 \
--log-level d \
--enable-dashboard \
--num-workers 4 \
--plugins proxy.plugin.SendRequestData \
--ca-key-file ca-key.pem \
--ca-file ca-cert.pem \
--ca-cert-file ca-cert.pem \
--ca-signing-key-file ca-signing-key.pem
```
Output

```
KeyError: "<socket.socket fd=22, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8899), raddr=('127.0.0.1', 55121)> (FD 22) is already registered"
ssl.SSLError
Traceback (most recent call last):
  File "/Users/appsec/GIT/proxy.py/proxy/http/handler.py", line 405, in run
    teardown = self.run_once()
  File "/Users/appsec/GIT/proxy.py/proxy/http/handler.py", line 390, in run_once
    teardown = self.handle_events(readables, writables)
  File "/Users/appsec/GIT/proxy.py/proxy/http/handler.py", line 193, in handle_events
    teardown = self.handle_readables(readables)
  File "/Users/appsec/GIT/proxy.py/proxy/http/handler.py", line 349, in handle_readables
    upgraded_sock = plugin.on_request_complete()
  File "/Users/appsec/GIT/proxy.py/proxy/http/proxy/server.py", line 271, in on_request_complete
    self.wrap_server()
  File "/Users/appsec/GIT/proxy.py/proxy/http/proxy/server.py", line 424, in wrap_server
    self.server._conn = ctx.wrap_socket(
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/ssl.py", line 500, in wrap_socket
    return self.sslsocket_class._create(
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/ssl.py", line 1040, in _create
    self.do_handshake()
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/ssl.py", line 1309, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1108)
Exception in thread Thread-2:
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/threading.py", line 932, in _bootstrap_inner
    self.run()
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "/Users/appsec/GIT/proxy.py/proxy/http/handler.py", line 417, in run
    self.shutdown()
  File "/Users/appsec/GIT/proxy.py/proxy/http/handler.py", line 208, in shutdown
    self.flush()
  File "/Users/appsec/GIT/proxy.py/proxy/http/handler.py", line 261, in flush
    self.selector.register(
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/selectors.py", line 517, in register
    key = super().register(fileobj, events, data)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/selectors.py", line 241, in register
    raise KeyError("{!r} (FD {}) is already registered"
KeyError: "<socket.socket fd=22, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8899), raddr=('127.0.0.1', 55124)> (FD 22) is already registered"
^C2020-06-07 16:18:03,978 - pid:7097 [I] shutdown:101 - Shutting down 4 workers
...
```

Running on Docker:
```docker build -t pyproxy .```
Output:
```
2020-06-07 12:23:16,565 - pid:13 [D] recv:63 - received 201 bytes from client
2020-06-07 12:23:16,578 - pid:13 [D] connect_upstream:456 - Connecting to upstream mail.ru:443
2020-06-07 12:23:16,632 - pid:13 [D] connect_upstream:461 - Connected to upstream mail.ru:443
2020-06-07 12:23:16,654 - pid:13 [D] _new_conn:226 - Starting new HTTP connection (1): sequencer:2020
2020-06-07 12:23:16,670 - pid:13 [D] _make_request:433 - http://sequencer:2020 "POST /record HTTP/1.1" 200 2
2020-06-07 12:23:16,782 - pid:13 [D] gen_ca_signed_certificate:367 - Generating public key /root/.proxy.py/certificates/mail.ru.pub
2020-06-07 12:23:16,790 - pid:13 [E] on_request_complete:282 - OSError when wrapping client
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/proxy/http/proxy/server.py", line 276, in on_request_complete
    self.wrap_client()
  File "/usr/local/lib/python3.8/site-packages/proxy/http/proxy/server.py", line 432, in wrap_client
    generated_cert = self.generate_upstream_certificate(
  File "/usr/local/lib/python3.8/site-packages/proxy/http/proxy/server.py", line 414, in generate_upstream_certificate
    self.gen_ca_signed_certificate(cert_file_path)
  File "/usr/local/lib/python3.8/site-packages/proxy/http/proxy/server.py", line 368, in gen_ca_signed_certificate
    resp = gen_public_key(public_key_path=public_key_path, private_key_path=private_key_path,
  File "/usr/local/lib/python3.8/site-packages/proxy/common/pki.py", line 106, in gen_public_key
    return run_openssl_command(command, timeout)
  File "/usr/local/lib/python3.8/site-packages/proxy/common/pki.py", line 211, in run_openssl_command
    cmd = subprocess.Popen(
  File "/usr/local/lib/python3.8/subprocess.py", line 854, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/usr/local/lib/python3.8/subprocess.py", line 1702, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'openssl'
2020-06-07 12:23:16,795 - pid:13 [D] flush:91 - flushed 39 bytes to client
2020-06-07 12:23:16,797 - pid:13 [I] access_log:325 - 172.23.0.1:47572 - CONNECT mail.ru:443 - 0 bytes - 233.02 ms
2020-06-07 12:23:16,801 - pid:13 [D] on_client_connection_close:187 - Closed server connection, has buffer False
```

whitespots: @abhinavsingh 
An interesting thing.
Python3.6 does now raise SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED]
But in both cases, there is an issue with sockets.

```
2020-06-07 12:35:56,992 - pid:17 [D] connect_upstream:463 - Connected to upstream mail.ru:443
2020-06-07 12:35:57,009 - pid:17 [D] _new_conn:230 - Starting new HTTP connection (1): sequencer:2020
2020-06-07 12:35:57,018 - pid:17 [D] _make_request:442 - http://sequencer:2020 "POST /record HTTP/1.1" 200 2
2020-06-07 12:35:57,124 - pid:17 [D] gen_ca_signed_certificate:367 - Generating public key /root/.proxy.py/certificates/mail.ru.pub
2020-06-07 12:35:57,141 - pid:17 [E] run:415 - Exception while handling connection <socket.socket fd=11, family=AddressFamily.AF_INET, type=2049, proto=0, laddr=('172.23.0.3', 8899), raddr=('172.23.0.1', 48044)>
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/proxy/http/handler.py", line 405, in run
    teardown = self.run_once()
  File "/usr/local/lib/python3.6/site-packages/proxy/http/handler.py", line 390, in run_once
    teardown = self.handle_events(readables, writables)
  File "/usr/local/lib/python3.6/site-packages/proxy/http/handler.py", line 193, in handle_events
    teardown = self.handle_readables(readables)
  File "/usr/local/lib/python3.6/site-packages/proxy/http/handler.py", line 349, in handle_readables
    upgraded_sock = plugin.on_request_complete()
  File "/usr/local/lib/python3.6/site-packages/proxy/http/proxy/server.py", line 276, in on_request_complete
    self.wrap_client()
  File "/usr/local/lib/python3.6/site-packages/proxy/http/proxy/server.py", line 433, in wrap_client
    cast(Dict[str, Any], self.server.connection.getpeercert()))
  File "/usr/local/lib/python3.6/site-packages/proxy/http/proxy/server.py", line 414, in generate_upstream_certificate
    self.gen_ca_signed_certificate(cert_file_path)
  File "/usr/local/lib/python3.6/site-packages/proxy/http/proxy/server.py", line 371, in gen_ca_signed_certificate
    assert(resp is True)
AssertionError
2020-06-07 12:35:57,142 - pid:17 [D] shutdown:230 - Client connection closed
```
abhinavsingh: > @abhinavsingh
> An interesting thing.
> Python3.6 does now raise SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED]

@whitespots Thanks for a quick try.  I suspect `--ca-file ca-cert.pem` is causing issue for you.  Per flag documentation (my apologies, I should have used CA bundle file here)

<img width="912" alt="Screen Shot 2020-06-07 at 6 13 19 PM" src="https://user-images.githubusercontent.com/126065/83968981-94c56700-a8ea-11ea-85d3-de984a3f4f3c.png">

Since you are on MacOS, simply `pip install certifi` and then use appropriate path as I did for `--ca-file` flag.   This is the CA bundle file (not same as `--ca-key-file`).  This CA bundle file will look like this (a list of CA root certificates):
<img width="1452" alt="Screen Shot 2020-06-07 at 6 17 36 PM" src="https://user-images.githubusercontent.com/126065/83969058-3fd62080-a8eb-11ea-9bd5-1276663cee44.png">

This flag is required on MacOS because installed Python no longer has access to root CA bundle installed on MacOS.

> But in both cases, there is an issue with sockets.

We run into socket issue because `wrap_client` failed, essentially leaving us with same old FD (pre-tls-interception attempt), leading us into `(FD 22) is already registered` error.
abhinavsingh: > Running on Docker:
> `docker build -t pyproxy .`
> Output:
> 
> ```
> 2020-06-07 12:23:16,565 - pid:13 [D] recv:63 - received 201 bytes from client
> 2020-06-07 12:23:16,578 - pid:13 [D] connect_upstream:456 - Connecting to upstream mail.ru:443
> 2020-06-07 12:23:16,632 - pid:13 [D] connect_upstream:461 - Connected to upstream mail.ru:443
> 2020-06-07 12:23:16,654 - pid:13 [D] _new_conn:226 - Starting new HTTP connection (1): sequencer:2020
> 2020-06-07 12:23:16,670 - pid:13 [D] _make_request:433 - http://sequencer:2020 "POST /record HTTP/1.1" 200 2
> 2020-06-07 12:23:16,782 - pid:13 [D] gen_ca_signed_certificate:367 - Generating public key /root/.proxy.py/certificates/mail.ru.pub
> 2020-06-07 12:23:16,790 - pid:13 [E] on_request_complete:282 - OSError when wrapping client
> Traceback (most recent call last):
>   File "/usr/local/lib/python3.8/site-packages/proxy/http/proxy/server.py", line 276, in on_request_complete
>     self.wrap_client()
>   File "/usr/local/lib/python3.8/site-packages/proxy/http/proxy/server.py", line 432, in wrap_client
>     generated_cert = self.generate_upstream_certificate(
>   File "/usr/local/lib/python3.8/site-packages/proxy/http/proxy/server.py", line 414, in generate_upstream_certificate
>     self.gen_ca_signed_certificate(cert_file_path)
>   File "/usr/local/lib/python3.8/site-packages/proxy/http/proxy/server.py", line 368, in gen_ca_signed_certificate
>     resp = gen_public_key(public_key_path=public_key_path, private_key_path=private_key_path,
>   File "/usr/local/lib/python3.8/site-packages/proxy/common/pki.py", line 106, in gen_public_key
>     return run_openssl_command(command, timeout)
>   File "/usr/local/lib/python3.8/site-packages/proxy/common/pki.py", line 211, in run_openssl_command
>     cmd = subprocess.Popen(
>   File "/usr/local/lib/python3.8/subprocess.py", line 854, in __init__
>     self._execute_child(args, executable, preexec_fn, close_fds,
>   File "/usr/local/lib/python3.8/subprocess.py", line 1702, in _execute_child
>     raise child_exception_type(errno_num, err_msg, err_filename)
> FileNotFoundError: [Errno 2] No such file or directory: 'openssl'
> 2020-06-07 12:23:16,795 - pid:13 [D] flush:91 - flushed 39 bytes to client
> 2020-06-07 12:23:16,797 - pid:13 [I] access_log:325 - 172.23.0.1:47572 - CONNECT mail.ru:443 - 0 bytes - 233.02 ms
> 2020-06-07 12:23:16,801 - pid:13 [D] on_client_connection_close:187 - Closed server connection, has buffer False
> ```

Docker container doesn't have `openssl` installed hence leading us into `FileNotFoundError: [Errno 2] No such file or directory: 'openssl'`.  Good point, I never packaged Docker containers with openssl.

Let's use this current branch for verification:

```
git checkout interception-cert-generation
git pull origin interception-cert-generation
python -m proxy ...... flags ......
```

Once after verification I can make a new release for `pip`, `docker` and `brew` distributions.
whitespots: I really need this library and that's why I'm testing right now :)
Will test OpenSSL installation in docker.
abhinavsingh: > I really need this library and that's why I'm testing right now :)
> Will test OpenSSL installation in docker.

Thank you.  When you get time please lemme know about:

1) If it works on MacOS (without docker)
2) If you can update `Dockerfile` to also install `openssl` and verify it work, that will be excellent.  Feel free to send PR for `Dockerfile` updates and we'll make a release.

Cheers!!!
whitespots: Dockerfile is updated with

```
...
LABEL com.abhinavsingh.name="abhinavsingh/proxy.py" \
      com.abhinavsingh.description="    Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on \
        Network monitoring, controls & Application development, testing, debugging." \
      com.abhinavsingh.url="https://github.com/abhinavsingh/proxy.py" \
      com.abhinavsingh.vcs-url="https://github.com/abhinavsingh/proxy.py" \
      com.abhinavsingh.docker.cmd="docker run -it --rm -p 8899:8899 abhinavsingh/proxy.py"

COPY --from=builder /deps /usr/local

RUN apk update && apk add openssl-dev openssl
...
```

But there is a problem with connections

```
2020-06-07 13:25:05,210 - pid:13 [D] handle_readables:302 - Client is ready for reads, reading
2020-06-07 13:25:05,211 - pid:13 [D] recv:63 - received 201 bytes from client
2020-06-07 13:25:05,220 - pid:13 [D] connect_upstream:456 - Connecting to upstream mail.ru:443
2020-06-07 13:25:05,270 - pid:13 [D] connect_upstream:461 - Connected to upstream mail.ru:443
2020-06-07 13:25:05,381 - pid:13 [D] gen_ca_signed_certificate:367 - Generating public key /root/.proxy.py/certificates/mail.ru.pub
2020-06-07 13:25:05,402 - pid:13 [E] run:413 - Exception while handling connection <socket.socket fd=12, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('172.23.0.3', 8899), raddr=('172.23.0.1', 50084)>
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/proxy/http/handler.py", line 405, in run
    teardown = self.run_once()
  File "/usr/local/lib/python3.8/site-packages/proxy/http/handler.py", line 390, in run_once
    teardown = self.handle_events(readables, writables)
  File "/usr/local/lib/python3.8/site-packages/proxy/http/handler.py", line 193, in handle_events
    teardown = self.handle_readables(readables)
  File "/usr/local/lib/python3.8/site-packages/proxy/http/handler.py", line 349, in handle_readables
    upgraded_sock = plugin.on_request_complete()
  File "/usr/local/lib/python3.8/site-packages/proxy/http/proxy/server.py", line 276, in on_request_complete
    self.wrap_client()
  File "/usr/local/lib/python3.8/site-packages/proxy/http/proxy/server.py", line 432, in wrap_client
    generated_cert = self.generate_upstream_certificate(
  File "/usr/local/lib/python3.8/site-packages/proxy/http/proxy/server.py", line 414, in generate_upstream_certificate
    self.gen_ca_signed_certificate(cert_file_path)
  File "/usr/local/lib/python3.8/site-packages/proxy/http/proxy/server.py", line 371, in gen_ca_signed_certificate
    assert(resp is True)
AssertionError
2020-06-07 13:25:05,406 - pid:13 [D] shutdown:230 - Client connection closed
Exception in thread Thread-9:
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/threading.py", line 932, in _bootstrap_inner
    self.run()
  File "/usr/local/lib/python3.8/threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/local/lib/python3.8/site-packages/proxy/http/handler.py", line 417, in run
    self.shutdown()
  File "/usr/local/lib/python3.8/site-packages/proxy/http/handler.py", line 208, in shutdown
    self.flush()
  File "/usr/local/lib/python3.8/site-packages/proxy/http/handler.py", line 261, in flush
    self.selector.register(
  File "/usr/local/lib/python3.8/selectors.py", line 352, in register
    key = super().register(fileobj, events, data)
  File "/usr/local/lib/python3.8/selectors.py", line 241, in register
    raise KeyError("{!r} (FD {}) is already registered"
KeyError: "<socket.socket fd=12, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('172.23.0.3', 8899), raddr=('172.23.0.1', 50084)> (FD 12) is already registered"
2020-06-07 13:25:05,469 - pid:13 [D] initialize:145 - Handling connection <socket.socket fd=12, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('172.23.0.3', 8899), raddr=('172.23.0.1', 50090)>
2020-06-07 13:25:05,511 - pid:13 [D] handle_readables:302 - Client is ready for reads, reading
2020-06-07 13:25:05,513 - pid:13 [D] recv:63 - received 201 bytes from client
```
abhinavsingh: @whitespots Thank you.  I'll check back on `docker` usage.  From logs it is clear that underlying `openssl` command failed.  But will need more investigation to understand why.

Meanwhile, does it work without docker on MacOS for you?
whitespots: > @whitespots Thank you. I'll check back on `docker` usage. From logs it is clear that underlying `openssl` command failed. But will need more investigation to understand why.
> 
> Meanwhile, does it work without docker on MacOS for you?

..No :(
But I need more time to investigate it
```
2020-06-07 17:36:43,915 - pid:8497 [D] start_workers:78 - Started acceptor#0 process 8501
2020-06-07 17:36:43,916 - pid:8497 [I] start_workers:84 - Started 1 workers
OSError when wrapping client
Traceback (most recent call last):
  File "/Users/appsec/GIT/proxy.py/proxy/http/proxy/server.py", line 276, in on_request_complete
    self.wrap_client()
  File "/Users/appsec/GIT/proxy.py/proxy/http/proxy/server.py", line 436, in wrap_client
    self.client._conn = ssl.wrap_socket(
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/ssl.py", line 1402, in wrap_socket
    context.load_cert_chain(certfile, keyfile)
ssl.SSLError: [X509: KEY_VALUES_MISMATCH] key values mismatch (_ssl.c:4012)
OSError when wrapping client
Traceback (most recent call last):
  File "/Users/appsec/GIT/proxy.py/proxy/http/proxy/server.py", line 276, in on_request_complete
    self.wrap_client()
  File "/Users/appsec/GIT/proxy.py/proxy/http/proxy/server.py", line 436, in wrap_client
    self.client._conn = ssl.wrap_socket(
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/ssl.py", line 1402, in wrap_socket
    context.load_cert_chain(certfile, keyfile)
ssl.SSLError: [X509: KEY_VALUES_MISMATCH] key values mismatch (_ssl.c:4012)
```
abhinavsingh: Hmmm, this is weird.  I gave it a try with `Python 3.8` on MacOS and worked fine for me.  I am wondering if `ssl.SSLError: [X509: KEY_VALUES_MISMATCH] key values mismatch` error could be due to old certificates under `~/.proxy/certificates`.  Among other things `KEY_VALUES_MISMATCH` can indicate mismatch of public and private keys.

Try to delete all older certificates under `~/.proxy/certificates` directory and try again.
whitespots: It's empty actually..
But ```rm -rf ~/.proxy/``` does not give me any success
abhinavsingh: Update for Ubuntu
-------------------
Gave it a try on Ubuntu

```
$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 18.04.4 LTS
Release:	18.04
Codename:	bionic
```

and TLS interception worked fine from command line.  I didn't get a chance to setup browser for it.  Please give it a try and lemme know.
whitespots: It's ok on the Ubuntu VM. Confirmed with FF also :)
Now we need to place it in the Dockerfile properly
whitespots: I met the following error:
After the first exception, your traffic will be looking like "CONNECT,CONNECT,.."
![image](https://user-images.githubusercontent.com/65983771/83979104-fb637880-a91c-11ea-9184-078b6efaf3bc.png)

I met this problem on Mac OS and Ubuntu during tests
```
Traceback (most recent call last):
  File "/media/psf/Home/GIT/proxy.py/proxy/http/proxy/server.py", line 276, in on_request_complete
    self.wrap_client()
  File "/media/psf/Home/GIT/proxy.py/proxy/http/proxy/server.py", line 440, in wrap_client
    keyfile=self.flags.ca_signing_key_file)
  File "/usr/lib/python3.6/ssl.py", line 1158, in wrap_socket
    ciphers=ciphers)
  File "/usr/lib/python3.6/ssl.py", line 750, in __init__
    self._context.load_cert_chain(certfile, keyfile)
ssl.SSLError: [SSL] PEM lib (_ssl.c:3503)

```

PS. FF browser on Ubuntu says, that connection is secured by example.com, but Mac OS does not. Even after reboot.

BTW, you can see my extension in debug mode
codecov[bot]: # [Codecov](https://codecov.io/gh/abhinavsingh/proxy.py/pull/362?src=pr&el=h1) Report
> Merging [#362](https://codecov.io/gh/abhinavsingh/proxy.py/pull/362?src=pr&el=desc) into [develop](https://codecov.io/gh/abhinavsingh/proxy.py/commit/ab089012395f94ab8e502c44a985bbf7d088c1b4&el=desc) will **increase** coverage by `0.15%`.
> The diff coverage is `96.77%`.

[![Impacted file tree graph](https://codecov.io/gh/abhinavsingh/proxy.py/pull/362/graphs/tree.svg?width=650&height=150&src=pr&token=Zh9J7b4la2)](https://codecov.io/gh/abhinavsingh/proxy.py/pull/362?src=pr&el=tree)

```diff
@@             Coverage Diff             @@
##           develop     #362      +/-   ##
===========================================
+ Coverage    81.40%   81.55%   +0.15%     
===========================================
  Files           72       72              
  Lines         2775     2798      +23     
===========================================
+ Hits          2259     2282      +23     
  Misses         516      516              
```


| [Impacted Files](https://codecov.io/gh/abhinavsingh/proxy.py/pull/362?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [proxy/http/proxy/server.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/362/diff?src=pr&el=tree#diff-cHJveHkvaHR0cC9wcm94eS9zZXJ2ZXIucHk=) | `79.35% <96.66%> (+1.84%)` | :arrow_up: |
| [proxy/common/version.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/362/diff?src=pr&el=tree#diff-cHJveHkvY29tbW9uL3ZlcnNpb24ucHk=) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/abhinavsingh/proxy.py/pull/362?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/abhinavsingh/proxy.py/pull/362?src=pr&el=footer). Last update [ab08901...6c953e3](https://codecov.io/gh/abhinavsingh/proxy.py/pull/362?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

abhinavsingh: > UPD
> Only CONNECT method in logs is working now..
> I am checking the course of this change (all was ok for 10 minutes)
> 

Yes, for `HTTPS` connections log will only show `CONNECT` method.

> Really interesting error for you, @abhinavsingh:
> 
> ```
> 2020-06-08 00:25:35,294 - pid:28446 [E] run:415 - Exception while handling connection <ssl.SSLSocket fd=54, family=AddressFamily.AF_INET, type=2049, proto=0, laddr=('127.0.0.1', 8899), raddr=('127.0.0.1', 43428)>
> Traceback (most recent call last):
>   File "/media/psf/Home/GIT/proxy.py/proxy/http/handler.py", line 405, in run
>     teardown = self.run_once()
>   File "/media/psf/Home/GIT/proxy.py/proxy/http/handler.py", line 390, in run_once
>     teardown = self.handle_events(readables, writables)
>   File "/media/psf/Home/GIT/proxy.py/proxy/http/handler.py", line 193, in handle_events
>     teardown = self.handle_readables(readables)
>   File "/media/psf/Home/GIT/proxy.py/proxy/http/handler.py", line 331, in handle_readables
>     client_data)
>   File "/media/psf/Home/GIT/proxy.py/proxy/http/proxy/server.py", line 215, in on_client_data
>     self.pipeline_request.parse(raw.tobytes())
>   File "/media/psf/Home/GIT/proxy.py/proxy/http/parser.py", line 176, in parse
>     more, raw = self.process(raw)
>   File "/media/psf/Home/GIT/proxy.py/proxy/http/parser.py", line 186, in process
>     self.process_line(line)
>   File "/media/psf/Home/GIT/proxy.py/proxy/http/parser.py", line 214, in process_line
>     self.set_url(line[1])
> IndexError: list index out of range
> ```
> 

This error is interesting.  Looks like an invalid request was made.  Specifically, for a `HTTP/1.1` pipelined request, subsequent requests didn't receive a URL.  Curious, if you can share request parameters so I can dig further into it.

> PS. FF browser on Ubuntu says, that connection is secured by example.com, but Mac OS does not. Even after reboot.

That's interesting because I am getting success on `MacOS`.  Screenshot attached in description of this PR is actually from `MacOS`.  What version of `MacOS` are you using?  Also did you trust CA root certificate in keychain?
abhinavsingh: @Benouare @httpnotonly @ja8zyjits @roshanprince402 @tawmoto @whitespots 

Folks, looks like we have a way forward.  So I'll go ahead and merge this PR.  I have personally verified the changes on both `MacOS` (using CLI and Browser) and `Ubuntu` (CLI only).  @whitespots helped us verify this fix on `Ubuntu` (using Browser).

A few things to resolve going forward:

1) @whitespots reported TLS interception on `MacOS` doesn't say trusted.  Frankly, this is more of a non-proxy problem and can be resolved outside of `proxy.py` changes a.k.a. trusting root certificate is not something `proxy.py` can do.

2) Currently `proxy.py` docker image is not shipped with `openssl` and hence users cannot try TLS interception with docker.  Possible solutions:
    - Mount `openssl` into container so that it is available to `proxy.py` running within docker
    - Package `openssl` with `proxy.py` docker container (which will increase our container size)

Patch:
diff --git a/README.md b/README.md
index 9140f9d..1ce5e21 100644
--- a/README.md
+++ b/README.md
@@ -795,8 +795,7 @@ response from the server.  Start `proxy.py` as:
 ```
 
 
-> :note: **MacOS users** also need to pass explicit CA file path
-> needed for validation of peer certificates. See --ca-file flag.
+[![NOTE](https://img.shields.io/static/v1?label=MacOS&message=note&color=yellow)](https://github.com/abhinavsingh/proxy.py#flags) Also provide explicit CA bundle path needed for validation of peer certificates. See `--ca-file` flag.
 
 
 Verify TLS interception using `curl`
@@ -1327,7 +1326,7 @@ usage: pki.py [-h] [--password PASSWORD] [--private-key-path PRIVATE_KEY_PATH]
               [--public-key-path PUBLIC_KEY_PATH] [--subject SUBJECT]
               action
 
-proxy.py v2.1.2 : PKI Utility
+proxy.py v2.2.0 : PKI Utility
 
 positional arguments:
   action                Valid actions: remove_passphrase, gen_private_key,
@@ -1518,7 +1517,7 @@ usage: proxy [-h] [--backlog BACKLOG] [--basic-auth BASIC_AUTH]
              [--static-server-dir STATIC_SERVER_DIR] [--threadless]
              [--timeout TIMEOUT] [--version]
 
-proxy.py v2.1.2
+proxy.py v2.2.0
 
 optional arguments:
   -h, --help            show this help message and exit
diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json
index 1b0c71a..a28f327 100644
--- a/dashboard/package-lock.json
+++ b/dashboard/package-lock.json
@@ -1382,9 +1382,9 @@
       "dev": true
     },
     "follow-redirects": {
-      "version": "1.10.0",
-      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.10.0.tgz",
-      "integrity": "sha512-4eyLK6s6lH32nOvLLwlIOnr9zrL8Sm+OvW4pVTJNoXeGzYIkHVf+pADQi+OJ0E67hiuSLezPVPyBcIZO50TmmQ==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz",
+      "integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==",
       "dev": true,
       "requires": {
         "debug": "^3.0.0"
@@ -1582,9 +1582,9 @@
       }
     },
     "http-proxy": {
-      "version": "1.18.0",
-      "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz",
-      "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==",
+      "version": "1.18.1",
+      "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
+      "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
       "dev": true,
       "requires": {
         "eventemitter3": "^4.0.0",
@@ -1593,19 +1593,19 @@
       }
     },
     "http-server": {
-      "version": "0.12.1",
-      "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.1.tgz",
-      "integrity": "sha512-T0jB+7J7GJ2Vo+a4/T7P7SbQ3x2GPDnqRqQXdfEuPuUOmES/9NBxPnDm7dh1HGEeUWqUmLUNtGV63ZC5Uy3tGA==",
+      "version": "0.12.3",
+      "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz",
+      "integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==",
       "dev": true,
       "requires": {
         "basic-auth": "^1.0.3",
-        "colors": "^1.3.3",
+        "colors": "^1.4.0",
         "corser": "^2.0.1",
         "ecstatic": "^3.3.2",
-        "http-proxy": "^1.17.0",
+        "http-proxy": "^1.18.0",
+        "minimist": "^1.2.5",
         "opener": "^1.5.1",
-        "optimist": "~0.6.1",
-        "portfinder": "^1.0.20",
+        "portfinder": "^1.0.25",
         "secure-compare": "3.0.1",
         "union": "~0.5.0"
       }
@@ -2641,30 +2641,6 @@
         "pinkie-promise": "^2.0.0"
       }
     },
-    "optimist": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
-      "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
-      "dev": true,
-      "requires": {
-        "minimist": "~0.0.1",
-        "wordwrap": "~0.0.2"
-      },
-      "dependencies": {
-        "minimist": {
-          "version": "0.0.10",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
-          "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=",
-          "dev": true
-        },
-        "wordwrap": {
-          "version": "0.0.3",
-          "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
-          "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
-          "dev": true
-        }
-      }
-    },
     "optionator": {
       "version": "0.8.2",
       "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
@@ -2832,9 +2808,9 @@
       "dev": true
     },
     "portfinder": {
-      "version": "1.0.25",
-      "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz",
-      "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==",
+      "version": "1.0.26",
+      "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz",
+      "integrity": "sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==",
       "dev": true,
       "requires": {
         "async": "^2.6.2",
@@ -2873,9 +2849,9 @@
       "dev": true
     },
     "qs": {
-      "version": "6.9.2",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.2.tgz",
-      "integrity": "sha512-2eQ6zajpK7HwqrY1rRtGw5IZvjgtELXzJECaEDuzDFo2jjnIXpJSimzd4qflWZq6bLLi+Zgfj5eDrAzl/lptyg==",
+      "version": "6.9.4",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz",
+      "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==",
       "dev": true
     },
     "read-pkg": {
diff --git a/dashboard/package.json b/dashboard/package.json
index f5bd8bf..0f63c92 100644
--- a/dashboard/package.json
+++ b/dashboard/package.json
@@ -37,7 +37,7 @@
     "eslint-plugin-node": "^10.0.0",
     "eslint-plugin-promise": "^4.2.1",
     "eslint-plugin-standard": "^4.0.1",
-    "http-server": "^0.12.1",
+    "http-server": "^0.12.3",
     "jasmine": "^3.5.0",
     "jasmine-ts": "^0.3.0",
     "jquery": "^3.5.0",
diff --git a/helper/homebrew/stable/proxy.rb b/helper/homebrew/stable/proxy.rb
index c13d221..b7ca93f 100644
--- a/helper/homebrew/stable/proxy.rb
+++ b/helper/homebrew/stable/proxy.rb
@@ -5,7 +5,7 @@ class Proxy < Formula
     Network monitoring, controls & Application development, testing, debugging."
   homepage "https://github.com/abhinavsingh/proxy.py"
   url "https://github.com/abhinavsingh/proxy.py/archive/master.zip"
-  version "2.0.0"
+  version "2.1.2"
 
   depends_on "python"
 
diff --git a/proxy/common/version.py b/proxy/common/version.py
index 913d804..e8af269 100644
--- a/proxy/common/version.py
+++ b/proxy/common/version.py
@@ -8,5 +8,5 @@
     :copyright: (c) 2013-present by Abhinav Singh and contributors.
     :license: BSD, see LICENSE for more details.
 """
-VERSION = (2, 1, 2)
+VERSION = (2, 2, 0)
 __version__ = '.'.join(map(str, VERSION[0:3]))
diff --git a/proxy/http/proxy/server.py b/proxy/http/proxy/server.py
index db03de9..83e77fb 100644
--- a/proxy/http/proxy/server.py
+++ b/proxy/http/proxy/server.py
@@ -8,14 +8,13 @@
     :copyright: (c) 2013-present by Abhinav Singh and contributors.
     :license: BSD, see LICENSE for more details.
 """
+import logging
 import threading
-import subprocess
 import os
 import ssl
 import socket
 import time
 import errno
-import logging
 from typing import Optional, List, Union, Dict, cast, Any, Tuple
 
 from .plugin import HttpProxyBasePlugin
@@ -28,6 +27,7 @@ from ..methods import httpMethods
 from ...common.types import HasFileno
 from ...common.constants import PROXY_AGENT_HEADER_VALUE
 from ...common.utils import build_http_response, text_
+from ...common.pki import gen_public_key, gen_csr, sign_csr
 
 from ...core.event import eventNames
 from ...core.connection import TcpServerConnection, TcpConnectionUninitializedException
@@ -279,7 +279,8 @@ class HttpProxyPlugin(HttpProtocolHandlerPlugin):
                         'BrokenPipeError when wrapping client')
                     return True
                 except OSError as e:
-                    logger.exception('OSError when wrapping client', exc_info=e)
+                    logger.exception(
+                        'OSError when wrapping client', exc_info=e)
                     return True
                 # Update all plugin connection reference
                 for plugin in self.plugins.values():
@@ -342,6 +343,57 @@ class HttpProxyPlugin(HttpProtocolHandlerPlugin):
                  self.response.total_size,
                  connection_time_ms))
 
+    def gen_ca_signed_certificate(self, cert_file_path: str) -> None:
+        '''CA signing key (default) is used for generating a public key
+        for common_name, if one already doesn't exist.  Using generated
+        public key a CSR request is generated, which is then signed by
+        CA key and secret.  Again this process only happen if signed
+        certificate doesn't already exist.
+
+        returns signed certificate path.'''
+        assert(self.request.host and self.flags.ca_cert_dir and self.flags.ca_signing_key_file and
+               self.flags.ca_key_file and self.flags.ca_cert_file)
+        public_key_path = os.path.join(self.flags.ca_cert_dir,
+                                       '{0}.{1}'.format(text_(self.request.host), 'pub'))
+        private_key_path = self.flags.ca_signing_key_file
+        private_key_password = ''
+        subject = '/CN={0}'.format(text_(self.request.host))
+        alt_subj_names = [text_(self.request.host), ]
+        validity_in_days = 365 * 2
+        timeout = 10
+
+        # Generate a public key for the common name
+        if not os.path.isfile(public_key_path):
+            logger.debug('Generating public key %s', public_key_path)
+            resp = gen_public_key(public_key_path=public_key_path, private_key_path=private_key_path,
+                                  private_key_password=private_key_password, subject=subject, alt_subj_names=alt_subj_names,
+                                  validity_in_days=validity_in_days, timeout=timeout)
+            assert(resp is True)
+
+        csr_path = os.path.join(self.flags.ca_cert_dir,
+                                '{0}.{1}'.format(text_(self.request.host), 'csr'))
+
+        # Generate a CSR request for this common name
+        if not os.path.isfile(csr_path):
+            logger.debug('Generating CSR %s', csr_path)
+            resp = gen_csr(csr_path=csr_path, key_path=private_key_path, password=private_key_password,
+                           crt_path=public_key_path, timeout=timeout)
+            assert(resp is True)
+
+        ca_key_path = self.flags.ca_key_file
+        ca_key_password = ''
+        ca_crt_path = self.flags.ca_cert_file
+        serial = self.uid.int
+
+        # Sign generated CSR
+        if not os.path.isfile(cert_file_path):
+            logger.debug('Signing CSR %s', cert_file_path)
+            resp = sign_csr(csr_path=csr_path, crt_path=cert_file_path, ca_key_path=ca_key_path,
+                            ca_key_password=ca_key_password, ca_crt_path=ca_crt_path,
+                            serial=str(serial), alt_subj_names=alt_subj_names,
+                            validity_in_days=validity_in_days, timeout=timeout)
+            assert(resp is True)
+
     @staticmethod
     def generated_cert_file_path(ca_cert_dir: str, host: str) -> str:
         return os.path.join(ca_cert_dir, '%s.pem' % host)
@@ -359,21 +411,7 @@ class HttpProxyPlugin(HttpProtocolHandlerPlugin):
             self.flags.ca_cert_dir, text_(self.request.host))
         with self.lock:
             if not os.path.isfile(cert_file_path):
-                logger.debug('Generating certificates %s', cert_file_path)
-                # TODO: Parse subject from certificate
-                # Currently we only set CN= field for generated certificates.
-                gen_cert = subprocess.Popen(
-                    ['openssl', 'req', '-new', '-key', self.flags.ca_signing_key_file, '-subj',
-                     f'/C=/ST=/L=/O=/OU=/CN={ text_(self.request.host) }'],
-                    stdout=subprocess.PIPE,
-                    stderr=subprocess.PIPE)
-                sign_cert = subprocess.Popen(
-                    ['openssl', 'x509', '-req', '-days', '365', '-CA', self.flags.ca_cert_file, '-CAkey',
-                     self.flags.ca_key_file, '-set_serial', str(self.uid.int), '-out', cert_file_path],
-                    stdin=gen_cert.stdout,
-                    stderr=subprocess.PIPE)
-                # TODO: Ensure sign_cert success.
-                sign_cert.communicate(timeout=10)
+                self.gen_ca_signed_certificate(cert_file_path)
         return cert_file_path
 
     def wrap_server(self) -> None:
diff --git a/setup.py b/setup.py
index 6c73e03..0d2c675 100644
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,7 @@
 """
 from setuptools import setup, find_packages
 
-VERSION = (2, 1, 2)
+VERSION = (2, 2, 0)
 __version__ = '.'.join(map(str, VERSION[0:3]))
 __description__ = '''⚡⚡⚡Fast, Lightweight, Pluggable, TLS interception capable proxy server
     focused on Network monitoring, controls & Application development, testing, debugging.'''


Test patch:
diff --git a/tests/http/test_http_proxy_tls_interception.py b/tests/http/test_http_proxy_tls_interception.py
index 6f4d93e..f8019d8 100644
--- a/tests/http/test_http_proxy_tls_interception.py
+++ b/tests/http/test_http_proxy_tls_interception.py
@@ -30,14 +30,18 @@ class TestHttpProxyTlsInterception(unittest.TestCase):
     @mock.patch('ssl.wrap_socket')
     @mock.patch('ssl.create_default_context')
     @mock.patch('proxy.http.proxy.server.TcpServerConnection')
-    @mock.patch('subprocess.Popen')
+    @mock.patch('proxy.http.proxy.server.gen_public_key')
+    @mock.patch('proxy.http.proxy.server.gen_csr')
+    @mock.patch('proxy.http.proxy.server.sign_csr')
     @mock.patch('selectors.DefaultSelector')
     @mock.patch('socket.fromfd')
     def test_e2e(
             self,
             mock_fromfd: mock.Mock,
             mock_selector: mock.Mock,
-            mock_popen: mock.Mock,
+            mock_sign_csr: mock.Mock,
+            mock_gen_csr: mock.Mock,
+            mock_gen_public_key: mock.Mock,
             mock_server_conn: mock.Mock,
             mock_ssl_context: mock.Mock,
             mock_ssl_wrap: mock.Mock) -> None:
@@ -46,11 +50,17 @@ class TestHttpProxyTlsInterception(unittest.TestCase):
 
         self.mock_fromfd = mock_fromfd
         self.mock_selector = mock_selector
-        self.mock_popen = mock_popen
+        self.mock_sign_csr = mock_sign_csr
+        self.mock_gen_csr = mock_gen_csr
+        self.mock_gen_public_key = mock_gen_public_key
         self.mock_server_conn = mock_server_conn
         self.mock_ssl_context = mock_ssl_context
         self.mock_ssl_wrap = mock_ssl_wrap
 
+        self.mock_sign_csr.return_value = True
+        self.mock_gen_csr.return_value = True
+        self.mock_gen_public_key.return_value = True
+
         ssl_connection = mock.MagicMock(spec=ssl.SSLSocket)
         self.mock_ssl_context.return_value.wrap_socket.return_value = ssl_connection
         self.mock_ssl_wrap.return_value = mock.MagicMock(spec=ssl.SSLSocket)
@@ -118,6 +128,7 @@ class TestHttpProxyTlsInterception(unittest.TestCase):
                 fd=self._conn.fileno,
                 events=selectors.EVENT_READ,
                 data=None), selectors.EVENT_READ)], ]
+
         self.protocol_handler.run_once()
 
         # Assert our mocked plugins invocations
@@ -142,8 +153,9 @@ class TestHttpProxyTlsInterception(unittest.TestCase):
         self.assertEqual(plain_connection.setblocking.call_count, 2)
         self.mock_ssl_context.return_value.wrap_socket.assert_called_with(
             plain_connection, server_hostname=host)
-        # TODO: Assert Popen arguments, piping, success condition
-        self.assertEqual(self.mock_popen.call_count, 2)
+        self.assertEqual(self.mock_sign_csr.call_count, 1)
+        self.assertEqual(self.mock_gen_csr.call_count, 1)
+        self.assertEqual(self.mock_gen_public_key.call_count, 1)
         self.assertEqual(ssl_connection.setblocking.call_count, 1)
         self.assertEqual(
             self.mock_server_conn.return_value._conn,
diff --git a/tests/plugin/test_http_proxy_plugins_with_tls_interception.py b/tests/plugin/test_http_proxy_plugins_with_tls_interception.py
index 2976869..0c1ec0a 100644
--- a/tests/plugin/test_http_proxy_plugins_with_tls_interception.py
+++ b/tests/plugin/test_http_proxy_plugins_with_tls_interception.py
@@ -33,23 +33,33 @@ class TestHttpProxyPluginExamplesWithTlsInterception(unittest.TestCase):
     @mock.patch('ssl.wrap_socket')
     @mock.patch('ssl.create_default_context')
     @mock.patch('proxy.http.proxy.server.TcpServerConnection')
-    @mock.patch('subprocess.Popen')
+    @mock.patch('proxy.http.proxy.server.gen_public_key')
+    @mock.patch('proxy.http.proxy.server.gen_csr')
+    @mock.patch('proxy.http.proxy.server.sign_csr')
     @mock.patch('selectors.DefaultSelector')
     @mock.patch('socket.fromfd')
     def setUp(self,
               mock_fromfd: mock.Mock,
               mock_selector: mock.Mock,
-              mock_popen: mock.Mock,
+              mock_sign_csr: mock.Mock,
+              mock_gen_csr: mock.Mock,
+              mock_gen_public_key: mock.Mock,
               mock_server_conn: mock.Mock,
               mock_ssl_context: mock.Mock,
               mock_ssl_wrap: mock.Mock) -> None:
         self.mock_fromfd = mock_fromfd
         self.mock_selector = mock_selector
-        self.mock_popen = mock_popen
+        self.mock_sign_csr = mock_sign_csr
+        self.mock_gen_csr = mock_gen_csr
+        self.mock_gen_public_key = mock_gen_public_key
         self.mock_server_conn = mock_server_conn
         self.mock_ssl_context = mock_ssl_context
         self.mock_ssl_wrap = mock_ssl_wrap
 
+        self.mock_sign_csr.return_value = True
+        self.mock_gen_csr.return_value = True
+        self.mock_gen_public_key.return_value = True
+
         self.fileno = 10
         self._addr = ('127.0.0.1', 54382)
         self.flags = Flags(
@@ -126,7 +136,10 @@ class TestHttpProxyPluginExamplesWithTlsInterception(unittest.TestCase):
         )
         self.protocol_handler.run_once()
 
-        self.mock_popen.assert_called()
+        self.assertEqual(self.mock_sign_csr.call_count, 1)
+        self.assertEqual(self.mock_gen_csr.call_count, 1)
+        self.assertEqual(self.mock_gen_public_key.call_count, 1)
+
         self.mock_server_conn.assert_called_once_with('uni.corn', 443)
         self.server.connect.assert_called()
         self.assertEqual(