├── .dockerignore ├── .gitignore ├── .gitmodules ├── CITATION.bib ├── Dockerfile ├── LICENSE.md ├── README.md ├── measuring ├── .gitignore ├── LICENSE.md ├── README.md ├── archived-results │ ├── .gitignore │ ├── README.md │ ├── download.sh │ └── upload.sh ├── run_experiment.sh └── scripts │ ├── count-hs-bytes.py │ ├── create-experimental-setup.sh │ ├── experiment.py │ ├── measure-handshake-size.sh │ ├── process.py │ ├── setinitcwd.sh │ └── setup_ns.sh ├── secsidh-rs ├── update_algorithms.sh └── update_submodules.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | .git/* 2 | Dockerfile 3 | .dockerignore 4 | *.sh 5 | **/target/* 6 | mk-cert/*.crt 7 | mk-cert/*.pub 8 | mk-cert/*.bin 9 | mk-cert/*.key 10 | mk-cert/tbs* 11 | *.o 12 | *.a 13 | *.so 14 | measuring/* 15 | rustls/Cargo.lock 16 | webpki/Cargo.lock 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "webpki"] 2 | path = webpki 3 | url = https://github.com/thomwiggers/webpki.git 4 | branch = optls 5 | [submodule "mk-cert"] 6 | path = mk-cert 7 | url = https://github.com/thomwiggers/mk-cert 8 | [submodule "rustls"] 9 | path = rustls 10 | url = https://github.com/thomwiggers/rustls.git 11 | branch = secsidh-nike 12 | [submodule "ring"] 13 | path = ring 14 | url = https://github.com/thomwiggers/ring.git 15 | [submodule "oqs-rs"] 16 | path = oqs-rs 17 | url = https://github.com/thomwiggers/liboqs-rust.git 18 | [submodule "secsidh"] 19 | path = secsidh 20 | url = https://github.com/kemtls-secsidh/secsidh.git 21 | -------------------------------------------------------------------------------- /CITATION.bib: -------------------------------------------------------------------------------- 1 | @inproceedings{CCS:SchSteWig20, 2 | author = {Schwabe, Peter and Stebila, Douglas and Wiggers, Thom}, 3 | title = {Post-Quantum {TLS} Without Handshake Signatures}, 4 | year = {2020}, 5 | isbn = {9781450370899}, 6 | publisher = {Association for Computing Machinery}, 7 | address = {New York, {NY}, {USA}}, 8 | url = {https://thomwiggers.nl/publication/kemtls/}, 9 | doi = {10.1145/3372297.3423350}, 10 | booktitle = {Proceedings of the 2020 {ACM} {SIGSAC} Conference on Computer and Communications Security}, 11 | pages = {1461–1480}, 12 | numpages = {20}, 13 | keywords = {transport layer security, key-encapsulation mechanism, {NIST PQC}, post-quantum cryptography}, 14 | location = {Virtual Event, {USA}}, 15 | series = {{CCS '20}} 16 | } 17 | 18 | @misc{EPRINT:SchSteWig20, 19 | author = {Peter Schwabe and Douglas Stebila and Thom Wiggers}, 20 | title = {Post-quantum {TLS} without handshake signatures}, 21 | year = 2022, 22 | month = mar, 23 | note = {full online version}, 24 | url = {https://ia.cr/2020/534}, 25 | } 26 | 27 | @inproceedings{ESORICS:SchSteWig21, 28 | title = {More efficient post-quantum {KEMTLS} with pre-distributed public keys}, 29 | author = {Peter Schwabe and Douglas Stebila and Thom Wiggers}, 30 | year = 2021, 31 | month = sep, 32 | url = {https://thomwiggers.nl/publication/kemtlspdk/}, 33 | editor = {Bertino, Elisa and Shulman, Haya and Waidner, Michael}, 34 | booktitle = {Computer Security -- ESORICS 2021}, 35 | series = {Lecture Notes in Computer Science}, 36 | publisher = {Springer International Publishing}, 37 | address = {Cham}, 38 | pages = {3--22}, 39 | isbn = {978-3-030-88418-5}, 40 | doi = {10.1007/978-3-030-88418-5_1}, 41 | } 42 | 43 | @misc{EPRINT:SchSteWig21, 44 | author = {Peter Schwabe and Douglas Stebila and Thom Wiggers}, 45 | title = {More efficient post-quantum {KEMTLS} with pre-distributed public keys}, 46 | howpublished = {Cryptology ePrint Archive, Paper 2021/779}, 47 | year = {2022}, 48 | month = mar, 49 | note = {full online version}, 50 | url = {https://eprint.iacr.org/2021/779} 51 | } 52 | 53 | @misc{EPRINT:CCCMRRSW23, 54 | author = {Fabio Campos and Jorge Chavez-Saab and Jesús-Javier Chi-Domínguez and Michael Meyer and Krijn Reijnders and Francisco Rodríguez-Henríquez and Peter Schwabe and Thom Wiggers}, 55 | title = {Optimizations and Practicality of High-Security {CSIDH}}, 56 | howpublished = {Cryptology ePrint Archive, Paper 2023/793}, 57 | year = {2023}, 58 | url = {https://eprint.iacr.org/2023/793} 59 | } 60 | 61 | @phdthesis{RU:Wiggers24, 62 | title = {Post-Quantum {TLS}}, 63 | author = {Thom Wiggers}, 64 | date = {2024-01-09}, 65 | school = {Radboud University}, 66 | address = {Nijmegen, The Netherlands}, 67 | url = {https://thomwiggers.nl/publication/thesis/} 68 | } 69 | 70 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Author: Thom Wiggers 2 | # LICENSE: CC0 3 | # 4 | FROM rust:1.66-bullseye AS builder 5 | 6 | SHELL ["/bin/bash", "-c"] 7 | 8 | EXPOSE 8443 443/tcp 9 | 10 | ADD https://apt.llvm.org/llvm-snapshot.gpg.key /llvm.key 11 | RUN apt-key add /llvm.key 12 | 13 | # Install requirements 14 | RUN echo "deb http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-12 main" > /etc/apt/sources.list.d/llvm.list 15 | RUN apt-get update -qq 16 | RUN apt-get install -qq -y pipenv libssl-dev cmake clang-12 llvm-12 17 | 18 | # Default C compiler 19 | # XXX: Somehow clang breaks. 20 | ENV CC=gcc 21 | 22 | # Rust options 23 | ENV RUSTFLAGS "-C target-cpu=native -C link-arg=-s" 24 | ENV RUST_MIN_STACK "20971520" 25 | 26 | # Copy in the source 27 | COPY mk-cert /usr/src/pqtls/mk-cert 28 | 29 | # Cleanup mk-cert and install deps 30 | WORKDIR /usr/src/pqtls/mk-cert 31 | RUN pipenv install 32 | RUN ./clean.sh 33 | 34 | # populate cargo build caches 35 | WORKDIR /usr/src/pqtls/mk-cert/signutil 36 | RUN echo "pub use oqs::sig::Algorithm::Dilithium2 as alg;" > src/lib.rs 37 | RUN cargo update 38 | RUN cargo build --release --examples 39 | 40 | WORKDIR /usr/src/pqtls/mk-cert/kemutil 41 | RUN echo "pub use oqs::kem::Algorithm::Kyber512 as thealgorithm;" > src/kem.rs 42 | RUN cargo update 43 | RUN cargo build --release --features oqs 44 | 45 | COPY secsidh /usr/src/pqtls 46 | COPY secsidh-rs /usr/src/pqtls/secsidh-rs 47 | WORKDIR /usr/src/pqtls/mk-cert/csidhutil 48 | RUN echo "pub use csidh_rust::ctidh512 as csidh;" > src/instance.rs 49 | RUN cargo update 50 | RUN cargo build --features csidh-rust --release 51 | 52 | WORKDIR /usr/src/pqtls/mk-cert/xmss-rs 53 | RUN cargo build --release 54 | 55 | # Copy remaining sources 56 | COPY webpki /usr/src/pqtls/webpki 57 | COPY ring /usr/src/pqtls/ring 58 | COPY rustls /usr/src/pqtls/rustls 59 | 60 | # Generate rustls build cache 61 | WORKDIR /usr/src/pqtls/rustls/rustls-mio 62 | RUN cargo build --release --examples 63 | 64 | # pre-Compile tlsserver and tlsclient examples 65 | WORKDIR /usr/src/pqtls/rustls/rustls-mio/ 66 | RUN cargo build --release --example tlsserver && \ 67 | cargo build --release --example tlsclient 68 | 69 | # These must exactly match what is listed in the options of mk-cert/encoder.py 70 | # (and those follow from liboqs) 71 | ARG KEX_ALG="Kyber512" 72 | # re-export build args as env vars 73 | ENV KEX_ALG $KEX_ALG 74 | 75 | # Update the KEX alg 76 | RUN sed -i 's@NamedGroup::[[:alnum:]]\+@NamedGroup::'${KEX_ALG}'@' /usr/src/pqtls/rustls/rustls/src/client/default_group.rs 77 | 78 | ARG RUSTLS_FEATURES="" 79 | # Compile tlsserver and tlsclient examples 80 | RUN cargo build --release $RUSTLS_FEATURES --example tlsserver && \ 81 | cargo build --release $RUSTLS_FEATURES --example tlsclient 82 | 83 | # These must exactly match what is listed in the options of mk-cert/encoder.py 84 | # (and those follow from liboqs) 85 | ARG ROOT_SIGALG="Dilithium2" 86 | ARG INT_SIGALG="Dilithium2" 87 | ARG LEAF_ALG="Dilithium2" 88 | ARG CLIENT_ALG="Kyber512" 89 | ARG CLIENT_CA_ALG="Dilithium2" 90 | ENV ROOT_SIGALG $ROOT_SIGALG 91 | ENV INT_SIGALG $INT_SIGALG 92 | ENV LEAF_ALG $LEAF_ALG 93 | ENV CLIENT_ALG $CLIENT_ALG 94 | ENV CLIENT_CA_ALG $CLIENT_CA_ALG 95 | 96 | # actually generate the certificates 97 | WORKDIR /usr/src/pqtls/mk-cert 98 | RUN pipenv run python encoder.py 99 | 100 | # Set up clean environment 101 | FROM debian:bullseye 102 | 103 | # Install libssl1.1 104 | RUN apt-get update -qq \ 105 | && apt-get install -qq -y libssl1.1 \ 106 | && rm -rf /var/cache/apt 107 | 108 | COPY --from=builder /usr/src/pqtls/rustls/target/release/examples/tlsserver /usr/local/bin/tlsserver 109 | COPY --from=builder /usr/src/pqtls/rustls/target/release/examples/tlsclient /usr/local/bin/tlsclient 110 | COPY --from=builder /usr/src/pqtls/mk-cert/*.crt /certs/ 111 | COPY --from=builder /usr/src/pqtls/mk-cert/*.key /certs/ 112 | COPY --from=builder /usr/src/pqtls/mk-cert/*.pub /certs/ 113 | 114 | WORKDIR /certs 115 | CMD ["echo", "Run tls{server,client} for the rustls-mio server/client with KEX:", $KEX_ALG] 116 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Licensing in this consolidated project 2 | 3 | Due to the nature of this project, making heavy use of (modified) existing software, 4 | there is no uniform licensing that we can make available. 5 | In general, we want to make our modifications available under an as permissive as 6 | possible license, and as such, we make them available under the CC0 license. 7 | 8 | In each of the subdirectories you should find one or more files named `LICENSE`. 9 | The terms in those files generally apply to the software contained within. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Post-Quantum TLS without handshake signatures 2 | 3 | This repository accompanies 4 | 5 | * Peter Schwabe, Douglas Stebila and Thom Wiggers. **More efficient KEMTLS with pre-distributed public keys.** ESORICS 2021. 6 | * Peter Schwabe, Douglas Stebila and Thom Wiggers. **Post-quantum TLS without handshake signatures.** ACM CCS 2020. 7 | * Peter Schwabe, Douglas Stebila and Thom Wiggers. **More efficient KEMTLS with pre-distributed public keys.** IACR Cryptology ePrint Archive, Report 2021/779. Updated online version. March 2022. 8 | * Peter Schwabe, Douglas Stebila and Thom Wiggers. **Post-quantum TLS without handshake signatures.** IACR Cryptology ePrint Archive, Report 2020/534. Updated online version. March 2022. 9 | * Fabio Campos, Jorge Chavez-Saab, Jesús-Javier Chi-Domínguez, Michael Meyer, Krijn Reijnders, Francisco Rodríguez-Henríquez, Peter Schwabe, Thom Wiggers. **Optimizations and Practicality of High-Security CSIDH.** IACR Cryptology ePrint Archive, Report 2023/793. October 2023. 10 | * Thom Wiggers. **Post-Quantum TLS**. PhD thesis, January 2024. 11 | 12 | ```bibtex 13 | @inproceedings{CCS:SchSteWig20, 14 | author = {Schwabe, Peter and Stebila, Douglas and Wiggers, Thom}, 15 | title = {Post-Quantum {TLS} Without Handshake Signatures}, 16 | year = {2020}, 17 | isbn = {9781450370899}, 18 | publisher = {Association for Computing Machinery}, 19 | address = {New York, {NY}, {USA}}, 20 | url = {https://thomwiggers.nl/publication/kemtls/}, 21 | doi = {10.1145/3372297.3423350}, 22 | booktitle = {Proceedings of the 2020 {ACM} {SIGSAC} Conference on Computer and Communications Security}, 23 | pages = {1461–1480}, 24 | numpages = {20}, 25 | keywords = {transport layer security, key-encapsulation mechanism, {NIST PQC}, post-quantum cryptography}, 26 | location = {Virtual Event, {USA}}, 27 | series = {{CCS '20}} 28 | } 29 | 30 | @misc{EPRINT:SchSteWig20, 31 | author = {Peter Schwabe and Douglas Stebila and Thom Wiggers}, 32 | title = {Post-quantum {TLS} without handshake signatures}, 33 | year = 2022, 34 | month = mar, 35 | note = {full online version}, 36 | url = {https://ia.cr/2020/534}, 37 | } 38 | 39 | @inproceedings{ESORICS:SchSteWig21, 40 | title = {More efficient post-quantum {KEMTLS} with pre-distributed public keys}, 41 | author = {Peter Schwabe and Douglas Stebila and Thom Wiggers}, 42 | year = 2021, 43 | month = sep, 44 | url = {https://thomwiggers.nl/publication/kemtlspdk/}, 45 | editor = {Bertino, Elisa and Shulman, Haya and Waidner, Michael}, 46 | booktitle = {Computer Security -- ESORICS 2021}, 47 | series = {Lecture Notes in Computer Science}, 48 | publisher = {Springer International Publishing}, 49 | address = {Cham}, 50 | pages = {3--22}, 51 | isbn = {978-3-030-88418-5}, 52 | doi = {10.1007/978-3-030-88418-5_1}, 53 | } 54 | 55 | @misc{EPRINT:SchSteWig21, 56 | author = {Peter Schwabe and Douglas Stebila and Thom Wiggers}, 57 | title = {More efficient post-quantum {KEMTLS} with pre-distributed public keys}, 58 | howpublished = {Cryptology ePrint Archive, Paper 2021/779}, 59 | year = {2022}, 60 | month = mar, 61 | note = {full online version}, 62 | url = {https://eprint.iacr.org/2021/779} 63 | } 64 | 65 | @misc{EPRINT:CCCMRRSW23, 66 | author = {Fabio Campos and Jorge Chavez-Saab and Jesús-Javier Chi-Domínguez and Michael Meyer and Krijn Reijnders and Francisco Rodríguez-Henríquez and Peter Schwabe and Thom Wiggers}, 67 | title = {Optimizations and Practicality of High-Security {CSIDH}}, 68 | howpublished = {Cryptology ePrint Archive, Paper 2023/793}, 69 | year = {2023}, 70 | url = {https://eprint.iacr.org/2023/793} 71 | } 72 | 73 | @phdthesis{RU:Wiggers24, 74 | title = {Post-Quantum {TLS}}, 75 | author = {Thom Wiggers}, 76 | date = {2024-01-09}, 77 | school = {Radboud University}, 78 | address = {Nijmegen, The Netherlands}, 79 | url = {https://thomwiggers.nl/publication/thesis/} 80 | } 81 | 82 | ``` 83 | 84 | ## Overview of this repository 85 | 86 | The below are all [git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules). 87 | If you want to make a fork of this repository, you will need to also fork the relevant submodules and update your `.gitmodules`. 88 | See also the notes below. 89 | 90 | ### Main folders 91 | 92 | * ``rustls``: modified Rustls TLS stack to implement KEMTLS and post-quantum versions of "normal" TLS 1.3 93 | * ``measuring``: The scripts to measure the above 94 | * ``ring``: Modified version of Ring to allow for longer DER-encoded strings than typically expected from TLS instances. 95 | * ``webpki``: Modified version of WebPKI to work with PQ and KEM public keys in certificates 96 | * ``mk-cert``: Utility scripts to create post-quantum PKI for pqtls and KEMTLS. 97 | 98 | ### Supporting repositories 99 | 100 | * [``oqs-rs``][]: Rust wrapper around ``liboqs``. Contains additional implementations of schemes (notably AVX2 implementations). 101 | * ``mk-cert/xmss-rs``: Rust wrapper around the XMSS reference code, with our custom parameter set (``src/settings.rs``) and utilities for keygen and signing. 102 | 103 | [``oqs-rs``]: https://github.com/open-quantum-safe/liboqs-rust 104 | 105 | ## Working with this repository 106 | 107 | * **MAKE SURE TO CLONE WITH __ALL__ SUBMODULES**. There are submodules _within_ submodules, so clone with ``--recurse-submodules``. 108 | * If you want to make a fork of this repository, you will need to also fork the relevant submodules and update your `.gitmodules`. 109 | * The Dockerfile serves as an example of how everything can be compiled and how test setups can be created. 110 | It is used by the ``./measuring/script/create-experimental-setup.sh`` script, which serves as an example of its use. 111 | * The `mk-certs` folder contains a python script, `encoder.py`, that can be used to create the required PKI. 112 | RSA certificates and X25519 certificates are available in subfolders. 113 | The certificates assume that the server hostname is ``servername``, so put this in your `/etc/hosts`. 114 | Alternatively, override it using the environment variables in the file (which is also how you set which algorithms are used). 115 | * Experimenting with ``rustls`` can be done directly; use the ``rustls-mio`` subfolders 116 | and run ``cargo run --example tlsserver -- --help`` or ``cargo run --example tlsclient -- --help``. 117 | * The measurement setup is handled in the `measuring/` folder. See the `./run_experiment.sh` script. 118 | * Processing of results is done by the `./scripts/process.py` folder. It expects a `data` folder as produced by `./scripts/experiment.py`. 119 | * Downloading archived results can be done through the scripts in ``measuring/archived-results/`` 120 | -------------------------------------------------------------------------------- /measuring/.gitignore: -------------------------------------------------------------------------------- 1 | bin/* 2 | data/* 3 | data-*/* 4 | *.tar.* 5 | processed/* 6 | __pycache__ 7 | -------------------------------------------------------------------------------- /measuring/LICENSE.md: -------------------------------------------------------------------------------- 1 | # SOFTWARE AND DATA LICENSE 2 | 3 | Software and data in this folder is available under CC0 License 4 | 5 | ## CC0 1.0 Universal 6 | 7 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. 8 | 9 | ### Statement of Purpose 10 | 11 | The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). 12 | 13 | Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. 14 | 15 | For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 16 | 17 | 1. __Copyright and Related Rights.__ A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: 18 | 19 | i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; 20 | 21 | ii. moral rights retained by the original author(s) and/or performer(s); 22 | 23 | iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; 24 | 25 | iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; 26 | 27 | v. rights protecting the extraction, dissemination, use and reuse of data in a Work; 28 | 29 | vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and 30 | 31 | vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 32 | 33 | 2. __Waiver.__ To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 34 | 35 | 3. __Public License Fallback.__ Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 36 | 37 | 4. __Limitations and Disclaimers.__ 38 | 39 | a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. 40 | 41 | b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. 42 | 43 | c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. 44 | 45 | d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. 46 | -------------------------------------------------------------------------------- /measuring/README.md: -------------------------------------------------------------------------------- 1 | # Scripts 2 | 3 | * `./create-binaries-and-certs.sh`: creates the necessary binaries and certificates. Uses Docker. 4 | * `experiment.py`: produces a `data` folder to be used by `process.py`. Sets up experiments as necessary. 5 | 6 | ## credit 7 | This is inspired by https://github.com/xvzcf/pq-tls-benchmark/tree/master/emulation-exp/ 8 | 9 | which is the code that accompanies 10 | 11 | Christian Paquin, Douglas Stebila, and Goutam Tamvada. Benchmarking post-quantum cryptography in TLS. IACR Cryptology ePrint Archive, Report 2019/1447. December, 2019. https://eprint.iacr.org/2019/1447. 12 | -------------------------------------------------------------------------------- /measuring/archived-results/.gitignore: -------------------------------------------------------------------------------- 1 | *.tar.* 2 | data-* 3 | draft-* 4 | -------------------------------------------------------------------------------- /measuring/archived-results/README.md: -------------------------------------------------------------------------------- 1 | # Archived results 2 | 3 | This directory contains results that have been processed into results in (versions of) our publications. 4 | 5 | ## ERRATA 6 | 7 | The results from 2021-05 and 2021-12 are flawed; due to an implementation error they all use Kyber512 for ephemeral key exchange. 8 | 9 | ## Obtaining results 10 | 11 | Execute the `download.sh` script. 12 | 13 | ## Uploading new results 14 | 15 | Thom has the keys for the S3 bucket, he can upload results. 16 | 17 | ## Long-term access 18 | 19 | This repository is archived with Zenodo, and the archive files should also be there. 20 | -------------------------------------------------------------------------------- /measuring/archived-results/download.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | wget 'https://kemtls.s3.amazonaws.com/archived-results/data-2020-05-03.tar.xz' 3 | wget 'https://kemtls.s3.amazonaws.com/archived-results/data-2020-06-12.tar.xz' 4 | wget 'https://kemtls.s3.amazonaws.com/archived-results/data-2021-05-07.tar.xz' 5 | wget 'https://kemtls.s3.amazonaws.com/archived-results/data-2021-12-13.tar.xz' 6 | wget 'https://kemtls.s3.amazonaws.com/archived-results/data-2022-01-26.tar.xz' 7 | wget 'https://kemtls.s3.amazonaws.com/archived-results/data-2023-04-14.tar.zst' 8 | wget 'https://kemtls.s3.amazonaws.com/archived-results/secsidh-data-2023-05-20.tar.zst' 9 | wget 'https://kemtls.s3.amazonaws.com/archived-results/secsidh-data-2023-10-11.tar.zst' 10 | -------------------------------------------------------------------------------- /measuring/archived-results/upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # First list already existing files 4 | 5 | BUCKET=kemtls 6 | 7 | existing_files=$(aws s3 ls s3://$BUCKET/archived-results/ | grep -o ".*data-.*.tar.*") 8 | 9 | echo "#!/bin/sh" > ./download.sh 10 | 11 | for file in *.tar.*; do 12 | if [[ ! "${existing_files}" =~ "${file}" ]]; then 13 | echo "$file not already uploaded" 14 | aws s3 cp "$file" "s3://$BUCKET/archived-results/" 15 | else 16 | echo "$file already exists" 17 | fi 18 | echo "wget 'https://kemtls.s3.amazonaws.com/archived-results/$file'" >> ./download.sh 19 | done 20 | -------------------------------------------------------------------------------- /measuring/run_experiment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "Make sure to setup the namespaces beforehand." 6 | echo "Run ./scripts/setup_ns.sh" 7 | 8 | echo "This will remove the current experiment data!" 9 | read -p "Are you sure? " -n 1 -r 10 | echo # (optional) move to a new line 11 | 12 | if [[ $REPLY =~ ^[Nn]$ ]] 13 | then 14 | exit 15 | fi 16 | 17 | rm -rf data 18 | if [ -d bin ]; then 19 | echo "Delete bin folder to recreate stuff" 20 | fi 21 | 22 | sudo killall -9 tlsserver || true 23 | 24 | ntfy="" 25 | if command -v ntfy > /dev/null; then 26 | ntfy="ntfy -b pushover done " 27 | fi 28 | 29 | $ntfy sudo -E python3.9 -u scripts/experiment.py $@ 30 | -------------------------------------------------------------------------------- /measuring/scripts/count-hs-bytes.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script counts the handshake bytes from a json-dumped tshark pcap 3 | 4 | Measure handshakes using 5 | ``` 6 | tshark -i lo -w dump.pcap 7 | ``` 8 | 9 | Dump them to ``dump.json`` using 10 | ``` 11 | tshark -r dump.pcap -R tls -2 -Tjson --no-duplicate-keys > dump.json 12 | ``` 13 | 14 | (Requires a decently recent version of wireshark) 15 | 16 | and then process the dump using this script. 17 | """ 18 | 19 | 20 | import itertools 21 | import json 22 | import os 23 | import sys 24 | 25 | import logging 26 | 27 | SUPPORTED_TYPES = ("pqtls", "pqtls-mut", "kemtls", "kemtls-mut", "kemtls-pdk", "kemtls-pdk-mut") 28 | 29 | if len(sys.argv) != 3 or sys.argv[1] not in SUPPORTED_TYPES: 30 | print(f"Usage: {sys.argv[0]} dump.json") 31 | sys.exit(1) 32 | 33 | logging.basicConfig(level=getattr(logging, os.environ.get('LOGLEVEL', 'DEBUG').upper())) 34 | 35 | with open(sys.argv[2]) as f: 36 | data = json.load(f) 37 | 38 | client_port = None 39 | server_port = None 40 | 41 | class Packet: 42 | 43 | def __init__(self, packet): 44 | self._packet = packet 45 | self._tcp = packet["_source"]["layers"]["tcp"] 46 | self._tls = packet["_source"]["layers"].get("tls") 47 | 48 | @property 49 | def srcport(self): 50 | return self._tcp["tcp.srcport"] 51 | 52 | @property 53 | def dstport(self): 54 | return self._tcp["tcp.dstport"] 55 | 56 | @property 57 | def is_tls(self): 58 | return self._tls is not None 59 | 60 | @property 61 | def tls_records(self): 62 | if not self.is_tls: 63 | raise ValueError 64 | if isinstance(self._tls, list): 65 | all_records = [] 66 | for tls_item in self._tls: 67 | records = tls_item['tls.record'] 68 | if isinstance(records, list): 69 | all_records.extend(records) 70 | else: 71 | all_records.append(records) 72 | return all_records 73 | # just a singular tls record 74 | records = self._tls['tls.record'] 75 | if isinstance(records, list): 76 | return records 77 | else: 78 | return [records] 79 | 80 | def is_css(self): 81 | return any(record.get('tls.change_cipher_spec', False) == "" for record in self.tls_records) 82 | 83 | @property 84 | def is_client_hello(self): 85 | hs = self.tls_records[0].get('tls.handshake') 86 | if not hs: 87 | return False 88 | return hs['tls.handshake.type'] == "1" 89 | 90 | @property 91 | def is_server_hello(self): 92 | hs = self.tls_records[0].get('tls.handshake') 93 | if not hs: 94 | return False 95 | return hs['tls.handshake.type'] == "2" 96 | 97 | @property 98 | def tcp_payload_size(self): 99 | return int(self._tcp['tcp.len']) 100 | 101 | handshakes = [] 102 | for packet in [Packet(p) for p in data]: 103 | if not packet.is_tls: 104 | continue 105 | logging.debug(f"Packet: {packet.srcport} -> {packet.dstport}: {packet.tcp_payload_size} bytes") 106 | if packet.is_client_hello: 107 | client_port = packet.srcport 108 | server_port = packet.dstport 109 | handshakes.append([]) 110 | handshakes[-1].append(packet) 111 | 112 | # Now handshake contains a full TLS handshake 113 | 114 | def length(record): 115 | return 5 + int(record['tls.record.length']) 116 | 117 | # if PQTLS 118 | TLS_TYPE = sys.argv[1] 119 | if TLS_TYPE == "pqtls" or TLS_TYPE == "pqtls-mut": 120 | for handshake in handshakes: 121 | size = 0 122 | # Client Hello 123 | clmsgs = list(filter(lambda p: p.dstport == server_port, handshake)) 124 | cmsgiter = itertools.chain.from_iterable(msg.tls_records for msg in clmsgs) 125 | 126 | assert clmsgs[0].is_client_hello 127 | size += (msgsize := length(next(cmsgiter))) 128 | logging.debug(f"Client hello size: {msgsize}") 129 | 130 | # Server Hello, CSS, EE, Cert, CertV, SFIN 131 | # chain all next server->client messages 132 | servmsgs = list(filter(lambda p: p.srcport == server_port, handshake)) 133 | smsgiter = itertools.chain.from_iterable(msg.tls_records for msg in servmsgs) 134 | assert servmsgs[0].is_server_hello 135 | 136 | size += (msgsize := length(next(smsgiter))) 137 | logging.debug(f"Server hello size: {msgsize}") 138 | 139 | size += (msgsize := length(next(smsgiter))) 140 | assert msgsize == 6, f"expected ccs to be 6 bytes instead of {msgsize}" 141 | logging.debug(f"ChangeCipherSpec size: {msgsize}") 142 | 143 | size += (msgsize := length(next(smsgiter))) 144 | logging.debug(f"EncryptedExtensions size: {msgsize}") 145 | if TLS_TYPE == "pqtls-mut": 146 | size += (msgsize := length(next(smsgiter))) 147 | logging.debug(f"CertificateRequest size: {msgsize}") 148 | 149 | cert_size = (msgsize := length(next(smsgiter))) 150 | while msgsize == 16406: # magic constant for large msgs that got fragmented by TLS 151 | cert_size += (msgsize := length(next(smsgiter))) 152 | size += cert_size 153 | logging.debug(f"Certificate size: {cert_size}") 154 | size += (msgsize := length(next(smsgiter))) 155 | logging.debug(f"CertificateVerify size: {msgsize}") 156 | size += (msgsize := length(next(smsgiter))) 157 | logging.debug(f"ServerFinished size: {msgsize}") 158 | assert msgsize == 58, f"Expected finished size to be 58 bytes instead of {msgsize}" 159 | 160 | # CSS, ClientFinished 161 | size += (msgsize := length(next(cmsgiter))) 162 | assert msgsize == 6, f"expected ccs to be 6 bytes instead of {msgsize}" 163 | logging.debug(f"ChangeCipherSpec size: {msgsize}") 164 | 165 | if TLS_TYPE == "pqtls-mut": 166 | cert_size += (msgsize := length(next(cmsgiter))) 167 | while msgsize == 16406: # magic constant for large msgs that got fragmented by TLS 168 | cert_size += (msgsize := length(next(cmsgiter))) 169 | size += cert_size 170 | logging.debug(f"Certificate size: {cert_size}") 171 | size += (msgsize := length(next(cmsgiter))) 172 | logging.debug(f"CertificateVerify size: {msgsize}") 173 | 174 | size += (msgsize := length(next(cmsgiter))) 175 | logging.debug(f"ClientFinished size: {msgsize}") 176 | assert msgsize == 58, f"Expected finished size to be 58 bytes instead of {msgsize}" 177 | 178 | print(f"Total size: {size}") 179 | 180 | 181 | if TLS_TYPE == "kemtls" or TLS_TYPE == "kemtls-mut": 182 | for handshake in handshakes: 183 | size = 0 184 | 185 | # Client msgs 186 | clmsgs = list(filter(lambda p: p.dstport == server_port, handshake)) 187 | cmsgiter = itertools.chain.from_iterable(msg.tls_records for msg in clmsgs) 188 | # Server msgs 189 | servmsgs = list(filter(lambda p: p.srcport == server_port, handshake)) 190 | smsgiter = itertools.chain.from_iterable(msg.tls_records for msg in servmsgs) 191 | 192 | # Client Hello 193 | ch = next(cmsgiter) 194 | assert clmsgs[0].is_client_hello 195 | size += (msgsize := length(ch)) 196 | logging.debug(f"Client hello size: {msgsize}") 197 | 198 | # Server Hello, CSS, EE, Cert 199 | assert servmsgs[0].is_server_hello 200 | size += (msgsize := length(next(smsgiter))) 201 | logging.debug(f"Server hello size: {msgsize}") 202 | size += (msgsize := length(next(smsgiter))) 203 | logging.debug(f"ChangeCipherSpec size: {msgsize}") 204 | assert msgsize == 6, f"expected ccs to be 6 bytes instead of {msgsize}" 205 | size += (msgsize := length(next(smsgiter))) 206 | logging.debug(f"EncryptedExtensions size: {msgsize}") 207 | 208 | if TLS_TYPE == "kemtls-mut": 209 | size += (msgsize := length(next(smsgiter))) 210 | logging.debug(f"CertificateRequest size: {msgsize}") 211 | 212 | cert_size = (msgsize := length(next(smsgiter))) 213 | while msgsize == 16406: # magic constant for large msgs that got fragmented by TLS 214 | cert_size += (msgsize := length(next(smsgiter))) 215 | size += cert_size 216 | logging.debug(f"Certificate size: {cert_size}") 217 | 218 | # CSS, CKEX 219 | size += (msgsize := length(next(cmsgiter))) 220 | assert msgsize == 6, f"expected ccs to be 6 bytes instead of {msgsize}" 221 | logging.debug(f"Client ChangeCipherSpec: {msgsize}") 222 | size += (msgsize := length(next(cmsgiter))) 223 | logging.debug(f"ClientCiphertext: {msgsize}") 224 | 225 | if TLS_TYPE == "kemtls-mut": 226 | # CCERT 227 | cert_size = (msgsize := length(next(cmsgiter))) 228 | while msgsize == 16406: # magic constant for large msgs that got fragmented by TLS 229 | cert_size += (msgsize := length(next(cmsgiter))) 230 | size += cert_size 231 | logging.debug("ClientCertificate size: %d", cert_size) 232 | 233 | # SKEX 234 | size += (msgsize := length(next(smsgiter))) 235 | logging.debug("ServerCiphertext size: %d", msgsize) 236 | 237 | # CFIN 238 | size += (msgsize := length(next(cmsgiter))) 239 | logging.debug(f"ClientFinished: {msgsize}") 240 | assert msgsize == 58, f"Expected finished size to be 58 bytes instead of {msgsize}" 241 | 242 | # ServerFinished 243 | size += (msgsize := length(next(smsgiter))) 244 | logging.debug(f"ServerFinished size: {msgsize}") 245 | assert msgsize == 58, f"Expected finished size to be 58 bytes instead of {msgsize}" 246 | 247 | 248 | print(f"Total size: {size}") 249 | 250 | if TLS_TYPE == "kemtls-pdk" or TLS_TYPE == "kemtls-pdk-mut": 251 | for handshake in handshakes: 252 | size = 0 253 | # Client messages 254 | clmsgs = list(filter(lambda p: p.dstport == server_port, handshake)) 255 | cmsgiter = itertools.chain.from_iterable(msg.tls_records for msg in clmsgs) 256 | # Server msgs 257 | servmsgs = list(filter(lambda p: p.srcport == server_port, handshake)) 258 | smsgiter = itertools.chain.from_iterable(msg.tls_records for msg in servmsgs) 259 | 260 | # Client hello 261 | ch = next(cmsgiter) 262 | assert clmsgs[0].is_client_hello 263 | size += (msgsize := length(ch)) 264 | logging.debug(f"ClientHello size (PDK): {msgsize}") 265 | 266 | if TLS_TYPE == "kemtls-pdk-mut": 267 | size += (msgsize := length(msg := next(cmsgiter))) 268 | logging.debug(f"ChangeCipherSpec: {msgsize}") 269 | 270 | cert_size = (msgsize := length(next(cmsgiter))) 271 | while msgsize == 16406: # magic constant for large msgs that got fragmented by TLS 272 | cert_size += (msgsize := length(next(cmsgiter))) 273 | size += cert_size 274 | logging.debug("ClientCertificate size: %d", cert_size) 275 | 276 | # SH, CSS, EE, [SKEX], SFIN 277 | assert servmsgs[0].is_server_hello 278 | size += (msgsize := length(next(smsgiter))) 279 | logging.debug(f"Server hello size: {msgsize}") 280 | size += (msgsize := length(next(smsgiter))) 281 | logging.debug(f"ChangeCipherSpec size: {msgsize}") 282 | assert msgsize == 6, f"expected ccs to be 6 bytes instead of {msgsize}" 283 | size += (msgsize := length(next(smsgiter))) 284 | logging.debug(f"EncryptedExtensions size: {msgsize}") 285 | 286 | if TLS_TYPE == "kemtls-pdk-mut": 287 | size += (msgsize := length(next(smsgiter))) 288 | logging.debug("ServerKemCiphertext size: %d", msgsize) 289 | 290 | size += (msgsize := length(next(smsgiter))) 291 | logging.debug(f"ServerFinished size: {msgsize}") 292 | assert msgsize == 58, f"Expected finished size to be 58 bytes instead of {msgsize}" 293 | 294 | # [CSS], CFIN 295 | if not TLS_TYPE == "kemtls-pdk-mut": 296 | size += (msgsize := length(msg := next(cmsgiter))) 297 | assert msgsize == 6, f"expected ccs to be 6 bytes instead of {msgsize}" 298 | logging.debug(f"ChangeCipherSpec: {msgsize}") 299 | size += (msgsize := length(next(cmsgiter))) 300 | logging.debug(f"ClientFinished: {msgsize}") 301 | assert msgsize == 58, f"Expected finished size to be 58 bytes instead of {msgsize}" 302 | 303 | print(f"Total size: {size}") 304 | -------------------------------------------------------------------------------- /measuring/scripts/create-experimental-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | ROOT=$(dirname $0)/../../ 6 | cd $ROOT 7 | 8 | export KEX_ALG="${1:-Kyber512}" 9 | export LEAF_ALG="${2:-Dilithium2}" 10 | export INT_SIGALG="${3:-Dilithium2}" 11 | export ROOT_SIGALG="${4:-Dilithium2}" 12 | export CLIENT_ALG="${5}" 13 | export CLIENT_CA_ALG="${6}" 14 | export KEYGEN_CACHE="${7}" 15 | 16 | tag=${KEX_ALG,,}-${LEAF_ALG,,}-${INT_SIGALG,,}-${ROOT_SIGALG,,} 17 | 18 | extra_args= 19 | if [ "$CLIENT_ALG" != "" ]; then 20 | tag=${tag}-clauth-${CLIENT_ALG,,}-${CLIENT_CA_ALG,,} 21 | extra_args="--build-arg CLIENT_ALG=$CLIENT_ALG --build-arg CLIENT_CA_ALG=$CLIENT_CA_ALG" 22 | fi 23 | 24 | if [ "$KEYGEN_CACHE" != "" ]; then 25 | tag=${tag}-keycache 26 | extra_args="$extra_args --build-arg RUSTLS_FEATURES=--features=lru" 27 | fi 28 | 29 | dockertag="$(echo -n $tag | shasum - | cut -f1 -d' ')" 30 | 31 | docker build \ 32 | --build-arg ROOT_SIGALG=$ROOT_SIGALG \ 33 | --build-arg INT_SIGALG=$INT_SIGALG \ 34 | --build-arg LEAF_ALG=$LEAF_ALG \ 35 | --build-arg KEX_ALG=$KEX_ALG \ 36 | $extra_args \ 37 | --tag "pqtls-builder:$dockertag" . 38 | 39 | volumename=$PWD/measuring/bin/$tag 40 | echo $volumename 41 | rm -rf $volumename 42 | mkdir -p $volumename 43 | 44 | docker run --rm \ 45 | --user $(id -u):$(id -g) \ 46 | --volume $volumename:/output \ 47 | --workdir /output \ 48 | "pqtls-builder:$dockertag" \ 49 | bash -c "cp /usr/local/bin/tlsserver . && 50 | cp /usr/local/bin/tlsclient . && 51 | cp /certs/* ." 52 | 53 | if [ "$SUDO_USER" != "" ]; then 54 | chown -R $SUDO_USER:$SUDO_GID . 55 | fi -------------------------------------------------------------------------------- /measuring/scripts/experiment.py: -------------------------------------------------------------------------------- 1 | """Based on https://github.com/xvzcf/pq-tls-benchmark/blob/master/emulation-exp/code/kex/experiment.py""" 2 | 3 | import csv 4 | import datetime 5 | import io 6 | import itertools 7 | import logging 8 | import math 9 | import multiprocessing 10 | import os 11 | import re 12 | import socket 13 | import subprocess 14 | import sys 15 | import time 16 | from functools import partial 17 | from multiprocessing.connection import Connection 18 | from pathlib import Path 19 | from typing import ( 20 | Final, 21 | Iterable, 22 | List, 23 | Literal, 24 | NamedTuple, 25 | Optional, 26 | Tuple, 27 | Union, 28 | cast, 29 | ) 30 | 31 | ################################################################################################### 32 | ## SETTTINGS ###################################################################################### 33 | ################################################################################################### 34 | 35 | SECSIDH_PAPER = False 36 | 37 | # Original set of latencies 38 | # LATENCIES = ['2.684ms', '15.458ms', '39.224ms', '97.73ms'] 39 | # LATENCIES = ["2.0ms"] 40 | LATENCIES: Final[list[str]] = [ 41 | "15.458ms", 42 | "97.73ms", 43 | ] # ['2.684ms', '15.458ms', '97.73ms'] #['15.458ms', '97.73ms'] 44 | #: Loss rates are too annoying to include 45 | LOSS_RATES: Final[list[int]] = [0] 46 | #: Number of pings used for measuring latency 47 | NUM_PINGS: Final[int] = 20 # for measuring the practical latency 48 | #: Link speeds to use in experiments 49 | SPEEDS: Final[list[int]] = [1000, 10] 50 | 51 | 52 | START_PORT: Final[int] = 10000 53 | 54 | if not SECSIDH_PAPER: 55 | # xvzcf's experiment used POOL_SIZE = 40 56 | # We start as many servers as clients, so make sure to adjust accordingly 57 | POOL_SIZE: int = 40 58 | ITERATIONS: int = 1 59 | # Total iterations = ITERATIONS * POOL_SIZE * MEASUREMENTS_PER_ITERATION 60 | MEASUREMENTS_PER_ITERATION: int = 500 61 | MEASUREMENTS_PER_CLIENT: int = 500 62 | else: 63 | POOL_SIZE: int = 80 64 | ITERATIONS: int = 10 65 | MEASUREMENTS_PER_ITERATION: int = 10 66 | MEASUREMENTS_PER_CLIENT: int = 10 67 | 68 | ################################################################################################### 69 | 70 | ResultType = dict[str, str] 71 | ResultListType = list[ResultType] 72 | 73 | SCRIPTDIR: Path = Path(sys.path[0]).resolve() 74 | sys.path.append(str(SCRIPTDIR.parent.parent / "mk-cert")) 75 | 76 | SERVER_PORTS: Final[list[str]] = [ 77 | str(port) for port in range(START_PORT, START_PORT + POOL_SIZE) 78 | ] 79 | 80 | 81 | import algorithms 82 | 83 | hostname = "servername" 84 | 85 | #: UserID of the user so we don't end up with a bunch of root-owned files 86 | USERID: int = int(os.environ.get("SUDO_UID", 1001)) 87 | #: Group ID of the user so we don't end up with a bunch of root-owned files 88 | GROUPID: int = int(os.environ.get("SUDO_GID", 1001)) 89 | 90 | 91 | class CustomFormatter(logging.Formatter): 92 | """ 93 | Logging Formatter to add colors and count warning / errors 94 | 95 | https://stackoverflow.com/a/56944256/248065 96 | """ 97 | 98 | grey: Final[str] = "\x1b[38;21m" 99 | yellow: Final[str] = "\x1b[33;21m" 100 | red: Final[str] = "\x1b[31;21m" 101 | bold_red: Final[str] = "\x1b[31;1m" 102 | reset: Final[str] = "\x1b[0m" 103 | format_tpl: Final[ 104 | str 105 | ] = "%(asctime)s - %(levelname)-8s - %(message)-50s (%(filename)s:%(lineno)d)" 106 | 107 | FORMATS: Final[dict[int, str]] = { 108 | logging.DEBUG: grey + format_tpl + reset, 109 | logging.INFO: grey + format_tpl + reset, 110 | logging.WARNING: yellow + format_tpl + reset, 111 | logging.ERROR: red + format_tpl + reset, 112 | logging.CRITICAL: bold_red + format_tpl + reset, 113 | } 114 | 115 | def format(self, record: logging.LogRecord) -> str: 116 | log_fmt = self.FORMATS.get(record.levelno) 117 | formatter = logging.Formatter(log_fmt) 118 | return formatter.format(record) 119 | 120 | 121 | ExperimentType = Union[ 122 | Literal["sign"], 123 | Literal["pdk"], 124 | Literal["kemtls"], 125 | Literal["sign-cached"], 126 | Literal["optls"], 127 | ] 128 | 129 | NistLevel = Union[Literal[1], Literal[3], Literal[5]] 130 | 131 | 132 | class Experiment(NamedTuple): 133 | """Represents an experiment""" 134 | 135 | type: ExperimentType 136 | level: Union[NistLevel, Literal["n/a"]] 137 | kex: str 138 | leaf: str 139 | intermediate: Optional[str] = None 140 | root: Optional[str] = None 141 | client_auth: Optional[str] = None 142 | client_ca: Optional[str] = None 143 | keygen_cache: bool = False 144 | 145 | def all_algorithms(self) -> set[str]: 146 | algs = {self.kex, self.leaf} 147 | if self.intermediate is not None: 148 | algs.add(self.intermediate) 149 | if self.root is not None: 150 | algs.add(self.root) 151 | if self.client_auth is not None: 152 | algs.add(self.client_auth) 153 | if self.client_ca is not None: 154 | algs.add(self.client_ca) 155 | 156 | return algs 157 | 158 | 159 | FRODOS = [ 160 | f"FrodoKem{size.title()}{alg.title()}" 161 | for size in ("640", "976", "1344") 162 | for alg in ("aes", "shake") 163 | ] 164 | SMALLFRODOS = [frodo for frodo in FRODOS if "640" in frodo] 165 | KYBERS = ["Kyber512", "Kyber768", "Kyber1024"] 166 | KYBER = {1: "Kyber512", 3: "Kyber768", 5: "Kyber1024"} 167 | BIKES = ["BikeL1", "BikeL3"] # NOTE: IND-CPA! 168 | HQCS = ["Hqc128", "Hqc192", "Hqc256"] 169 | HQC = {1: "Hqc128", 3: "Hqc192", 5: "Hqc256"} 170 | MCELIECES_ = [ 171 | f"ClassicMcEliece{size}{variant}" 172 | for size in ("348864", "460896", "6688128", "6960119", "8192128") 173 | for variant in ["", "f"] 174 | ] 175 | MCELIECEL1 = [mc for mc in MCELIECES_ if "348864" in mc] 176 | MCELIECEL3 = [mc for mc in MCELIECES_ if "460896" in mc] 177 | MCELIECEL5 = [mc for mc in MCELIECES_ if mc not in (MCELIECEL1 + MCELIECEL3)] 178 | MCELIECES = {1: MCELIECEL1, 3: MCELIECEL3, 5: MCELIECEL5} 179 | 180 | DILITHIUMS = ["Dilithium2", "Dilithium3", "Dilithium5"] 181 | # yes I know D2 is level 2, but this is how we map the experiments 182 | DILITHIUM = {1: "Dilithium2", 3: "Dilithium3", 5: "Dilithium5"} 183 | FALCONS = ["Falcon512", "Falcon1024"] 184 | # Idem falcon 1024 which is level 5, but which we use in the L3 experiments 185 | FALCON = {1: "Falcon512", 3: "Falcon1024", 5: "Falcon1024"} 186 | SPHINCSES_ = [ 187 | f"SphincsHaraka{size}{var}Simple" for size in [128, 192, 256] for var in ["s", "f"] 188 | ] 189 | SPHINCSESL1 = [spx for spx in SPHINCSES_ if "128" in spx] 190 | SPHINCSESL3 = [spx for spx in SPHINCSES_ if "192" in spx] 191 | SPHINCSESL5 = [spx for spx in SPHINCSES_ if "256" in spx] 192 | SPHINCS = {1: SPHINCSESL1, 3: SPHINCSESL3, 5: SPHINCSESL5} 193 | 194 | XMSS = {1: "XMSS1", 3: "XMSS3", 5: "XMSS5"} 195 | 196 | UOVS_ = [ 197 | f"Pqov{size}{variant}" 198 | for size in ("1616064", "25611244", "25618472", "25624496") 199 | for variant in ["Classic"] 200 | ] 201 | UOVL1 = [uov for uov in UOVS_ if "1616064" in uov or "25611244" in uov] 202 | UOVL3 = [uov for uov in UOVS_ if "25618472" in uov] 203 | UOVL5 = [uov for uov in UOVS_ if "25624496" in uov] 204 | # UOVS = {1: UOVL1, 3: UOVL3, 5: UOVL5} 205 | UOVS = {1: [], 3: [], 5: []} 206 | 207 | # KEMS: list[str] = [ 208 | # *KYBERS, 209 | # *HQCS, 210 | # *BIKES, 211 | # *SMALLFRODOS, 212 | # ] 213 | 214 | KEMSL1 = [KYBERS[0], BIKES[0], HQCS[0], *SMALLFRODOS] 215 | KEMSL3 = [KYBERS[1], BIKES[1], HQCS[1]] 216 | KEMSL5 = [KYBERS[2], HQCS[2]] 217 | 218 | LEVELS: list[NistLevel] = [1, 3, 5] 219 | KEMS = {1: KEMSL1, 3: KEMSL3, 5: KEMSL5} 220 | 221 | # SIGS: list[str] = [*DILITHIUMS, *FALCONS, *SPHINCSES] 222 | 223 | SIGSL1 = [DILITHIUMS[0], FALCONS[0], *SPHINCSESL1] 224 | SIGSL3 = [DILITHIUMS[1], FALCONS[1], *SPHINCSESL3] 225 | SIGSL5 = [DILITHIUMS[2], FALCONS[1], *SPHINCSESL5] 226 | 227 | SIGS = {1: SIGSL1, 3: SIGSL3, 5: SIGSL5} 228 | 229 | ALGORITHMS: set[Experiment] = { 230 | # Need to specify leaf always as sigalg to construct correct binary directory 231 | # EXPERIMENT - KEX - LEAF - INT - ROOT - CLIENT AUTH - CLIENT CA 232 | # Smaller list of actually printed experiments 233 | Experiment("sign", "n/a", "X25519", "RSA2048", "RSA2048", "RSA2048"), 234 | Experiment( 235 | "sign", "n/a", "X25519", "RSA2048", "RSA2048", "RSA2048", "RSA2048", "RSA2048" 236 | ), 237 | # PQ experiments 238 | # KDDD & KFFF + KSfSfSf + KSsSsSs 239 | *( 240 | Experiment("sign", level, KYBER[level], sig, sig, sig) 241 | for level in LEVELS 242 | for sig in [DILITHIUM[level], FALCON[level], *SPHINCS[level]] 243 | ), 244 | # And the mutual variants 245 | *( 246 | Experiment("sign", level, KYBER[level], sig, sig, sig, sig, sig) 247 | for level in LEVELS 248 | for sig in [DILITHIUM[level], FALCON[level], *SPHINCS[level]] 249 | ), 250 | # KDFF 251 | *( 252 | Experiment( 253 | "sign", level, KYBER[level], DILITHIUM[level], FALCON[level], FALCON[level] 254 | ) 255 | for level in LEVELS 256 | ), 257 | # KDFFDF 258 | *( 259 | Experiment( 260 | "sign", 261 | level, 262 | KYBER[level], 263 | DILITHIUM[level], 264 | FALCON[level], 265 | FALCON[level], 266 | DILITHIUM[level], 267 | FALCON[level], 268 | ) 269 | for level in LEVELS 270 | ), 271 | # KSxXX + KDXX 272 | *( 273 | Experiment("sign", level, KYBER[level], sig, XMSS[level], XMSS[level]) 274 | for level in LEVELS 275 | for sig in [*SPHINCS[level], DILITHIUM[level]] 276 | ), 277 | # KSxXXSxX + KDXXDX 278 | *( 279 | Experiment( 280 | "sign", level, KYBER[level], sig, XMSS[level], XMSS[level], sig, XMSS[level] 281 | ) 282 | for level in LEVELS 283 | for sig in [*SPHINCS[level], DILITHIUM[level]] 284 | ), 285 | # HDDD 286 | *( 287 | Experiment( 288 | "sign", 289 | level, 290 | HQC[level], 291 | DILITHIUM[level], 292 | DILITHIUM[level], 293 | DILITHIUM[level], 294 | ) 295 | for level in LEVELS 296 | ), 297 | # HDDDDD 298 | *( 299 | Experiment( 300 | "sign", 301 | level, 302 | HQC[level], 303 | DILITHIUM[level], 304 | DILITHIUM[level], 305 | DILITHIUM[level], 306 | DILITHIUM[level], 307 | DILITHIUM[level], 308 | ) 309 | for level in LEVELS 310 | ), 311 | ## KEMTLS 312 | # KKDD + KKFF + KKSxSx + KKXX 313 | *( 314 | Experiment("kemtls", level, KYBER[level], KYBER[level], sig, sig) 315 | for level in LEVELS 316 | for sig in {DILITHIUM[level], FALCON[level], *SPHINCS[level], XMSS[level]} 317 | ), 318 | # KKDDKD + KKFFKF + KKSxSxKSx + KKXXKX 319 | *( 320 | Experiment( 321 | "kemtls", level, KYBER[level], KYBER[level], sig, sig, KYBER[level], sig 322 | ) 323 | for level in LEVELS 324 | for sig in {DILITHIUM[level], FALCON[level], *SPHINCS[level], XMSS[level]} 325 | ), 326 | *( 327 | Experiment( 328 | "kemtls", level, HQC[level], HQC[level], DILITHIUM[level], DILITHIUM[level] 329 | ) 330 | for level in LEVELS 331 | ), 332 | *( 333 | Experiment( 334 | "kemtls", 335 | level, 336 | HQC[level], 337 | HQC[level], 338 | DILITHIUM[level], 339 | DILITHIUM[level], 340 | HQC[level], 341 | DILITHIUM[level], 342 | ) 343 | for level in LEVELS 344 | ), 345 | ## PDK 346 | ## KK + HH 347 | *( 348 | Experiment("pdk", level, kem, kem) 349 | for level in LEVELS 350 | for kem in [KYBER[level], HQC[level]] 351 | ), 352 | ## KK-KD + HH-HD 353 | *( 354 | Experiment("pdk", level, kem, kem, client_auth=kem, client_ca=sig) 355 | for level in LEVELS 356 | for kem in [KYBER[level], HQC[level]] 357 | for sig in [DILITHIUM[level], FALCON[level]] 358 | ), 359 | # Mceliece + K 360 | *( 361 | Experiment("pdk", level, KYBER[level], kem) 362 | for level in LEVELS 363 | for kem in MCELIECES[level] 364 | ), 365 | # McEliece + K + KD 366 | *( 367 | Experiment( 368 | "pdk", 369 | level, 370 | KYBER[level], 371 | kem, 372 | client_auth=KYBER[level], 373 | client_ca=sig, 374 | ) 375 | for level in LEVELS 376 | for kem in MCELIECES[level] 377 | for sig in [DILITHIUM[level], FALCON[level]] 378 | ), 379 | # Sign-cached 380 | *( 381 | Experiment("sign-cached", level, kem, sig) 382 | for level in LEVELS 383 | for kem in [KYBER[level], HQC[level]] 384 | for sig in [DILITHIUM[level], FALCON[level]] 385 | ), 386 | *( 387 | Experiment("sign-cached", level, kem, sig, client_auth=sig, client_ca=sig) 388 | for level in LEVELS 389 | for kem in [KYBER[level], HQC[level]] 390 | for sig in [DILITHIUM[level], FALCON[level]] 391 | ), 392 | *( 393 | Experiment("optls", "n/a", nike, nike, "Falcon512", "Falcon512", keygen_cache=cached) 394 | for nike in [ 395 | "CTIDH512", 396 | "CTIDH1024", 397 | # "CSIDH2047M1L226", 398 | # "CTIDH2047M1L226", 399 | # "CSIDH4095M27L262", 400 | # "CTIDH4095M27L262", 401 | # "CTIDH5119M46L244", 402 | ] 403 | for cached in [True, False] 404 | ), 405 | } 406 | 407 | if SECSIDH_PAPER: 408 | # Selection for secsidh paper 409 | ALGORITHMS = { 410 | Experiment("sign", 1, "Kyber512", "Falcon512", "Falcon512"), 411 | Experiment("sign", 1, "Kyber512", "Dilithium2", "Falcon512"), 412 | Experiment("sign", 3, "Kyber768", "Falcon1024", "Falcon512"), 413 | Experiment("sign", 3, "Kyber768", "Dilithium3", "Falcon512"), 414 | Experiment("kemtls", 1, "Kyber512", "Kyber512", "Falcon512"), 415 | Experiment("kemtls", 3, "Kyber768", "Kyber768", "Falcon512"), 416 | *( 417 | Experiment("optls", "n/a", nike, nike, "Falcon512", "Falcon512", keygen_cache=cached) 418 | for nike in [ 419 | "CTIDH512", 420 | "CTIDH1024", 421 | "CSIDH2047M1L226", 422 | "CSIDH4095M27L262", 423 | "CTIDH2047M1L226", 424 | "CTIDH4095M27L262", 425 | "CTIDH5119M46L244", 426 | ] 427 | for cached in [True, False] 428 | ), 429 | } 430 | 431 | BIG_LIST: set[Experiment] = { 432 | Experiment("sign", "n/a", "X25519", "RSA2048", "RSA2048", "RSA2048"), 433 | Experiment( 434 | "sign", "n/a", "X25519", "RSA2048", "RSA2048", "RSA2048", "RSA2048", "RSA2048" 435 | ), 436 | # KEMTLS paper 437 | # PQ Signed KEX 438 | *( 439 | Experiment("sign", level, kem, sig, sig, sig) 440 | for level in LEVELS 441 | for kem in KEMS[level] 442 | for sig in SIGS[level] 443 | ), 444 | # PQ Signed KEX with XMSS roots 445 | *( 446 | Experiment("sign", level, kem, sig, XMSS[level], XMSS[level]) 447 | for level in LEVELS 448 | for kem in KEMS[level] 449 | for sig in SIGS[level] 450 | ), 451 | ## Mutually authenticated 452 | *( 453 | Experiment("sign", level, kem, sig, sig, sig, sig, sig) 454 | for level in LEVELS 455 | for kem in KEMS[level] 456 | for sig in SIGS[level] 457 | ), 458 | # PQ Signed KEX with XMSS roots 459 | *( 460 | Experiment("sign", level, kem, sig, XMSS[level], XMSS[level], sig, XMSS[level]) 461 | for level in LEVELS 462 | for kem in KEMS[level] 463 | for sig in SIGS[level] 464 | ), 465 | # TLS with cached certs + client auth 466 | *( 467 | Experiment("sign-cached", level, kex, sig, client_auth=sig, client_ca=sig) 468 | for level in LEVELS 469 | for kex in KEMS[level] 470 | for sig in SIGS[level] 471 | ), 472 | # KEMTLS 473 | *( 474 | Experiment("kemtls", level, kex, kex, sig, sig) 475 | for level in LEVELS 476 | for kex in KEMS[level] 477 | for sig in [*SIGS[level], XMSS[level]] 478 | ), 479 | # KEMTLS 480 | # KEMTLS mutual 481 | *( 482 | Experiment("kemtls", level, kex, kex, sig, sig, kex, sig) 483 | for level in LEVELS 484 | for kex in KEMS[level] 485 | for sig in [*SIGS[level], XMSS[level]] 486 | ), 487 | # KEMTLS extra combinations L1 488 | *( 489 | Experiment("kemtls", 1, kex, kex, sig, sig2) 490 | for kex in KEMSL1 491 | for sig in [DILITHIUMS[0], FALCONS[0]] 492 | for sig2 in [FALCONS[0], *UOVS[1]] 493 | if sig2 != sig 494 | ), 495 | # KEMTLS extra L3 496 | *( 497 | Experiment("kemtls", 3, kex, kex, sig, sig2) 498 | for kex in KEMSL3 499 | for sig in [DILITHIUMS[1], FALCONS[1]] 500 | for sig2 in [FALCONS[1], *UOVS[3]] 501 | if sig2 != sig 502 | ), 503 | # KEMTLS extra L5 504 | *( 505 | Experiment("kemtls", 5, kex, kex, sig, sig2) 506 | for kex in KEMSL5 507 | for sig in [DILITHIUMS[2], FALCONS[1]] 508 | for sig2 in [FALCONS[1], *UOVS[5]] 509 | if sig2 != sig 510 | ), 511 | # KEMTLS MUTUAL extra combinations 512 | *( 513 | Experiment("kemtls", 1, kex, kex, sig, sig2, kex, sig2) 514 | for kex in KEMSL1 515 | for sig in [DILITHIUMS[0], FALCONS[0]] 516 | for sig2 in [DILITHIUMS[0], FALCONS[0], *UOVS[1]] 517 | ), 518 | # KEMTLS MUTUAL extra combinations 519 | *( 520 | Experiment("kemtls", 3, kex, kex, sig, sig2, kex, sig2) 521 | for kex in KEMSL3 522 | for sig in [DILITHIUMS[1], FALCONS[1]] 523 | for sig2 in [DILITHIUMS[1], FALCONS[1], *UOVS[3]] 524 | if sig2 != sig 525 | ), 526 | *( 527 | Experiment("kemtls", 5, kex, kex, sig, sig2, kex, sig2) 528 | for kex in KEMSL5 529 | for sig in [DILITHIUMS[2], FALCONS[1]] 530 | for sig2 in [DILITHIUMS[2], FALCONS[1], *UOVS[5]] 531 | if sig2 != sig 532 | ), 533 | # PDK 534 | # Level 535 | *(Experiment("pdk", level, kex, kex) for level in LEVELS for kex in KEMS[level]), 536 | # With mutual auth 537 | *( 538 | Experiment("pdk", level, kex, kex, client_auth=kex, client_ca=sig) 539 | for level in LEVELS 540 | for kex in KEMS[level] 541 | for sig in [*SIGS[level], XMSS[level]] 542 | ), 543 | # Special combos with McEliece 544 | *( 545 | Experiment("pdk", level, kex, leaf=mceliece) 546 | for level in LEVELS 547 | for kex in KEMS[level] 548 | for mceliece in MCELIECES[level] 549 | ), 550 | # McEliece + Mutual 551 | *( 552 | Experiment("pdk", level, kex, mceliece, client_auth=kex, client_ca=sig) 553 | for level in LEVELS 554 | for kex in KEMS[level] 555 | for sig in [*SIGS[level], XMSS[level]] 556 | for mceliece in MCELIECES[level] 557 | ), 558 | # OPTLS 559 | *( 560 | Experiment("optls", 1, alg, alg, "Falcon512", "Falcon512", keygen_cache=True) 561 | for alg in ( 562 | "CSIDH2047M1L226", 563 | "CTIDH2047M1L226", 564 | ) 565 | ), 566 | *( 567 | Experiment("optls", 1, alg, "Falcon512", "Falcon512", keygen_cache=False) 568 | for alg in ( 569 | "CSIDH2047M1L226", 570 | "CTIDH2047M1L226", 571 | ) 572 | ), 573 | } 574 | 575 | # Validate choices 576 | def __validate_experiments() -> None: 577 | nikes: list[str] = [alg.upper() for alg in algorithms.nikes] 578 | known_kexes: list[str] = [kem[1] for kem in algorithms.kems] + ["X25519"] + nikes 579 | known_sigs: list[str] = [sig[1] for sig in algorithms.signs] + ["RSA2048"] 580 | for (type, _, kex, leaf, int, root, client_auth, client_ca, _) in ALGORITHMS: 581 | assert ( 582 | kex in known_kexes 583 | ), f"{kex} is not a known KEM (not in {' '.join(known_kexes)})" 584 | assert (leaf in known_kexes and type in ("kemtls", "pdk", "optls")) or ( 585 | leaf in known_sigs and type not in ("kemtls", "pdk", "optls") 586 | ), f"{leaf} is not a known algorithm for hs authentication with {type}" 587 | assert ( 588 | int is None or int in known_sigs 589 | ), f"{int} is not a known signature algorithm" 590 | assert ( 591 | root is None or root in known_sigs 592 | ), f"{root} is not a known signature algorithm" 593 | assert ( 594 | client_auth is None 595 | or (client_auth in known_sigs and type not in ("kemtls", "pdk")) 596 | or (client_auth in known_kexes and type in ("kemtls", "pdk")) 597 | ), f"{client_auth} is not a known signature algorith or KEM for {type}" 598 | assert ( 599 | client_ca is None or client_ca in known_sigs 600 | ), f"{client_ca} is not a known sigalg" 601 | 602 | 603 | __validate_experiments() 604 | 605 | 606 | def only_unique_experiments() -> None: 607 | """get unique experiments: one of each type""" 608 | global ALGORITHMS 609 | seen: set[tuple[ExperimentType, bool, bool]] = set() 610 | 611 | def update(exp: Experiment) -> Experiment: 612 | seen.add((exp.type, exp.client_auth is None, exp.keygen_cache)) 613 | return exp 614 | 615 | ALGORITHMS = { 616 | update(exp) 617 | for exp in ALGORITHMS 618 | if (exp.type, exp.client_auth is None, exp.keygen_cache) not in seen 619 | } 620 | 621 | 622 | TIMER_REGEX = re.compile(r"(?P