diff --git a/.gitignore b/.gitignore index 45423f12..343de585 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ proxy.py.iml *.csr *.crt *.key +*.pem venv* cover diff --git a/Makefile b/Makefile index 0dfd26ed..d47a5543 100644 --- a/Makefile +++ b/Makefile @@ -30,18 +30,31 @@ autopep8: https-certificates: # Generate server key - openssl genrsa -out $(HTTPS_KEY_FILE_PATH) 2048 + python -m proxy.common.pki gen_private_key \ + --private-key-path $(HTTPS_KEY_FILE_PATH) + python -m proxy.common.pki remove_passphrase \ + --private-key-path $(HTTPS_KEY_FILE_PATH) # Generate server certificate - openssl req -new -x509 -days 3650 -key $(HTTPS_KEY_FILE_PATH) -out $(HTTPS_CERT_FILE_PATH) + python -m proxy.common.pki gen_public_key \ + --private-key-path $(HTTPS_KEY_FILE_PATH) \ + --public-key-path $(HTTPS_CERT_FILE_PATH) ca-certificates: # Generate CA key - openssl genrsa -out $(CA_KEY_FILE_PATH) 2048 + 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) # Generate CA certificate - openssl req -new -x509 -days 3650 -key $(CA_KEY_FILE_PATH) -out $(CA_CERT_FILE_PATH) + python -m proxy.common.pki gen_public_key \ + --private-key-path $(CA_KEY_FILE_PATH) \ + --public-key-path $(CA_CERT_FILE_PATH) # Generate key that will be used to generate domain certificates on the fly # Generated certificates are then signed with CA certificate / key generated above - openssl genrsa -out $(CA_SIGNING_KEY_FILE_PATH) 2048 + 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 $(CA_SIGNING_KEY_FILE_PATH) lib-clean: find . -name '*.pyc' -exec rm -f {} + diff --git a/README.md b/README.md index 9101e2a2..08841ae8 100644 --- a/README.md +++ b/README.md @@ -88,9 +88,9 @@ Table of Contents * [Http](#http-client) * [build_http_request](#build_http_request) * [build_http_response](#build_http_response) - * [Websocket](#websocket) - * [WebsocketFrame](#websocketframe) - * [WebsocketClient](#websocketclient) + * [Public Key Infrastructure](#pki) + * [API Usage](#api-usage) + * [CLI Usage](#cli-usage) * [Frequently Asked Questions](#frequently-asked-questions) * [SyntaxError: invalid syntax](#syntaxerror-invalid-syntax) * [Unable to load plugins](#unable-to-load-plugins) @@ -1161,7 +1161,7 @@ Utilities ## TCP Sockets -#### new_socket_connection +### new_socket_connection Attempts to create an IPv4 connection, then IPv6 and finally a dual stack connection to provided address. @@ -1172,7 +1172,7 @@ finally a dual stack connection to provided address. >>> conn.close() ``` -#### socket_connection +### socket_connection `socket_connection` is a convenient decorator + context manager around `new_socket_connection` which ensures `conn.close` is implicit. @@ -1194,9 +1194,9 @@ As a decorator: ## Http Client -#### build_http_request +### build_http_request -##### Generate HTTP GET request +#### Generate HTTP GET request ```python >>> build_http_request(b'GET', b'/') @@ -1204,7 +1204,7 @@ b'GET / HTTP/1.1\r\n\r\n' >>> ``` -##### Generate HTTP GET request with headers +#### Generate HTTP GET request with headers ```python >>> build_http_request(b'GET', b'/', @@ -1213,7 +1213,7 @@ b'GET / HTTP/1.1\r\nConnection: close\r\n\r\n' >>> ``` -##### Generate HTTP POST request with headers and body +#### Generate HTTP POST request with headers and body ```python >>> import json @@ -1223,19 +1223,53 @@ b'GET / HTTP/1.1\r\nConnection: close\r\n\r\n' b'POST /form HTTP/1.1\r\nContent-type: application/json\r\n\r\n{"email": "hello@world.com"}' ``` -#### build_http_response +### build_http_response TODO -## Websocket +## PKI -#### WebsocketFrame +### API Usage -TODO +#### gen_private_key +#### gen_public_key +#### remove_passphrase +#### gen_csr +#### sign_csr -#### WebsocketClient +See [pki.py](https://github.com/abhinavsingh/proxy.py/blob/develop/proxy/common/pki.py) are +method definitions. -TODO +### CLI Usage + +Use `proxy.common.pki` module for: + +1) Generation of public and private keys +2) Generating CSR requests +3) Signing CSR requests using custom CA. + +```bash +python -m proxy.common.pki -h +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.0 : PKI Utility + +positional arguments: + action Valid actions: remove_passphrase, gen_private_key, + gen_public_key, gen_csr, sign_csr + +optional arguments: + -h, --help show this help message and exit + --password PASSWORD Password to use for encryption. Default: proxy.py + --private-key-path PRIVATE_KEY_PATH + Private key path + --public-key-path PUBLIC_KEY_PATH + Public key path + --subject SUBJECT Subject to use for public key generation. Default: + /CN=example.com +``` ## Internal Documentation @@ -1377,7 +1411,7 @@ usage: proxy [-h] [--backlog BACKLOG] [--basic-auth BASIC_AUTH] [--static-server-dir STATIC_SERVER_DIR] [--threadless] [--timeout TIMEOUT] [--version] -proxy.py v2.0.0 +proxy.py v2.1.0 optional arguments: -h, --help show this help message and exit diff --git a/proxy/common/pki.py b/proxy/common/pki.py index 024e4477..0f1030b6 100644 --- a/proxy/common/pki.py +++ b/proxy/common/pki.py @@ -8,6 +8,8 @@ :copyright: (c) 2013-present by Abhinav Singh and contributors. :license: BSD, see LICENSE for more details. """ +import sys +import argparse import contextlib import os import uuid @@ -18,6 +20,7 @@ from typing import List, Generator, Optional, Tuple from .utils import bytes_ from .constants import COMMA +from .version import __version__ logger = logging.getLogger(__name__) @@ -212,3 +215,68 @@ def run_openssl_command(command: List[str], timeout: int) -> bool: ) cmd.communicate(timeout=timeout) return cmd.returncode == 0 + + +if __name__ == '__main__': + available_actions = ( + 'remove_passphrase', 'gen_private_key', 'gen_public_key', + 'gen_csr', 'sign_csr' + ) + + parser = argparse.ArgumentParser( + description='proxy.py v%s : PKI Utility' % __version__, + ) + parser.add_argument( + 'action', + type=str, + default=None, + help='Valid actions: ' + ', '.join(available_actions) + ) + parser.add_argument( + '--password', + type=str, + default='proxy.py', + help='Password to use for encryption. Default: proxy.py', + ) + parser.add_argument( + '--private-key-path', + type=str, + default=None, + help='Private key path', + ) + parser.add_argument( + '--public-key-path', + type=str, + default=None, + help='Public key path', + ) + parser.add_argument( + '--subject', + type=str, + default='/CN=example.com', + help='Subject to use for public key generation. Default: /CN=example.com', + ) + args = parser.parse_args(sys.argv[1:]) + + # Validation + if args.action not in available_actions: + print('Invalid --action. Valid values ' + ', '.join(available_actions)) + sys.exit(1) + if args.action in ('gen_private_key', 'gen_public_key'): + if args.private_key_path is None: + print('--private-key-path is required for ' + args.action) + sys.exit(1) + if args.action == 'gen_public_key': + if args.public_key_path is None: + print('--public-key-file is required for private key generation') + sys.exit(1) + + # Execute + if args.action == 'gen_private_key': + gen_private_key(args.private_key_path, args.password) + elif args.action == 'gen_public_key': + gen_public_key(args.public_key_path, args.private_key_path, + args.password, args.subject) + elif args.action == 'remove_passphrase': + remove_passphrase(args.private_key_path, args.password, + args.private_key_path) diff --git a/proxy/common/version.py b/proxy/common/version.py index 36d645cd..1a23f791 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, 0, 0) +VERSION = (2, 1, 0) __version__ = '.'.join(map(str, VERSION[0:3]))