From b74ed89494492e13140846d68c0aa4a7c73a90e9 Mon Sep 17 00:00:00 2001 From: Vlad Gheorghiu Date: Sun, 31 Mar 2024 13:22:21 -0400 Subject: [PATCH] V0.10.0 (#80) Fixed shared lib detection code --- .../{python.yml => python_detailed.yml} | 12 +- .github/workflows/python_simplified.yml | 39 +++ CHANGES.md | 66 +++++ CHANGES.txt | 47 ---- LICENSE | 2 +- README.md | 157 ++++++----- RELEASE.md | 34 +-- docker/README.md | 24 +- examples/kem.py | 23 +- examples/rand.py | 22 +- examples/sig.py | 19 +- oqs/oqs.py | 249 +++++++++++++----- oqs/rand.py | 10 +- pyproject.toml | 4 +- setup.py | 7 +- tests/test_kem.py | 4 +- tests/test_sig.py | 6 +- 17 files changed, 477 insertions(+), 248 deletions(-) rename .github/workflows/{python.yml => python_detailed.yml} (91%) create mode 100644 .github/workflows/python_simplified.yml create mode 100644 CHANGES.md delete mode 100644 CHANGES.txt diff --git a/.github/workflows/python.yml b/.github/workflows/python_detailed.yml similarity index 91% rename from .github/workflows/python.yml rename to .github/workflows/python_detailed.yml index fd7d8dc..418faae 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python_detailed.yml @@ -1,12 +1,12 @@ -name: GitHub actions +name: GitHub actions detailed on: push: - branches: [ "**" ] + branches: ["**"] pull_request: - branches: [ "**" ] + branches: ["**"] repository_dispatch: - types: [ "**" ] + types: ["**"] permissions: contents: read @@ -20,7 +20,7 @@ jobs: build: strategy: matrix: - os: [ ubuntu-latest, macos-latest, windows-latest ] + os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: @@ -42,7 +42,7 @@ jobs: git clone --branch main --single-branch --depth 1 https://github.com/open-quantum-safe/liboqs cmake -S liboqs -B liboqs/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_SHARED_LIBS=ON -DOQS_BUILD_ONLY_LIB=ON cmake --build liboqs/build --parallel 4 - sudo cmake --build liboqs/build --target install + sudo cmake --build liboqs/build --target install - name: Run examples POSIX if: matrix.os != 'windows-latest' diff --git a/.github/workflows/python_simplified.yml b/.github/workflows/python_simplified.yml new file mode 100644 index 0000000..387ce6e --- /dev/null +++ b/.github/workflows/python_simplified.yml @@ -0,0 +1,39 @@ +name: GitHub actions simplified + +on: + push: + branches: ["**"] + pull_request: + branches: ["**"] + repository_dispatch: + types: ["**"] + +permissions: + contents: read + +jobs: + build: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + + - name: Run examples + run: | + python -m pip install --upgrade pip + pip install . + python examples/kem.py + python examples/sig.py + python examples/rand.py + + - name: Run unit tests + run: | + nose2 --verbose diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..f03ae45 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,66 @@ +# Version 0.10.0 - March 28, 2024 + +- Replaced CHANGES by + [CHANGES.md](https://github.com/open-quantum-safe/liboqs-python/blob/main/CHANGES.md), + as we now use Markdown format to keep track of changes in new releases +- Removed the NIST PRNG as the latter is no longer exposed by liboqs' public + API +- liboqs is now installed automatically if not detected at runtime + +# Version 0.9.0 - October 30, 2023 + +- This is a maintenance release, minor deprecation fixes +- Python minimum required version is enforced to Python 3.8 in `pyproject.toml` +- To follow Python conventions, renamed in `oqs/oqs.py`: + - `is_KEM_enabled()` -> `is_kem_enabled()` + - `get_enabled_KEM_mechanisms()` -> `get_enabled_kem_mechanisms()` + - `get_supported_KEM_mechanisms()` -> `get_supported_kem_mechanisms()` + +# Version 0.8.0 - July 5, 2023 + +- This is a maintenance release, minor fixes +- Minimalistic Docker support +- Migrated installation method to `pyproject.toml` +- Removed AppVeyor and CircleCI, all continuous integration is now done via + GitHub actions + +# Version 0.7.2 - August 27, 2022 + +- Added library version retrieval functions: + - `oqs_version()` + - `oqs_python_version()` + +# Version 0.7.1 - January 5, 2022 + +- Release numbering updated to match liboqs +- Added macOS support on CircleCI, we now support macOS & Linux (CircleCI) and + Windows (AppVeyor) + +# Version 0.4.0 - November 28, 2020 + +- Renamed 'master' branch to 'main' + +# Version 0.3.0 - June 10, 2020 + +- The liboqs handle has now module-private visibility in `oqs.py` so clients + can not access it directly; can be accessed via the new `oqs.native()` + function +- Closing + #7 [link](https://github.com/open-quantum-safe/liboqs-python/issues/7), all + issues addressed +- Added AppVeyor continuous integration + +# Version 0.2.1 - January 22, 2020 + +- Added a signature example +- Added partial support for RNGs from `` +- Added an RNG example + +# Version 0.2.0 - October 8, 2019 + +- This release updates for compatibility with liboqs 0.2.0, which contains + new/updated algorithms based on NIST Round 2 submissions. + +# Version 0.1.0 - April 23, 2019 + +- Initial release diff --git a/CHANGES.txt b/CHANGES.txt deleted file mode 100644 index ff8b28e..0000000 --- a/CHANGES.txt +++ /dev/null @@ -1,47 +0,0 @@ -Version 0.9.0 - October 30, 2023 - - This is a maintenance release, minor deprecation fixes - - Python minimum required version is enforced to Python 3.8 in pyproject.toml - - To follow Python conventions, renamed in "oqs/oqs.py" - is_KEM_enabled() -> is_kem_enabled() - get_enabled_KEM_mechanisms() -> get_enabled_kem_mechanisms() - get_supported_KEM_mechanisms() -> get_supported_kem_mechanisms() - -Version 0.8.0 - July 5, 2023 - - This is a maintenance release, minor fixes - - Minimalistic Docker support - - Migrated installation method to pyproject.toml - - Removed AppVeyor and CircleCI, all continuous integration is now done - via GitHub actions - -Version 0.7.2 - August 27, 2022 - - Added library version retrieval functions - oqs_version() - oqs_python_version() - -Version 0.7.1 - January 5, 2022 - - Release numbering updated to match liboqs - - Added macOS support on CircleCI, we now support macOS & Linux (CircleCI) - and Windows (AppVeyor) - -Version 0.4.0 - November 28, 2020 - - Renamed 'master' branch to 'main' - -Version 0.3.0 - June 10, 2020 - - The liboqs handle has now module-private visibility in oqs.py so clients - can not access it directly; can be accessed via the new oqs.native() - function - - Closing #7 https://github.com/open-quantum-safe/liboqs-python/issues/7, - all issues addressed - - Added AppVeyor continuous integration - -Version 0.2.1 - January 22, 2020 - - Added a signature example - - Added partial support for RNGs from - - Added an RNG example - -Version 0.2.0 - October 8, 2019 - - This release updates for compatibility with liboqs 0.2.0, which contains - new/updated algorithms based on NIST Round 2 submissions. - -Version 0.1.0 - April 23, 2019 - - Initial release diff --git a/LICENSE b/LICENSE index 25b4126..1fe6778 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018-2023 Open Quantum Safe +Copyright (c) 2018-2024 Open Quantum Safe Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 75feb52..b1fb632 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,23 @@ # liboqs-python: Python 3 bindings for liboqs -[![GitHub actions](https://github.com/open-quantum-safe/liboqs-python/actions/workflows/python.yml/badge.svg)](https://github.com/open-quantum-safe/liboqs-python/actions) +[![GitHub actions](https://github.com/open-quantum-safe/liboqs-python/actions/workflows/python_simplified.yml/badge.svg)](https://github.com/open-quantum-safe/liboqs-python/actions) --- ## About -The **Open Quantum Safe (OQS) project** has the goal of developing and prototyping quantum-resistant cryptography. +The **Open Quantum Safe (OQS) project** has the goal of developing and +prototyping quantum-resistant cryptography. -**liboqs-python** offers a Python 3 wrapper for -the [Open Quantum Safe](https://openquantumsafe.org/) [liboqs](https://github.com/open-quantum-safe/liboqs/) +**liboqs-python** offers a Python 3 wrapper for the +[Open Quantum Safe](https://openquantumsafe.org/) +[liboqs](https://github.com/open-quantum-safe/liboqs/) C library, which is a C library for quantum-resistant cryptographic algorithms. -The wrapper is written in Python 3, hence in the following it is assumed that you have access to a Python 3 interpreter. -liboqs-python has been extensively tested on Linux, macOS and Windows platforms. Continuous integration is provided via -GitHub actions. +The wrapper is written in Python 3, hence in the following it is assumed that +you have access to a Python 3 interpreter. liboqs-python has been extensively +tested on Linux, macOS and Windows platforms. Continuous integration is +provided via GitHub actions. The project contains the following files and directories: @@ -32,8 +35,8 @@ The project contains the following files and directories: - [liboqs](https://github.com/open-quantum-safe/liboqs) - [CMake](https://cmake.org/) - C compliant compiler, - e.g., [gcc](https://gcc.gnu.org/), [clang](https://clang.llvm.org), [MSVC](https://visualstudio.microsoft.com/vs/) - etc. + e.g., [gcc](https://gcc.gnu.org/), [clang](https://clang.llvm.org), + [MSVC](https://visualstudio.microsoft.com/vs/) etc. - [Python 3 interpreter](https://www.python.org/) --- @@ -51,37 +54,53 @@ cmake --build liboqs/build --parallel 8 cmake --build liboqs/build --target install ``` -The last line may require prefixing it by `sudo` on UNIX-like systems. Change `--parallel 8` to match the number of -available cores on your system. +The last line may require prefixing it by `sudo` on UNIX-like systems. Change +`--parallel 8` to match the number of available cores on your system. -On UNIX-like platforms, you may need to set the `LD_LIBRARY_PATH` (`DYLD_LIBRARY_PATH` on macOS) environment variable to -point to the path to liboqs' library directory, e.g., +On UNIX-like platforms, you may need to set the `LD_LIBRARY_PATH` +(`DYLD_LIBRARY_PATH` on macOS) environment variable to point to the path to +liboqs' library directory, e.g., ```shell export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib ``` -On Windows platforms, **you must ensure** that the liboqs shared library `oqs.dll` is visible system-wide, and that the -following environment variable are being set. Use the "Edit the system environment variables" Control Panel tool or -execute in a Command Prompt, e.g., +On Windows platforms, **you must ensure** that you add the +`-DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE` flag to CMake, and that the liboqs +shared library `oqs.dll` is visible system-wide, i.e., set the `PATH` +environment variable accordingly by using the "Edit the system environment +variables" Control Panel tool or executing in a Command Prompt ```shell set PATH=%PATH%;C:\Program Files (x86)\liboqs\bin ``` -You can change liboqs' installation directory by configuring the build to use an alternative path, e.g., `C:\liboqs`, by -replacing the first CMake line above by +You can change liboqs' installation directory by configuring the build to use +an alternative path, e.g., `C:\liboqs`, by passing the +`-DCMAKE_INSTALL_PREFIX=/path/to/liboqs` flag to CMake, e.g., ```shell cmake -S liboqs -B liboqs/build -DCMAKE_INSTALL_PREFIX="C:\liboqs" -DBUILD_SHARED_LIBS=ON ``` +### Let liboqs-python install liboqs automatically + +If you want to avoid installing liboqs manually (as detailed in the step +above), then you can skip the step above and let the liboqs-python wrapper try +to install it for you automatically, as follows. + +When liboqs is not detected at runtime by liboqs-python, it will be downloaded, +configured and installed automatically (as a shared library). This process will +be performed only once, at runtime, i.e., when loading the liboqs-python +wrapper. The liboqs source directory will be automatically removed at the end +of the process. + ### Install and activate a Python virtual environment Execute in a Terminal/Console/Administrator Command Prompt ```shell -python3 -mvenv venv +python3 -mvenv venv . venv/bin/activate python3 -m ensurepip --upgrade ``` @@ -136,21 +155,23 @@ liboqs-python can be imported into Python programs with import oqs ``` -liboqs-python defines two main classes: `KeyEncapsulation` and `Signature`, providing post-quantum key encapsulation and -signture mechanisms, respectively. Each must be instantiated with a string identifying one of mechanisms supported by -liboqs; these can be enumerated using the `get_enabled_KEM_mechanisms()` and `get_enabled_sig_mechanisms()` functions. -The files in `examples/` demonstrate the wrapper's API. Support for alternative RNGs is provided via -the `randombytes_*()` -functions. +liboqs-python defines two main classes: `KeyEncapsulation` and `Signature`, +providing post-quantum key encapsulation and signature mechanisms, +respectively. Each must be instantiated with a string identifying one of +mechanisms supported by liboqs; these can be enumerated using the +`get_enabled_KEM_mechanisms()` and `get_enabled_sig_mechanisms()` functions. +The files in `examples/` demonstrate the wrapper's API. Support for alternative +RNGs is provided via the `randombytes_*()` functions. -The liboqs-python project should be in the `PYTHONPATH`. To ensure this on UNIX-like systems, execute +The liboqs-python project should be in the `PYTHONPATH`. To ensure this on +UNIX-like systems, execute ```shell export PYTHONPATH=$PYTHONPATH:/path/to/liboqs-python ``` -or, on Windows platforms, use the "Edit the system environment variables" Control Panel tool or execute in a Command -Prompt +or, on Windows platforms, use the "Edit the system environment variables" +Control Panel tool or execute in a Command Prompt ```shell set PYTHONPATH=%PYTHONPATH%;C:\path\to\liboqs-python @@ -160,8 +181,8 @@ set PYTHONPATH=%PYTHONPATH%;C:\path\to\liboqs-python ## Docker -A self-explanatory minimalistic Docker file is provided -in [`Dockerfile`](https://github.com/open-quantum-safe/liboqs-python/tree/main/Dockerfile). +A self-explanatory minimalistic Docker file is provided in +[`Dockerfile`](https://github.com/open-quantum-safe/liboqs-python/tree/main/Dockerfile). Build the image by executing @@ -181,58 +202,68 @@ Or, run the unit tests with docker run -it oqs-python sh -c ". venv/bin/activate && nose2 --verbose liboqs-python" ``` -In case you want to use the Docker container as a development environment, mount your current project in the Docker -container with +In case you want to use the Docker container as a development environment, +mount your current project in the Docker container with ```shell -docker run --rm -it --workdir=/app -v ${PWD}:/app oqs-python /bin/bash +docker run --rm -it --workdir=/app -v ${PWD}:/app oqs-python /bin/bash ``` -A more comprehensive Docker example is provided in the -directory [`docker`](https://github.com/open-quantum-safe/liboqs-python/tree/main/docker). +A more comprehensive Docker example is provided in the directory +[`docker`](https://github.com/open-quantum-safe/liboqs-python/tree/main/docker). --- ## Limitations and security -liboqs is designed for prototyping and evaluating quantum-resistant cryptography. Security of proposed quantum-resistant -algorithms may rapidly change as research advances, and may ultimately be completely insecure against either classical -or quantum computers. - -We believe that the NIST Post-Quantum Cryptography standardization project is currently the best avenue to identifying -potentially quantum-resistant algorithms. liboqs does not intend to "pick winners", and we strongly recommend that -applications and protocols rely on the outcomes of the NIST standardization project when deploying post-quantum -cryptography. - -We acknowledge that some parties may want to begin deploying post-quantum cryptography prior to the conclusion of the -NIST standardization project. We strongly recommend that any attempts to do make use of so-called -**hybrid cryptography**, in which post-quantum public-key algorithms are used alongside traditional public key -algorithms (like RSA or elliptic curves) so that the solution is at least no less secure than existing traditional +liboqs is designed for prototyping and evaluating quantum-resistant +cryptography. Security of proposed quantum-resistant algorithms may rapidly +change as research advances, and may ultimately be completely insecure against +either classical or quantum computers. + +We believe that the NIST Post-Quantum Cryptography standardization project is +currently the best avenue to identifying potentially quantum-resistant +algorithms. liboqs does not intend to "pick winners", and we strongly recommend +that applications and protocols rely on the outcomes of the NIST +standardization project when deploying post-quantum cryptography. + +We acknowledge that some parties may want to begin deploying post-quantum +cryptography prior to the conclusion of the NIST standardization project. We +strongly recommend that any attempts to do make use of so-called +**hybrid cryptography**, in which post-quantum public-key algorithms are used +alongside traditional public key algorithms (like RSA or elliptic curves) so +that the solution is at least no less secure than existing traditional cryptography. -Just like liboqs, liboqs-python is provided "as is", without warranty of any kind. -See [LICENSE](https://github.com/open-quantum-safe/liboqs-python/blob/main/LICENSE) for the full disclaimer. +Just like liboqs, liboqs-python is provided "as is", without warranty of any +kind. See +[LICENSE](https://github.com/open-quantum-safe/liboqs-python/blob/main/LICENSE) +for the full disclaimer. --- ## License -liboqs-python is licensed under the MIT License; -see [LICENSE](https://github.com/open-quantum-safe/liboqs-python/blob/main/LICENSE) for details. +liboqs-python is licensed under the MIT License; see +[LICENSE](https://github.com/open-quantum-safe/liboqs-python/blob/main/LICENSE) +for details. --- ## Team -The Open Quantum Safe project is led by [Douglas Stebila](https://www.douglas.stebila.ca/research/) -and [Michele Mosca](http://faculty.iqc.uwaterloo.ca/mmosca/) at the University of Waterloo. +The Open Quantum Safe project is led by +[Douglas Stebila](https://www.douglas.stebila.ca/research/) and +[Michele Mosca](http://faculty.iqc.uwaterloo.ca/mmosca/) at the University of +Waterloo. ### Contributors Contributors to the liboqs-python wrapper include: - Ben Davies (University of Waterloo) -- Vlad Gheorghiu (softwareQ Inc., University of Waterloo) +- Vlad Gheorghiu ([softwareQ Inc.](https://www.softwareq.ca) and the University + of Waterloo) - Christian Paquin (Microsoft Research) - Douglas Stebila (University of Waterloo) @@ -240,12 +271,14 @@ Contributors to the liboqs-python wrapper include: ## Support -Financial support for the development of Open Quantum Safe has been provided by Amazon Web Services and the Canadian -Centre for Cyber Security. +Financial support for the development of Open Quantum Safe has been provided by +Amazon Web Services and the Canadian Centre for Cyber Security. -We'd like to make a special acknowledgement to the companies who have dedicated programmer time to contribute source -code to OQS, including Amazon Web Services, evolutionQ, softwareQ, and Microsoft Research. +We'd like to make a special acknowledgement to the companies who have dedicated +programmer time to contribute source code to OQS, including Amazon Web +Services, evolutionQ, softwareQ, and Microsoft Research. -Research projects which developed specific components of OQS have been supported by various research grants, including -funding from the Natural Sciences and Engineering Research Council of Canada (NSERC); see the source papers for funding -acknowledgments. +Research projects which developed specific components of OQS have been +supported by various research grants, including funding from the Natural +Sciences and Engineering Research Council of Canada (NSERC); see the source +papers for funding acknowledgments. diff --git a/RELEASE.md b/RELEASE.md index 6aa19e5..269f130 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,32 +1,36 @@ -# liboqs-python version 0.9.0 +# liboqs-python version 0.10.0 --- ## About -The **Open Quantum Safe (OQS) project** has the goal of developing and prototyping quantum-resistant cryptography. More -information on OQS can be found on our website https://openquantumsafe.org/ and on GitHub -at https://github.com/open-quantum-safe/. +The **Open Quantum Safe (OQS) project** has the goal of developing and +prototyping quantum-resistant cryptography. More information on OQS can be +found on our website https://openquantumsafe.org/ and on GitHub at +https://github.com/open-quantum-safe/. -**liboqs** is an open source C library for quantum-resistant cryptographic algorithms. See more about liboqs -at [https://github.com/open-quantum-safe/liboqs/](https://github.com/open-quantum-safe/liboqs/), including a list of -supported algorithms. +**liboqs** is an open source C library for quantum-resistant cryptographic +algorithms. See more about liboqs at +[https://github.com/open-quantum-safe/liboqs/](https://github.com/open-quantum-safe/liboqs/), +including a list of supported algorithms. -**liboqs-python** is an open source Python 3 wrapper for the liboqs C library for quantum-resistant cryptographic -algorithms. Details about liboqs-python can be found -in [README.md](https://github.com/open-quantum-safe/liboqs-python/blob/main/README.md). See in particular limitations on -intended use. +**liboqs-python** is an open source Python 3 wrapper for the liboqs C library +for quantum-resistant cryptographic algorithms. Details about liboqs-python can +be found in +[README.md](https://github.com/open-quantum-safe/liboqs-python/blob/main/README.md). +See in particular limitations on intended use. --- ## Release notes -This release of liboqs-python was released on October 30, 2023. Its release page on GitHub -is https://github.com/open-quantum-safe/liboqs-python/releases/tag/0.9.0. +This release of liboqs-python was released on October 30, 2023. Its release +page on GitHub is +https://github.com/open-quantum-safe/liboqs-python/releases/tag/0.9.0. --- ## What's New -This is the 9th release of liboqs-python. For a list of changes -see [CHANGES.txt](https://github.com/open-quantum-safe/liboqs-python/blob/main/CHANGES.txt). +This is the 10th release of liboqs-python. For a list of changes see +[CHANGES.md](https://github.com/open-quantum-safe/liboqs-python/blob/main/CHANGES.md). diff --git a/docker/README.md b/docker/README.md index 55da174..9109752 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,19 +1,23 @@ # OQS-python -This docker image contains python3 with library support for quantum-safe crypto (QSC) operations. +This docker image contains python3 with library support for quantum-safe crypto +(QSC) operations. -To this end, it contains [liboqs](https://github.com/open-quantum-safe/liboqs) as well -as [OQS-OpenSSL](https://github.com/open-quantum-safe/openssl) from the [OpenQuantumSafe](https://openquantumsafe.org) -project all wrapped up in Python APIs using [liboqs-python](https://github.com/open-quantum-safe/liboqs-python). +To this end, it contains [liboqs](https://github.com/open-quantum-safe/liboqs) +as well as [OQS-OpenSSL](https://github.com/open-quantum-safe/openssl) from the +[OpenQuantumSafe](https://openquantumsafe.org) project all wrapped up in Python +APIs using [liboqs-python](https://github.com/open-quantum-safe/liboqs-python). ## Quick start -- Executing `docker run -it openquantumsafe/python` tests all QSC algorithms against the interop server - at https://test.openquantumsafe.org. -- Executing `docker run -it openquantumsafe/python sh` provides a shell environment where liboqs and QSC-enabled SSL/TLS - is available for use. See the included file `minitest.py` for sample code exercizing this functionality. +- Executing `docker run -it openquantumsafe/python` tests all QSC algorithms + against the interop server at https://test.openquantumsafe.org. +- Executing `docker run -it openquantumsafe/python sh` provides a shell + environment where liboqs and QSC-enabled SSL/TLS is available for use. See + the included file `minitest.py` for sample code exercizing this + functionality. ## Further examples -More samples are available -at [liboqs-python examples](https://github.com/open-quantum-safe/liboqs-python/tree/main/examples). +More samples are available at +[liboqs-python examples](https://github.com/open-quantum-safe/liboqs-python/tree/main/examples). diff --git a/examples/kem.py b/examples/kem.py index defa6b8..a9deb2d 100644 --- a/examples/kem.py +++ b/examples/kem.py @@ -1,37 +1,36 @@ -# key encapsulation Python example +# Key encapsulation Python example import oqs from pprint import pprint -####################################################################### -# KEM example -####################################################################### - print("liboqs version:", oqs.oqs_version()) print("liboqs-python version:", oqs.oqs_python_version()) print("Enabled KEM mechanisms:") kems = oqs.get_enabled_kem_mechanisms() pprint(kems, compact=True) -# create client and server with sample KEM mechanisms +# Create client and server with sample KEM mechanisms kemalg = "Kyber512" with oqs.KeyEncapsulation(kemalg) as client: with oqs.KeyEncapsulation(kemalg) as server: print("\nKey encapsulation details:") pprint(client.details) - # client generates its keypair + # Client generates its keypair public_key_client = client.generate_keypair() - # optionally, the secret key can be obtained by calling export_secret_key() + # Optionally, the secret key can be obtained by calling export_secret_key() # and the client can later be re-instantiated with the key pair: # secret_key_client = client.export_secret_key() - # store key pair, wait... (session resumption): + + # Store key pair, wait... (session resumption): # client = oqs.KeyEncapsulation(kemalg, secret_key_client) - # the server encapsulates its secret using the client's public key + # The server encapsulates its secret using the client's public key ciphertext, shared_secret_server = server.encap_secret(public_key_client) - # the client decapsulates the server's ciphertext to obtain the shared secret + # The client decapsulates the server's ciphertext to obtain the shared secret shared_secret_client = client.decap_secret(ciphertext) - print("\nShared secretes coincide:", shared_secret_client == shared_secret_server) + print( + "\nShared secretes coincide:", shared_secret_client == shared_secret_server + ) diff --git a/examples/rand.py b/examples/rand.py index 82113d4..75e5c89 100644 --- a/examples/rand.py +++ b/examples/rand.py @@ -1,20 +1,22 @@ -# various RNGs Python example +# Various RNGs Python example import platform # to learn the OS we're on import oqs.rand as oqsrand # must be explicitly imported from oqs import oqs_version, oqs_python_version -####################################################################### -# randomness example -####################################################################### - print("liboqs version:", oqs_version()) print("liboqs-python version:", oqs_python_version()) -# we do not yet support OpenSSL under Windows +oqsrand.randombytes_switch_algorithm("system") +print( + "{:17s}".format("System (default):"), + " ".join("{:02X}".format(x) for x in oqsrand.randombytes(32)), +) + +# We do not yet support OpenSSL under Windows if platform.system() != "Windows": oqsrand.randombytes_switch_algorithm("OpenSSL") - print('{:17s}'.format("OpenSSL:"), ' '.join('{:02X}'.format(x) for x in oqsrand.randombytes(32))) - -oqsrand.randombytes_switch_algorithm("system") -print('{:17s}'.format("System (default):"), ' '.join('{:02X}'.format(x) for x in oqsrand.randombytes(32))) + print( + "{:17s}".format("OpenSSL:"), + " ".join("{:02X}".format(x) for x in oqsrand.randombytes(32)), + ) diff --git a/examples/sig.py b/examples/sig.py index f800245..ea07508 100644 --- a/examples/sig.py +++ b/examples/sig.py @@ -1,12 +1,8 @@ -# signature Python example +# Signature Python example import oqs from pprint import pprint -####################################################################### -# signature example -####################################################################### - print("liboqs version:", oqs.oqs_version()) print("liboqs-python version:", oqs.oqs_python_version()) print("Enabled signature mechanisms:") @@ -15,25 +11,26 @@ message = "This is the message to sign".encode() -# create signer and verifier with sample signature mechanisms +# Create signer and verifier with sample signature mechanisms sigalg = "Dilithium2" with oqs.Signature(sigalg) as signer: with oqs.Signature(sigalg) as verifier: print("\nSignature details:") pprint(signer.details) - # signer generates its keypair + # Signer generates its keypair signer_public_key = signer.generate_keypair() - # optionally, the secret key can be obtained by calling export_secret_key() + # Optionally, the secret key can be obtained by calling export_secret_key() # and the signer can later be re-instantiated with the key pair: # secret_key = signer.export_secret_key() - # store key pair, wait... (session resumption): + + # Store key pair, wait... (session resumption): # signer = oqs.Signature(sigalg, secret_key) - # signer signs the message + # Signer signs the message signature = signer.sign(message) - # verifier verifies the signature + # Verifier verifies the signature is_valid = verifier.verify(message, signature, signer_public_key) print("\nValid signature?", is_valid) diff --git a/oqs/oqs.py b/oqs/oqs.py index 6d10b9d..4f67184 100644 --- a/oqs/oqs.py +++ b/oqs/oqs.py @@ -1,5 +1,5 @@ """ -Open Quantum Safe (OQS) Python Wrapper for liboqs +Open Quantum Safe (OQS) Python wrapper for liboqs The liboqs project provides post-quantum public key cryptography algorithms: https://github.com/open-quantum-safe/liboqs @@ -10,40 +10,137 @@ import ctypes as ct # to call native import ctypes.util as ctu import importlib.metadata # to determine module version at runtime +import os # to run OS commands (install liboqs on demand if not found) import platform # to learn the OS we're on import sys +import tempfile # to install liboqs on demand +import time import warnings -# expected return value from native OQS functions -OQS_SUCCESS = 0 -OQS_ERROR = -1 + +def oqs_python_version(): + """liboqs-python version string.""" + try: + result = importlib.metadata.version("liboqs-python") + except importlib.metadata.PackageNotFoundError: + warnings.warn("Please install liboqs-python using pip install") + return None + return result -def _load_shared_obj(name): - """Attempts to load native OQS library.""" - paths = [] +# liboqs-python tries to automatically install and load this liboqs version in +# case no other version is found +REQUIRE_OQS_VERSION = oqs_python_version() + + +def _countdown(seconds): + while seconds > 0: + print(seconds, end=" ") + sys.stdout.flush() + seconds -= 1 + time.sleep(1) + print() + - # search typical locations - paths += [ctu.find_library(name)] - if not paths: - paths += [ctu.find_library("lib" + name)] +def _load_shared_obj(name, additional_searching_paths=None): + """Attempts to load shared library.""" + paths = [] dll = ct.windll if platform.system() == "Windows" else ct.cdll + # Search additional path, if any + if additional_searching_paths: + for path in additional_searching_paths: + if platform.system() == "Darwin": + paths.append( + os.path.abspath(path) + os.path.sep + "lib" + name + ".dylib" + ) + elif platform.system() == "Windows": + paths.append(os.path.abspath(path) + os.path.sep + name + ".dll") + # Does not work + # os.environ["PATH"] += os.path.abspath(path) + else: # Linux/FreeBSD/UNIX + paths.append(os.path.abspath(path) + os.path.sep + "lib" + name + ".so") + # https://stackoverflow.com/questions/856116/changing-ld-library-path-at-runtime-for-ctypes + # os.environ["LD_LIBRARY_PATH"] += os.path.abspath(path) + + # Search typical locations + try: + paths.insert(0, ctu.find_library(name)) + except FileNotFoundError: + pass + try: + paths.insert(0, ctu.find_library("lib" + name)) + except FileNotFoundError: + pass + for path in paths: if path: - lib = dll.LoadLibrary(path) - return lib + try: + lib = dll.LoadLibrary(path) + return lib + except OSError: + pass raise RuntimeError("No " + name + " shared libraries found") -try: - _liboqs = _load_shared_obj("oqs") - assert _liboqs -except OSError as err: - sys.exit("Could not load liboqs shared library") -except RuntimeError as err: - sys.exit("No liboqs shared libraries found") +def _install_liboqs(target_directory, oqs_version=None): + """Install liboqs version oqs_version (if None, installs latest at HEAD) in the target_directory.""" + with tempfile.TemporaryDirectory() as tmpdirname: + oqs_install_str = ( + "cd " + + tmpdirname + + " && git clone https://github.com/open-quantum-safe/liboqs" + ) + if oqs_version: + oqs_install_str += " --branch " + oqs_version + oqs_install_str += ( + " --depth 1 && cmake -S liboqs -B liboqs/build -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=" + + target_directory + ) + if platform.system() == "Windows": + oqs_install_str += " -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE" + oqs_install_str += " && cmake --build liboqs/build --parallel 4 && cmake --build liboqs/build --target install" + print("liboqs not found, installing it in " + target_directory) + _countdown(5) + os.system(oqs_install_str) + print("Done installing liboqs") + + +def _load_liboqs(): + home_dir = os.path.expanduser("~") + oqs_install_dir = os.path.abspath(home_dir + os.path.sep + "_oqs") # $HOME/_oqs + oqs_lib_dir = ( + os.path.abspath(oqs_install_dir + os.path.sep + "bin") # $HOME/_oqs/bin + if platform.system() == "Windows" + else os.path.abspath(oqs_install_dir + os.path.sep + "lib") # $HOME/_oqs/lib + ) + try: + _liboqs = _load_shared_obj(name="oqs", additional_searching_paths=[oqs_lib_dir]) + assert _liboqs + except RuntimeError: + # We don't have liboqs, so we try to install it automatically + _install_liboqs( + target_directory=oqs_install_dir, oqs_version=REQUIRE_OQS_VERSION + ) + # Try loading it again + try: + _liboqs = _load_shared_obj( + name="oqs", additional_searching_paths=[oqs_lib_dir] + ) + assert _liboqs + except RuntimeError: + sys.exit("Could not load liboqs shared library") + + return _liboqs + + +_liboqs = _load_liboqs() + + +# Expected return value from native OQS functions +OQS_SUCCESS = 0 +OQS_ERROR = -1 def native(): @@ -58,22 +155,16 @@ def native(): def oqs_version(): """liboqs version string.""" native().OQS_version.restype = ct.c_char_p - return ct.c_char_p(native().OQS_version()).value.decode('UTF-8') + return ct.c_char_p(native().OQS_version()).value.decode("UTF-8") -def oqs_python_version(): - """liboqs-python version string.""" - try: - result = importlib.metadata.version("liboqs-python") - except importlib.metadata.PackageNotFoundError: - warnings.warn("Please install liboqs-python using pip install") - return None - return result - - -# warn the use if the liboqs version differs from liboqs-python version +# Warn the user if the liboqs version differs from liboqs-python version if oqs_version() != oqs_python_version(): - warnings.warn("liboqs version {} differs from liboqs-python version {}".format(oqs_version(), oqs_python_version())) + warnings.warn( + "liboqs version {} differs from liboqs-python version {}".format( + oqs_version(), oqs_python_version() + ) + ) class MechanismNotSupportedError(Exception): @@ -123,7 +214,7 @@ class KeyEncapsulation(ct.Structure): ("length_shared_secret", ct.c_size_t), ("keypair_cb", ct.c_void_p), ("encaps_cb", ct.c_void_p), - ("decaps_cb", ct.c_void_p) + ("decaps_cb", ct.c_void_p), ] def __init__(self, alg_name, secret_key=None): @@ -153,10 +244,13 @@ def __init__(self, alg_name, secret_key=None): "length_public_key": int(self._kem.contents.length_public_key), "length_secret_key": int(self._kem.contents.length_secret_key), "length_ciphertext": int(self._kem.contents.length_ciphertext), - "length_shared_secret": int(self._kem.contents.length_shared_secret)} + "length_shared_secret": int(self._kem.contents.length_shared_secret), + } if secret_key: - self.secret_key = ct.create_string_buffer(secret_key, self._kem.contents.length_secret_key) + self.secret_key = ct.create_string_buffer( + secret_key, self._kem.contents.length_secret_key + ) def __enter__(self): return self @@ -172,7 +266,9 @@ def generate_keypair(self): """ public_key = ct.create_string_buffer(self._kem.contents.length_public_key) self.secret_key = ct.create_string_buffer(self._kem.contents.length_secret_key) - rv = native().OQS_KEM_keypair(self._kem, ct.byref(public_key), ct.byref(self.secret_key)) + rv = native().OQS_KEM_keypair( + self._kem, ct.byref(public_key), ct.byref(self.secret_key) + ) return bytes(public_key) if rv == OQS_SUCCESS else 0 def export_secret_key(self): @@ -185,10 +281,14 @@ def encap_secret(self, public_key): :param public_key: the peer's public key. """ - my_public_key = ct.create_string_buffer(public_key, self._kem.contents.length_public_key) + my_public_key = ct.create_string_buffer( + public_key, self._kem.contents.length_public_key + ) ciphertext = ct.create_string_buffer(self._kem.contents.length_ciphertext) shared_secret = ct.create_string_buffer(self._kem.contents.length_shared_secret) - rv = native().OQS_KEM_encaps(self._kem, ct.byref(ciphertext), ct.byref(shared_secret), my_public_key) + rv = native().OQS_KEM_encaps( + self._kem, ct.byref(ciphertext), ct.byref(shared_secret), my_public_key + ) return bytes(ciphertext), bytes(shared_secret) if rv == OQS_SUCCESS else 0 def decap_secret(self, ciphertext): @@ -197,15 +297,21 @@ def decap_secret(self, ciphertext): :param ciphertext: the ciphertext received from the peer. """ - my_ciphertext = ct.create_string_buffer(ciphertext, self._kem.contents.length_ciphertext) + my_ciphertext = ct.create_string_buffer( + ciphertext, self._kem.contents.length_ciphertext + ) shared_secret = ct.create_string_buffer(self._kem.contents.length_shared_secret) - rv = native().OQS_KEM_decaps(self._kem, ct.byref(shared_secret), my_ciphertext, self.secret_key) + rv = native().OQS_KEM_decaps( + self._kem, ct.byref(shared_secret), my_ciphertext, self.secret_key + ) return bytes(shared_secret) if rv == OQS_SUCCESS else 0 def free(self): """Releases the native resources.""" if hasattr(self, "secret_key"): - native().OQS_MEM_cleanse(ct.byref(self.secret_key), self._kem.contents.length_secret_key) + native().OQS_MEM_cleanse( + ct.byref(self.secret_key), self._kem.contents.length_secret_key + ) native().OQS_KEM_free(self._kem) def __repr__(self): @@ -225,7 +331,9 @@ def is_kem_enabled(alg_name): return native().OQS_KEM_alg_is_enabled(ct.create_string_buffer(alg_name.encode())) -_KEM_alg_ids = [native().OQS_KEM_alg_identifier(i) for i in range(native().OQS_KEM_alg_count())] +_KEM_alg_ids = [ + native().OQS_KEM_alg_identifier(i) for i in range(native().OQS_KEM_alg_count()) +] _supported_KEMs = [i.decode() for i in _KEM_alg_ids] _enabled_KEMs = [i for i in _supported_KEMs if is_kem_enabled(i)] @@ -264,7 +372,7 @@ class Signature(ct.Structure): ("length_signature", ct.c_size_t), ("keypair_cb", ct.c_void_p), ("sign_cb", ct.c_void_p), - ("verify_cb", ct.c_void_p) + ("verify_cb", ct.c_void_p), ] def __init__(self, alg_name, secret_key=None): @@ -291,10 +399,13 @@ def __init__(self, alg_name, secret_key=None): "is_euf_cma": bool(self._sig.contents.euf_cma), "length_public_key": int(self._sig.contents.length_public_key), "length_secret_key": int(self._sig.contents.length_secret_key), - "length_signature": int(self._sig.contents.length_signature)} + "length_signature": int(self._sig.contents.length_signature), + } if secret_key: - self.secret_key = ct.create_string_buffer(secret_key, self._sig.contents.length_secret_key) + self.secret_key = ct.create_string_buffer( + secret_key, self._sig.contents.length_secret_key + ) def __enter__(self): return self @@ -310,7 +421,9 @@ def generate_keypair(self): """ public_key = ct.create_string_buffer(self._sig.contents.length_public_key) self.secret_key = ct.create_string_buffer(self._sig.contents.length_secret_key) - rv = native().OQS_SIG_keypair(self._sig, ct.byref(public_key), ct.byref(self.secret_key)) + rv = native().OQS_SIG_keypair( + self._sig, ct.byref(public_key), ct.byref(self.secret_key) + ) return bytes(public_key) if rv == OQS_SUCCESS else 0 def export_secret_key(self): @@ -323,16 +436,23 @@ def sign(self, message): :param message: the message to sign. """ - # provide length to avoid extra null char + # Provide length to avoid extra null char my_message = ct.create_string_buffer(message, len(message)) message_len = ct.c_int(len(my_message)) signature = ct.create_string_buffer(self._sig.contents.length_signature) - sig_len = ct.c_int(self._sig.contents.length_signature) # initialize to maximum signature size - rv = native().OQS_SIG_sign(self._sig, ct.byref(signature), - ct.byref(sig_len), my_message, - message_len, self.secret_key) - - return bytes(signature[:sig_len.value]) if rv == OQS_SUCCESS else 0 + sig_len = ct.c_int( + self._sig.contents.length_signature + ) # initialize to maximum signature size + rv = native().OQS_SIG_sign( + self._sig, + ct.byref(signature), + ct.byref(sig_len), + my_message, + message_len, + self.secret_key, + ) + + return bytes(signature[: sig_len.value]) if rv == OQS_SUCCESS else 0 def verify(self, message, signature, public_key): """ @@ -342,22 +462,27 @@ def verify(self, message, signature, public_key): :param signature: the signature on the message. :param public_key: the signer's public key. """ - # provide length to avoid extra null char + # Provide length to avoid extra null char my_message = ct.create_string_buffer(message, len(message)) message_len = ct.c_int(len(my_message)) - # provide length to avoid extra null char in sig + # Provide length to avoid extra null char in sig my_signature = ct.create_string_buffer(signature, len(signature)) sig_len = ct.c_int(len(my_signature)) - my_public_key = ct.create_string_buffer(public_key, self._sig.contents.length_public_key) - rv = native().OQS_SIG_verify(self._sig, my_message, message_len, - my_signature, sig_len, my_public_key) + my_public_key = ct.create_string_buffer( + public_key, self._sig.contents.length_public_key + ) + rv = native().OQS_SIG_verify( + self._sig, my_message, message_len, my_signature, sig_len, my_public_key + ) return True if rv == OQS_SUCCESS else False def free(self): """Releases the native resources.""" if hasattr(self, "secret_key"): - native().OQS_MEM_cleanse(ct.byref(self.secret_key), self._sig.contents.length_secret_key) + native().OQS_MEM_cleanse( + ct.byref(self.secret_key), self._sig.contents.length_secret_key + ) native().OQS_SIG_free(self._sig) def __repr__(self): @@ -377,7 +502,9 @@ def is_sig_enabled(alg_name): return native().OQS_SIG_alg_is_enabled(ct.create_string_buffer(alg_name.encode())) -_sig_alg_ids = [native().OQS_SIG_alg_identifier(i) for i in range(native().OQS_SIG_alg_count())] +_sig_alg_ids = [ + native().OQS_SIG_alg_identifier(i) for i in range(native().OQS_SIG_alg_count()) +] _supported_sigs = [i.decode() for i in _sig_alg_ids] _enabled_sigs = [i for i in _supported_sigs if is_sig_enabled(i)] diff --git a/oqs/rand.py b/oqs/rand.py index ffe3fc5..5e7fd77 100644 --- a/oqs/rand.py +++ b/oqs/rand.py @@ -29,6 +29,10 @@ def randombytes_switch_algorithm(alg_name): :param alg_name: algorithm name, possible values are "system" and "OpenSSL". """ - if oqs.native().OQS_randombytes_switch_algorithm( - oqs.ct.create_string_buffer(alg_name.encode())) != oqs.OQS_SUCCESS: - raise RuntimeError('Can not switch algorithm') + if ( + oqs.native().OQS_randombytes_switch_algorithm( + oqs.ct.create_string_buffer(alg_name.encode()) + ) + != oqs.OQS_SUCCESS + ): + raise RuntimeError("Can not switch algorithm") diff --git a/pyproject.toml b/pyproject.toml index 0ec6314..fd558c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,8 +8,8 @@ build-backend = "setuptools.build_meta" [project] name = "liboqs-python" requires-python = ">=3.8" -version = "0.9.0" -description = "Python wrapper for liboqs, providing post-quantum public key cryptography algorithms" +version = "0.10.0" +description = "Python bindings for liboqs, providing post-quantum public key cryptography algorithms" authors = [ { name = "Open Quantum Safe project", email = "contact@openquantumsafe.org" }, ] diff --git a/setup.py b/setup.py index e6f792d..ab322e4 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ -from setuptools import find_packages -from distutils.core import setup +from setuptools import find_packages, setup setup( - packages=find_packages(exclude=('tests', 'docs', 'examples')), + packages=find_packages( + exclude=["tests", "docs", "examples"], + ), ) diff --git a/tests/test_kem.py b/tests/test_kem.py index b2b2fac..cd13640 100644 --- a/tests/test_kem.py +++ b/tests/test_kem.py @@ -54,7 +54,7 @@ def test_not_enabled(): # TODO: test broken as the compiled lib determines which algorithms are supported and enabled for alg_name in oqs.get_supported_kem_mechanisms(): if alg_name not in oqs.get_enabled_kem_mechanisms(): - # found a non-enabled but supported alg + # Found a non-enabled but supported alg try: with oqs.KeyEncapsulation(alg_name) as kem: raise AssertionError("oqs.MechanismNotEnabledError was not raised.") @@ -64,7 +64,7 @@ def test_not_enabled(): raise AssertionError("An unexpected exception was raised. " + ex) -if __name__ == '__main__': +if __name__ == "__main__": try: import nose2 diff --git a/tests/test_sig.py b/tests/test_sig.py index b03003d..5053df4 100644 --- a/tests/test_sig.py +++ b/tests/test_sig.py @@ -2,7 +2,7 @@ import platform # to learn the OS we're on import random -# sigs for which unit testing is disabled +# Sigs for which unit testing is disabled disabled_sig_patterns = [] if platform.system() == "Windows": @@ -86,7 +86,7 @@ def test_not_enabled(): # TODO: test broken as the compiled lib determines which algorithms are supported and enabled for alg_name in oqs.get_supported_sig_mechanisms(): if alg_name not in oqs.get_enabled_sig_mechanisms(): - # found a non-enabled but supported alg + # Found a non-enabled but supported alg try: with oqs.Signature(alg_name) as sig: raise AssertionError("oqs.MechanismNotEnabledError was not raised.") @@ -96,7 +96,7 @@ def test_not_enabled(): raise AssertionError("An unexpected exception was raised. " + ex) -if __name__ == '__main__': +if __name__ == "__main__": try: import nose2