diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index e00b852f0..405463d1d 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -6,7 +6,6 @@ permissions: on: [workflow_call, workflow_dispatch] jobs: - workflowcheck: name: Check validity of GitHub workflows runs-on: ubuntu-latest @@ -19,7 +18,7 @@ jobs: stylecheck: name: Check code formatting - needs: [ workflowcheck ] + needs: [workflowcheck] runs-on: ubuntu-latest container: openquantumsafe/ci-ubuntu-latest:latest steps: @@ -34,7 +33,7 @@ jobs: upstreamcheck: name: Check upstream code is properly integrated - needs: [ workflowcheck ] + needs: [workflowcheck] runs-on: ubuntu-latest container: openquantumsafe/ci-ubuntu-latest:latest steps: @@ -47,19 +46,19 @@ jobs: git config --global --add safe.directory "$PWD" && \ echo "LIBOQS_DIR=$PWD" >> "$GITHUB_ENV" - name: Verify copy_from_upstream state after copy - working-directory: 'scripts/copy_from_upstream' + working-directory: "scripts/copy_from_upstream" run: | python3 copy_from_upstream.py copy && \ ! git status | grep -i modified - name: Verify copy_from_upstream state after libjade - working-directory: 'scripts/copy_from_upstream' + working-directory: "scripts/copy_from_upstream" run: | python3 copy_from_upstream.py libjade && \ ! git status | grep -i modified buildcheck: name: Check that code passes a basic build - needs: [ workflowcheck, stylecheck, upstreamcheck ] + needs: [workflowcheck, stylecheck, upstreamcheck] runs-on: ubuntu-latest container: openquantumsafe/ci-ubuntu-latest:latest env: @@ -86,10 +85,39 @@ jobs: run: ninja gen_docs working-directory: build - + cppcheck: + name: Check C++ linking with example program + runs-on: ubuntu-latest + container: openquantumsafe/ci-ubuntu-latest:latest + env: + SIG_NAME: dilithium_2 + steps: + - name: Checkout code + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4 + - name: Configure + run: | + mkdir build && \ + cd build && \ + cmake -GNinja -DOQS_STRICT_WARNINGS=ON \ + -GNinja \ + -DOQS_MINIMAL_BUILD="SIG_$SIG_NAME" \ + --warn-uninitialized .. > config.log 2>&1 && \ + cat config.log && \ + cmake -LA -N .. && \ + ! (grep -i "uninitialized variable" config.log) + - name: Build liboqs + run: ninja + working-directory: build + - name: Link with C++ program + run: | + g++ ../cpp/sig_linking_test.cpp -g \ + -I./include -L./lib -loqs -lcrypto -std=c++11 -o example_sig && \ + ./example_sig + working-directory: build + fuzzbuildcheck: name: Check that code passes a basic fuzzing build - needs: [ workflowcheck, stylecheck, upstreamcheck ] + needs: [workflowcheck, stylecheck, upstreamcheck] runs-on: ubuntu-latest container: openquantumsafe/ci-ubuntu-latest:latest env: diff --git a/cpp/sig_linking_test.cpp b/cpp/sig_linking_test.cpp new file mode 100644 index 000000000..e29474611 --- /dev/null +++ b/cpp/sig_linking_test.cpp @@ -0,0 +1,182 @@ +/* + * example_sig.cpp + * + * Minimal C++ example of using a post-quantum signature implemented in liboqs. + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include +#include + +#include + +constexpr size_t MESSAGE_LEN = 50; + +/* Cleaning up memory etc */ +void cleanup_stack(uint8_t *secret_key, size_t secret_key_len); + +struct OQSSecureDeleter { + size_t length; + + explicit OQSSecureDeleter(size_t len) : length(len) {} + + void operator()(uint8_t* ptr) const { + if (ptr) { + OQS_MEM_secure_free(ptr, length); + } + } +}; + +struct OQSInsecureDeleter { + void operator()(uint8_t* ptr) { + if (ptr) { + OQS_MEM_insecure_free(ptr); + } + } +}; + +struct OQSSigDeleter { + void operator()(OQS_SIG* sig) { + if (sig) { + OQS_SIG_free(sig); + } + } +}; + + +/* This function gives an example of the signing operations + * using only compile-time macros and allocating variables + * statically on the stack, calling a specific algorithm's functions + * directly. + * + * The macros OQS_SIG_dilithium_2_length_* and the functions OQS_SIG_dilithium_2_* + * are only defined if the algorithm dilithium_2 was enabled at compile-time + * which must be checked using the OQS_ENABLE_SIG_dilithium_2 macro. + * + * , which is included in , contains macros + * indicating which algorithms were enabled when this instance of liboqs + * was compiled. + */ +static OQS_STATUS example_stack(void) { + +#ifdef OQS_ENABLE_SIG_dilithium_2 + + OQS_STATUS rc; + + uint8_t public_key[OQS_SIG_dilithium_2_length_public_key]; + uint8_t secret_key[OQS_SIG_dilithium_2_length_secret_key]; + uint8_t message[MESSAGE_LEN]; + uint8_t signature[OQS_SIG_dilithium_2_length_signature]; + size_t message_len = MESSAGE_LEN; + size_t signature_len; + + // let's create a random test message to sign + OQS_randombytes(message, message_len); + + rc = OQS_SIG_dilithium_2_keypair(public_key, secret_key); + if (rc != OQS_SUCCESS) { + std::cerr << "ERROR: OQS_SIG_dilithium_2_keypair failed!" << std::endl; + cleanup_stack(secret_key, OQS_SIG_dilithium_2_length_secret_key); + return OQS_ERROR; + } + rc = OQS_SIG_dilithium_2_sign(signature, &signature_len, message, message_len, secret_key); + if (rc != OQS_SUCCESS) { + std::cerr << "ERROR: OQS_SIG_dilithium_2_sign failed!" << std::endl; + cleanup_stack(secret_key, OQS_SIG_dilithium_2_length_secret_key); + return OQS_ERROR; + } + rc = OQS_SIG_dilithium_2_verify(message, message_len, signature, signature_len, public_key); + if (rc != OQS_SUCCESS) { + std::cerr << "ERROR: OQS_SIG_dilithium_2_verify failed!" << std::endl; + cleanup_stack(secret_key, OQS_SIG_dilithium_2_length_secret_key); + return OQS_ERROR; + } + + std::cout << "[example_stack] OQS_SIG_dilithium_2 operations completed" << std::endl; + cleanup_stack(secret_key, OQS_SIG_dilithium_2_length_secret_key); + return OQS_SUCCESS; // success! + +#else + + std::cout << "[example_stack] OQS_SIG_dilithium_2 was not enabled at compile-time" << std::endl; + return OQS_SUCCESS; + +#endif +} + +/* This function gives an example of the signing operations, + * allocating variables dynamically on the heap and calling the generic + * OQS_SIG object. + * + * This does not require the use of compile-time macros to check if the + * algorithm in question was enabled at compile-time; instead, the caller + * must check that the OQS_SIG object returned is not nullptr. + */ +static OQS_STATUS example_heap(void) { + +#ifdef OQS_ENABLE_SIG_dilithium_2 + + size_t message_len = MESSAGE_LEN; + size_t signature_len; + OQS_STATUS rc; + + std::unique_ptr sig(OQS_SIG_new((OQS_SIG_alg_dilithium_2))); + if (sig == nullptr) { + throw std::runtime_error("[example_heap] OQS_SIG_alg_dilithium_2 was not enabled at compile-time."); + } + std::unique_ptr public_key(static_cast(malloc(sig->length_public_key))); + std::unique_ptr secret_key(static_cast(malloc(sig->length_secret_key)), OQSSecureDeleter(sig->length_secret_key)); + std::unique_ptr message(static_cast(malloc(message_len))); + std::unique_ptr signature(static_cast(malloc(sig->length_signature))); + if ((public_key == nullptr) || (secret_key == nullptr) || (message == nullptr) || (signature == nullptr)) { + throw std::runtime_error("ERROR: malloc failed!"); + } + + // let's create a random test message to sign + OQS_randombytes(message.get(), message_len); + + rc = OQS_SIG_keypair(sig.get(), public_key.get(), secret_key.get()); + if (rc != OQS_SUCCESS) { + throw std::runtime_error("ERROR: OQS_SIG_keypair failed!"); + } + rc = OQS_SIG_sign(sig.get(), signature.get(), &signature_len, message.get(), message_len, secret_key.get()); + if (rc != OQS_SUCCESS) { + throw std::runtime_error("ERROR: OQS_SIG_sign failed!"); + } + rc = OQS_SIG_verify(sig.get(), message.get(), message_len, signature.get(), signature_len, public_key.get()); + if (rc != OQS_SUCCESS) { + throw std::runtime_error("ERROR: OQS_SIG_verify failed!"); + } + + std::cout << "[example_heap] OQS_SIG_dilithium_2 operations completed." << std::endl; + return OQS_SUCCESS; // success +#else + + std::cout << "[example_heap] OQS_SIG_dilithium_2 was not enabled at compile-time." << std::endl; + return OQS_SUCCESS; + +#endif +} + +int main() { + OQS_init(); + try { + example_stack(); + example_heap(); + } + catch (std::exception e) { + std::cerr << e.what() << std::endl; + OQS_destroy(); + return EXIT_FAILURE; + } + OQS_destroy(); + return EXIT_SUCCESS; +} + +void cleanup_stack(uint8_t *secret_key, size_t secret_key_len) { + OQS_MEM_cleanse(secret_key, secret_key_len); +}