diff --git a/README.md b/README.md index a01c377d..60921a61 100644 --- a/README.md +++ b/README.md @@ -17,23 +17,25 @@ We are explicitly soliciting contributors to maintain those integrations labelle Currently available integrations at their respective support level: -| | **Build instructions** | **Pre-built Docker image or binary files** | Support? | -| ---------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------- | -| **curl** | [Github: oqs-demos/curl](curl) | [Dockerhub: openquantumsafe/curl](https://hub.docker.com/repository/docker/openquantumsafe/curl), [Dockerhub: openquantumsafe/curl-quic](https://hub.docker.com/repository/docker/openquantumsafe/curl-quic) | @baentsch, @pi-314159 -| **Apache httpd** | [Github: oqs-demos/httpd](httpd) | [Dockerhub: openquantumsafe/httpd](https://hub.docker.com/repository/docker/openquantumsafe/httpd) | @baentsch -| **nginx** | [Github: oqs-demos/nginx](nginx) | [Dockerhub: openquantumsafe/nginx](https://hub.docker.com/repository/docker/openquantumsafe/nginx), [Dockerhub: openquantumsafe/nginx-quic](https://hub.docker.com/repository/docker/openquantumsafe/nginx-quic) | @baentsch, @bhess, @pi-314159 -| **Chromium** | [Github: oqs-demos/chromium](chromium) (limited support) | - | @pi-314159 | -| **OpenSSH** | [Github: oqs-demos/openssh](openssh) | [Dockerhub: openquantumsafe/openssh](https://hub.docker.com/repository/docker/openquantumsafe/openssh) | unsupported -| **Wireshark** | [Github: oqs-demos/wireshark](wireshark) | [Dockerhub: openquantumsafe/wireshark](https://hub.docker.com/repository/docker/openquantumsafe/wireshark) | unsupported -| **Epiphany** | [Github: oqs-demos/epiphany](epiphany) | [Dockerhub: openquantumsafe/epiphany](https://hub.docker.com/repository/docker/openquantumsafe/epiphany) | unsupported -| **OpenVPN** | [Github: oqs-demos/openvpn](openvpn) | [Dockerhub: openquantumsafe/openvpn](https://hub.docker.com/repository/docker/openquantumsafe/openvpn) | unsupported -| **ngtcp2** | [Github: oqs-demos/ngtcp2](ngtcp2) | Dockerhub: [Server: openquantumsafe/ngtcp2-server](https://hub.docker.com/repository/docker/openquantumsafe/ngtcp2-server), [Client: openquantumsafe/ngtcp2-client](https://hub.docker.com/repository/docker/openquantumsafe/ngtcp2-client) | unsupported -| **OpenLiteSpeed** | [Github: oqs-demos/openlitespeed](openlitespeed) | [ Dockerhub: openquantumsafe/openlitespeed](https://hub.docker.com/repository/docker/openquantumsafe/openlitespeed) | unsupported -| **h2load** | [Github: oqs-demos/h2load](h2load) | [ Dockerhub: openquantumsafe/h2load](https://hub.docker.com/repository/docker/openquantumsafe/h2load) | unsupported -| **HAproxy** | [Github: oqs-demos/haproxy](haproxy) | [Dockerhub: openquantumsafe/haproxy](https://hub.docker.com/repository/docker/openquantumsafe/haproxy) | unsupported -| **Mosquitto** | [Github: oqs-demos/mosquitto](mosquitto) | [Dockerhub: openquantumsafe/mosquitto](https://hub.docker.com/repository/docker/openquantumsafe/mosquitto) | unsupported -| **Envoy** | [Github: oqs-demos/envoy](envoy) | [ Dockerhub: openquantumsafe/envoy](https://hub.docker.com/repository/docker/openquantumsafe/envoy) | unsupported -| **Unbound** | [Github: oqs-demos/unbound](unbound) | [ Dockerhub: openquantumsafe/unbound](https://hub.docker.com/repository/docker/openquantumsafe/unbound) | unsupported +| | **Build instructions** | **Pre-built Docker image or binary files** | Support? | +|-------------------|----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -------- | +| **curl** | [Github: oqs-demos/curl](curl) | [Dockerhub: openquantumsafe/curl](https://hub.docker.com/repository/docker/openquantumsafe/curl), [Dockerhub: openquantumsafe/curl-quic](https://hub.docker.com/repository/docker/openquantumsafe/curl-quic) | @baentsch, @pi-314159 +| **Apache httpd** | [Github: oqs-demos/httpd](httpd) | [Dockerhub: openquantumsafe/httpd](https://hub.docker.com/repository/docker/openquantumsafe/httpd) | @baentsch +| **nginx** | [Github: oqs-demos/nginx](nginx) | [Dockerhub: openquantumsafe/nginx](https://hub.docker.com/repository/docker/openquantumsafe/nginx), [Dockerhub: openquantumsafe/nginx-quic](https://hub.docker.com/repository/docker/openquantumsafe/nginx-quic) | @baentsch, @bhess, @pi-314159 +| **Chromium** | [Github: oqs-demos/chromium](chromium) (limited support) | - | @pi-314159 | +| **Locust** | [Github: oqs-demos/locust](locust) | - | @davidgca| +| **OpenSSH** | [Github: oqs-demos/openssh](openssh) | [Dockerhub: openquantumsafe/openssh](https://hub.docker.com/repository/docker/openquantumsafe/openssh) | unsupported +| **Wireshark** | [Github: oqs-demos/wireshark](wireshark) | [Dockerhub: openquantumsafe/wireshark](https://hub.docker.com/repository/docker/openquantumsafe/wireshark) | unsupported +| **Epiphany** | [Github: oqs-demos/epiphany](epiphany) | [Dockerhub: openquantumsafe/epiphany](https://hub.docker.com/repository/docker/openquantumsafe/epiphany) | unsupported +| **OpenVPN** | [Github: oqs-demos/openvpn](openvpn) | [Dockerhub: openquantumsafe/openvpn](https://hub.docker.com/repository/docker/openquantumsafe/openvpn) | unsupported +| **ngtcp2** | [Github: oqs-demos/ngtcp2](ngtcp2) | Dockerhub: [Server: openquantumsafe/ngtcp2-server](https://hub.docker.com/repository/docker/openquantumsafe/ngtcp2-server), [Client: openquantumsafe/ngtcp2-client](https://hub.docker.com/repository/docker/openquantumsafe/ngtcp2-client) | unsupported +| **OpenLiteSpeed** | [Github: oqs-demos/openlitespeed](openlitespeed) | [ Dockerhub: openquantumsafe/openlitespeed](https://hub.docker.com/repository/docker/openquantumsafe/openlitespeed) | unsupported +| **h2load** | [Github: oqs-demos/h2load](h2load) | [ Dockerhub: openquantumsafe/h2load](https://hub.docker.com/repository/docker/openquantumsafe/h2load) | unsupported +| **HAproxy** | [Github: oqs-demos/haproxy](haproxy) | [Dockerhub: openquantumsafe/haproxy](https://hub.docker.com/repository/docker/openquantumsafe/haproxy) | unsupported +| **Mosquitto** | [Github: oqs-demos/mosquitto](mosquitto) | [Dockerhub: openquantumsafe/mosquitto](https://hub.docker.com/repository/docker/openquantumsafe/mosquitto) | unsupported +| **Envoy** | [Github: oqs-demos/envoy](envoy) | [ Dockerhub: openquantumsafe/envoy](https://hub.docker.com/repository/docker/openquantumsafe/envoy) | unsupported +| **Unbound** | [Github: oqs-demos/unbound](unbound) | [ Dockerhub: openquantumsafe/unbound](https://hub.docker.com/repository/docker/openquantumsafe/unbound) | unsupported + It should be possible to use the openssl (s_client), curl and GNOME Web/epiphany clients with all algorithm combinations available at the Open Quantum Safe TLS/X.509 interoperability test server at https://test.openquantumsafe.org (set up using `oqs-provider v0.6.1` and `liboqs v0.10.1`) but no guarantees are given for software not explicitly labelled with the name of a person offering support for it. Since [OQS-BoringSSL](https://github.com/open-quantum-safe/boringssl) no longer maintains the same set of algorithms, software that depends on OQS-BoringSSL (e.g., nginx-quic and curl-quic) may not fully (inter)operate with the test server. @@ -62,6 +64,7 @@ All modifications to this repository are released under the same terms as [liboq Dindyal Jeevesh Rishi (University of Mauritius / cyberstorm.mu) Dan Rouhana (University of Washington) JT (Henan Raytonne Trading Company) + David Gomez-Cambronero (Telefonica Innovacion digital) ## Acknowledgments diff --git a/locust/Dockerfile b/locust/Dockerfile new file mode 100644 index 00000000..c35abc68 --- /dev/null +++ b/locust/Dockerfile @@ -0,0 +1,115 @@ +# define the liboqs tag to be used +ARG LIBOQS_TAG=0.11.0 + +# define the oqsprovider tag to be used +ARG OQSPROVIDER_TAG=0.7.0 + +# define the openssl version to be baked in +ARG OPENSSL_BRANCH=openssl-3.3.2 + +# Default location where all binaries wind up: +ARG INSTALLDIR=/opt/oqssa + +# Default Python version to be used +ARG PYTHON_VERSION=3.12.6 + +# liboqs build type variant; maximum portability of image: +ARG LIBOQS_BUILD_DEFINES="-DOQS_DIST_BUILD=ON" + +# Default root CA signature algorithm; can be set to any listed at https://github.com/open-quantum-safe/oqs-provider#algorithms +ARG SIG_ALG="dilithium3" + +# Default KEM algorithms; can be set to any listed at https://github.com/open-quantum-safe/oqs-provider#algorithms +ARG DEFAULT_GROUPS="x25519:x448:kyber512:p256_kyber512:kyber768:p384_kyber768:kyber1024:p521_kyber1024" + +# Define the degree of parallelism when building the image; leave the number away only if you know what you are doing +ARG MAKE_DEFINES="-j 16" + +# Define the Alpine version to be used +ARG ALPINE_VERSION=3.20.3 + +FROM alpine:${ALPINE_VERSION} +# Take in all global args +ARG LIBOQS_TAG +ARG OQSPROVIDER_TAG +ARG INSTALLDIR +ARG LIBOQS_BUILD_DEFINES +ARG SIG_ALG +ARG DEFAULT_GROUPS +ARG MAKE_DEFINES +ARG PYTHON_VERSION +ARG OPENSSL_BRANCH + +LABEL version="1" + +ENV DEBIAN_FRONTEND noninteractive +ENV LD_LIBRARY_PATH=${INSTALLDIR}/lib + +RUN apk update && apk upgrade + +# Get all software packages required for builing all components: +RUN apk add build-base linux-headers \ + libtool automake autoconf cmake ninja \ + make \ + git wget vim nano zlib-dev py3-pip tcpdump python3-dev + +# get all sources +WORKDIR /opt +RUN git clone --depth 1 --branch ${LIBOQS_TAG} https://github.com/open-quantum-safe/liboqs && \ + git clone --depth 1 --branch ${OPENSSL_BRANCH} https://github.com/openssl/openssl.git && \ + git clone --depth 1 --branch ${OQSPROVIDER_TAG} https://github.com/open-quantum-safe/oqs-provider.git + +# build OpenSSL3 +WORKDIR /opt/openssl +RUN LDFLAGS="-Wl,-rpath -Wl,${INSTALLDIR}/lib64" ./config shared enable-zlib no-comp --prefix=${INSTALLDIR} && \ + make ${MAKE_DEFINES} && make install_sw install_ssldirs install_dev && \ + if [ -d ${INSTALLDIR}/lib64 ]; then ln -s ${INSTALLDIR}/lib64 ${INSTALLDIR}/lib; fi && \ + if [ -d ${INSTALLDIR}/lib ]; then ln -s ${INSTALLDIR}/lib ${INSTALLDIR}/lib64; fi + + +# build liboqs +WORKDIR /opt/liboqs +RUN mkdir build && \ + cd build && \ + cmake -G"Ninja" .. ${LIBOQS_BUILD_DEFINES} -DCMAKE_INSTALL_PREFIX=${INSTALLDIR} && \ + ninja install + +# set path to use 'new' openssl. Dyn libs have been properly linked in to match +ENV PATH="${INSTALLDIR}/bin:${PATH}" +ENV LD_LIBRARY_PATH=${INSTALLDIR}/lib + +# build & install provider (and activate by default) +WORKDIR /opt/oqs-provider +RUN ln -s ../openssl . && \ + cmake -DOPENSSL_ROOT_DIR=${INSTALLDIR} -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=${INSTALLDIR} -S . -B _build && \ + cmake --build _build && \ + cp _build/lib/oqsprovider.so ${INSTALLDIR}/lib64/ossl-modules && \ + sed -i "s/default = default_sect/default = default_sect\noqsprovider = oqsprovider_sect/g" /opt/oqssa/ssl/openssl.cnf && \ + sed -i "s/\[default_sect\]/\[default_sect\]\nactivate = 1\n\[oqsprovider_sect\]\nactivate = 1\n/g" /opt/oqssa/ssl/openssl.cnf && \ + sed -i "s/providers = provider_sect/providers = provider_sect\nssl_conf = ssl_sect\n\n\[ssl_sect\]\nsystem_default = system_default_sect\n\n\[system_default_sect\]\nGroups = \$ENV\:\:DEFAULT_GROUPS\n/g" /opt/oqssa/ssl/openssl.cnf && \ + sed -i "s/\# Use this in order to automatically load providers/\# Set default KEM groups if not set via environment variable\nKDEFAULT_GROUPS = $DEFAULT_GROUPS\n\n# Use this in order to automatically load providers/g" /opt/oqssa/ssl/openssl.cnf && \ + sed -i "s/HOME\t\t\t= ./HOME\t\t= .\nDEFAULT_GROUPS\t= ${DEFAULT_GROUPS}/g" /opt/oqssa/ssl/openssl.cnf + +# generate certificates for openssl s_server +ENV OPENSSL=${INSTALLDIR}/bin/openssl +ENV OPENSSL_CNF=${INSTALLDIR}/ssl/openssl.cnf + +WORKDIR ${INSTALLDIR}/bin +# generate CA key and cert +RUN set -x; \ + ${OPENSSL} req -x509 -new -newkey ${SIG_ALG} -keyout CA.key -out CA.crt -nodes -subj "/CN=oqstest CA" -days 365 -config ${OPENSSL_CNF} + +# Download current test.openquantumsafe.org test CA cert +WORKDIR ${INSTALLDIR} +RUN wget --no-check-certificate https://test.openquantumsafe.org/CA.crt && \ + mv CA.crt oqs-testca.pem + +# Install Locust +RUN mkdir /home/locust && cd /home/locust +ENV CFLAGS="-I/opt/python-${PYTHON_VERSION}-custom/include/${PYTHON_VERSION}" +ENV LDFLAGS="-L/opt/python-${PYTHON_VERSION}-custom/lib" +COPY requirements.txt /home/locust +RUN pip3 install --break-system-packages --upgrade pip +RUN pip3 install --break-system-packages -r /home/locust/requirements.txt +ADD / /mnt/locust + diff --git a/locust/README.md b/locust/README.md new file mode 100644 index 00000000..dd104bdc --- /dev/null +++ b/locust/README.md @@ -0,0 +1,32 @@ +## Purpose +This directory contains a Dockerfile that builds Locust using OpenSSL v3 using the [OQS provider](https://github.com/open-quantum-safe/oqs-provider) and Python3, which allows `Locust` to negotiate quantum-safe keys and use quantum-safe authentication in TLS 1.3. + +For more information on `Locust`, see the [official Locust project](https://github.com/locustio/locust). + +## Quick start + +1) Be sure to have [docker installed](https://docs.docker.com/install). +2) Run `docker build -t oqs-locust:0.0.1 .` to create a post quantum-enabled Locust docker image. +3) In order to configure endpoints and their weight, modify the file [scenarios/locustfile.py](scenarios/locustfile.py), more information can be found in [USAGE.md](USAGE.md) +4) To verify all components perform quantum-safe operations, first start the container with docker compose + +``` +LOGGER_LEVEL=DEBUG HOST=https://YOUR_QS_HOST:4433 docker compose up --scale worker=8 +``` +4) Connect to the locust web interface at `http://localhost:8189` and start a load test. + + +## Notes on this Version: + +In this version, we utilize the subprocess module to execute the oqs-openssl command within Locust. Ideally, the objective should be to leverage native Python libraries. However, as of now, there are no Python libraries that support quantum-safe (QS) group for TLS 1.3. Once such libraries become available, we should prioritize recompiling Python (for add the OQS-openssl version) and using the appropriate Python libraries for this functionality. + +For further reference on the Locust API, please refer to the official documentation [here](https://docs.locust.io/en/stable/). + +## Usage + +Information how to use locust: [available in the separate file USAGE.md](USAGE.md). + +## Disclaimer + +[THIS IS NOT FIT FOR PRODUCTION USE] + diff --git a/locust/USAGE.md b/locust/USAGE.md new file mode 100644 index 00000000..aa03fddd --- /dev/null +++ b/locust/USAGE.md @@ -0,0 +1,56 @@ +## Purpose +This directory contains a Dockerfile that builds the [OpenSSL v3](https://github.com/openssl/openssl) [OQS provider](https://github.com/open-quantum-safe/oqs-provider), and Python3 which allows locust to negotiate quantum-safe keys in TLS 1.3. + +## Start +1) Run `docker build -t oqs-locust:0.0.1 .` to create a post quantum-enabled Locust docker image. +2) To verify all components perform quantum-safe operations, first start the container with docker compose, setting all environment variables as needed. For example: +``` +LOGGER_LEVEL=DEBUG HOST=https://YOUR_QS_HOST:4433 GROUP=kyber1024 docker compose up --scale worker=8 +``` +3) Connect to the locust web interface at `http://localhost:8189` and start a load test. + +By default, Locust supports all algorithms supported by the OQS openssl. + +Some environments variables you need to know +- LOGGER_LEVEL: Set the log level for the locust master and worker. Default is ERROR. +- HOST: Set the host to test. Default is https://test:4433 +- WORKERS: Set the number of workers. Default is 8. Ideally, the number of workers should be the same as the number of cores in the machine. +- MASTER_PORT: Set the port for the master. Default is 8189. +- GROUP: Set the key exchange scheme for openssl. Default is kyber768. + +In Locust web server, you need to set 2 variables: +- Number of users to simulate: The number of users to simulate that will hit the server. +- Hatch rate: The rate per second in which users are spawned. + +After that, you can start the test: + +STATISTICS +![img.png](images/img.png) + +CHARTS +![img.png](images/img_charts.png) + +### HOW TO CREATE A PERFORMANCE SCENARIO IN LOCUST + +Using Locust, you can configure a performance scenario. For this, you can use the following structure. Note: This is just a basic example, and the real implementation might use subprocess and openssl to handle post-quantum cryptographic curves, as in the actual [locustfile.py](scenarios/locustfile.py). + +```python +from locust import HttpUser, TaskSet, task, between +class UserBehavior(TaskSet): + # on_start is called when a Locust starts, before any task is scheduled + def on_start(self): + self.index() + self.about() + + # tasks is a list of tasks that a Locust will choose from to execute + # tasks are chosen with the weighted_task_set attribute + @task(1) + def index(self): + self.client.get("/") + + # in this case the about task is twice as likely to be chosen as the index task + @task(2) + def about(self): + self.client.get("/about/") + + diff --git a/locust/docker-compose.yaml b/locust/docker-compose.yaml new file mode 100644 index 00000000..02e47e65 --- /dev/null +++ b/locust/docker-compose.yaml @@ -0,0 +1,29 @@ +version: '3' + +services: + master: + image: oqs-locust:0.0.1 + volumes: + - .:/mnt/locust + ports: + - ${MASTER_HTTP_PORT:-8189}:8089 + environment: + - LOGGER_LEVEL=${LOGGER_LEVEL:-ERROR} + - GROUP=${GROUP:-kyber768} + logging: + options: + max-size: "50m" + command: locust -f /mnt/locust/scenarios/locustfile.py --master --host ${HOST:-https://test:4433} + + worker: + image: oqs-locust:0.0.1 + volumes: + - .:/mnt/locust + environment: + - LOGGER_LEVEL=${LOGGER_LEVEL:-ERROR} + - GROUP=${GROUP:-kyber768} + - HOST=${HOST:-https://test:4433} + logging: + options: + max-size: "50m" + command: locust -f /mnt/locust/scenarios/locustfile.py --worker --master-host ${MASTER_HOST:-master} diff --git a/locust/images/img.png b/locust/images/img.png new file mode 100644 index 00000000..42f432a6 Binary files /dev/null and b/locust/images/img.png differ diff --git a/locust/images/img_charts.png b/locust/images/img_charts.png new file mode 100644 index 00000000..c18ad2dc Binary files /dev/null and b/locust/images/img_charts.png differ diff --git a/locust/requirements.txt b/locust/requirements.txt new file mode 100644 index 00000000..72131424 --- /dev/null +++ b/locust/requirements.txt @@ -0,0 +1,3 @@ +locust==2.31.6 +liboqs==0.9.1 +psutil==6.0.0 diff --git a/locust/scenarios/locustfile.py b/locust/scenarios/locustfile.py new file mode 100644 index 00000000..9d1d3cfa --- /dev/null +++ b/locust/scenarios/locustfile.py @@ -0,0 +1,88 @@ +import logging +import os +import subprocess +import time + +from locust import HttpUser, task, between +from urllib.parse import urlparse + + +url = str(os.environ.get("HOST")) +parsed_url = urlparse(url) +host = parsed_url.hostname +port = parsed_url.port + +logger_level = str(os.environ.get("LOGGER_LEVEL")) +logger = logging.getLogger(__name__) +logger.setLevel(logger_level) + +group = str(os.environ.get("GROUP")) + +class QscNginxUser(HttpUser): + wait_time = between(1, 2) + + def on_start(self): + self.client.base_url = host + logger.info("Starting Locust test using OpenSSL for TLS connection") + + def make_post_quantum_request_with_openssl(self, endpoint): + try: + logger.debug(f"Making request to {url}{endpoint} with group {group}") + host_and_port = host + ":" + str(port) + http_headers="Post-quantum" + request_name = f"Group {group} {endpoint}" + + start = time.time() + result = subprocess.run( + ["openssl", "s_client", "-groups", group, "-connect", host_and_port, "-ign_eof"], + input=f"GET {endpoint} HTTP/1.1\r\n" + f"Host: {host}\r\n" + f"User-Agent: {http_headers}\r\n" + f"Connection: close\r\n\r\n", + capture_output=True, text=True + ) + total = int((time.time() - start) * 1000) + logger.debug(f"result: {result.stdout}") + response_output = result.stdout + content_length = 0 + headers, _, body = response_output.partition("\r\n\r\n") + for line in headers.splitlines(): + if line.lower().startswith("content-length:"): + content_length = int(line.split(":")[1].strip()) + break + if result.returncode == 0: + logger.debug(f"Request to {endpoint} succeeded") + logger.debug(f"Response:\n{result.stdout}") + self.environment.events.request.fire( + request_type="GET", + name=request_name, + response_time=total, + response_length=content_length, + exception=None, + ) + else: + logger.error(f"Request to {endpoint} failed with return code {result.returncode}") + logger.error(f"Error:\n{result.stderr}") + self.environment.events.request.fire( + request_type="GET", + name=host+endpoint, + response_time=total, + response_length=content_length, + exception=f"Error Code: {result.returncode} - {result.stderr}" + ) + + except subprocess.CalledProcessError as e: + logger.error(f"Error executing OpenSSL command: {e}") + + # Change the following methods to use the make_post_quantum_request_with_openssl method where + # first parameter is the endpoint and the second parameter is the group (kyber768 by default) + @task(1) + def post_quantum_customers(self): + self.make_post_quantum_request_with_openssl("/customers") + + @task(1) + def post_quantum_devices(self): + self.make_post_quantum_request_with_openssl("/devices") + + +